diff --git a/.travis.yml b/.travis.yml index 233117718b..042d4b8f1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -5,7 +5,23 @@ language: cpp branches: only: - master + - coverity_scan - /openmw-.*$/ +env: + global: + # The next declaration is the encrypted COVERITY_SCAN_TOKEN, created + # via the "travis encrypt" command using the project repo's public key + - secure: "jybGzAdUbqt9vWR/GEnRd96BgAi/7Zd1+2HK68j/i/8+/1YH2XxLOy4Jv/DUBhBlJIkxs/Xv8dRcUlFOclZDHX1d/9Qnsqd3oUVkD7k1y7cTOWy9TBQaE/v/kZo3LpzA3xPwwthrb0BvqIbOfIELi5fS5s8ba85WFRg3AX70wWE=" +addons: + coverity_scan: + project: + name: "OpenMW/openmw" + description: "" + notification_email: scrawl@baseoftrash.de + build_command_prepend: "cmake ." + build_command: "make -j3" + branch_pattern: coverity_scan + before_install: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./CI/before_install.linux.sh; fi - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_install.osx.sh; fi @@ -14,7 +30,8 @@ before_script: - if [ "${TRAVIS_OS_NAME}" = "osx" ]; then ./CI/before_script.osx.sh; fi script: - cd ./build - - make -j4 + - if [ "$COVERITY_SCAN_BRANCH" != 1 ]; then make -j4; fi + - if [ "$COVERITY_SCAN_BRANCH" != 1 ] && ["${TRAVIS_OS_NAME}" = "osx"]; then make package; fi after_script: - if [ "${TRAVIS_OS_NAME}" = "linux" ]; then ./openmw_test_suite; fi notifications: diff --git a/AUTHORS.md b/AUTHORS.md new file mode 100644 index 0000000000..540c0ede5a --- /dev/null +++ b/AUTHORS.md @@ -0,0 +1,207 @@ +Contributors +============ + +The OpenMW project was started in 2008 by Nicolay Korslund. +In the course of years many people have contributed to the project. + +If you feel your name is missing from this list, please notify a developer. + + +Programmers +----------- + + Marc Zinnschlag (Zini) - Lead Programmer/Project Manager + + Adam Hogan (aurix) + Aleksandar Jovanov + Alex Haddad (rainChu) + Alex McKibben (WeirdSexy) + Alexander Nadeau (wareya) + Alexander Olofsson (Ace) + Artem Kotsynyak (greye) + Arthur Moore (EmperorArthur) + athile + Bret Curtis (psi29a) + Britt Mathis (galdor557) + cc9cii + Chris Boyce (slothlife) + Chris Robinson (KittyCat) + Cory F. Cohen (cfcohen) + Cris Mihalache (Mirceam) + darkf + Dmitry Shkurskiy (endorph) + Douglas Diniz (Dgdiniz) + Douglas Mencken (dougmencken) + dreamer-dead + David Teviotdale (dteviot) + Edmondo Tommasina (edmondo) + Eduard Cot (trombonecot) + Eli2 + Emanuel Guével (potatoesmaster) + eroen + Fil Krynicki (filkry) + Gašper Sedej + gugus/gus + Hallfaer Tuilinn + Internecine + Jacob Essex (Yacoby) + Jannik Heller (scrawl) + Jason Hooks (jhooks) + jeaye + Jeffrey Haines (Jyby) + Jengerer + Joel Graff (graffy) + John Blomberg (fstp) + Jordan Ayers + Jordan Milne + Julien Voisin (jvoisin/ap0) + Karl-Felix Glatzer (k1ll) + Kevin Poitra (PuppyKevin) + Lars Söderberg (Lazaroth) + lazydev + Leon Saunders (emoose) + Lukasz Gromanowski (lgro) + Manuel Edelmann (vorenon) + Marc Bouvier (CramitDeFrog) + Marcin Hulist (Gohan) + Mark Siewert (mark76) + Marco Melletti (mellotanica) + Marco Schulze + Mateusz Kołaczek (PL_kolek) + megaton + Michael Hogan (Xethik) + Michael Mc Donnell + Michael Papageorgiou (werdanith) + Michał Bień (Glorf) + Miroslav Puda (pakanek) + MiroslavR + naclander + Narmo + Nathan Jeffords (blunted2night) + NeveHanter + Nikolay Kasyanov (corristo) + nobrakal + Nolan Poe (nopoe) + Paul McElroy (Greendogo) + Pieter van der Kloet (pvdk) + Radu-Marius Popovici (rpopovici) + rdimesio + riothamus + Robert MacGregor (Ragora) + Rohit Nirmal + Roman Melnik (Kromgart) + Roman Proskuryakov (humbug) + sandstranger + Sandy Carter (bwrsandman) + Scott Howard + Sebastian Wick (swick) + Sergey Shambir + sir_herrbatka + Stefan Galowicz (bogglez) + Stanislav Bobrov (Jiub) + Sylvain Thesnieres (Garvek) + terrorfisch + Thomas Luppi (Digmaster) + Tom Mason (wheybags) + Torben Leif Carrington (TorbenC) + viadanna + Vincent Heuken + vocollapse + +Packagers +--------- + + Alexander Olofsson (Ace) - Windows + Bret Curtis (psi29a) - Ubuntu Linux + Edmondo Tommasina (edmondo) - Gentoo Linux + Julian Ospald (hasufell) - Gentoo Linux + Karl-Felix Glatzer (k1ll) - Linux Binaries + Kenny Armstrong (artorius) - Fedora Linux + Nikolay Kasyanov (corristo) - Mac OS X + Sandy Carter (bwrsandman) - Arch Linux + +Public Relations and Translations +--------------------------------- + + Alex McKibben (WeirdSexy) - Podcaster + Artem Kotsynyak (greye) - Russian News Writer + Jim Clauwaert (Zedd) - Public Outreach + Julien Voisin (jvoisin/ap0) - French News Writer + Tom Koenderink (Okulo) - English News Writer + Lukasz Gromanowski (lgro) - English News Writer + Mickey Lyle (raevol) - Release Manager + Pithorn - Chinese News Writer + sir_herrbatka - Polish News Writer + Dawid Lakomy (Vedyimyn) - Polish News Writer + +Website +------- + + Lukasz Gromanowski (Lgro) - Website Administrator + Ryan Sardonic (Wry) - Wiki Editor + sir_herrbatka - Forum Administrator + +Formula Research +---------------- + + Hrnchamd + Epsilon + fragonard + Greendogo + HiPhish + modred11 + Myckel + natirips + Sadler + +Artwork +------- + + Necrod - OpenMW Logo + Mickey Lyle (raevol) - Wordpress Theme + Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel - OpenMW Editor Icons + +Inactive Contributors +--------------------- + + Ardekantur + Armin Preiml + Berulacks + Carl Maxwell + Diggory Hardy + Dmitry Marakasov (AMDmi3) + ElderTroll + guidoj + Jan-Peter Nilsson (peppe) + Jan Borsodi + Josua Grawitter + juanmnzsk8 + Kingpix + Lordrea + Michal Sciubidlo + Nicolay Korslund + Nekochan + pchan3 + penguinroad + psi29a + sergoz + spyboot + Star-Demon + Thoronador + Yuri Krupenin + +Additional Credits +------------------ +In this section we would like to thank people not part of OpenMW for their work. + +Thanks to Maxim Nikolaev, +for allowing us to use his excellent Morrowind fan-art on our website and in other places. + +Thanks to DokterDume, +for kindly providing us with the Moon and Star logo, used as the application icon and project logo. + +Thanks to Kevin Ryan, +for creating the icon used for the Data Files tab of the OpenMW Launcher. + +Thanks to DejaVu team, +for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms. diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000000..c2aa03dc2c --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,1622 @@ +0.35.0 +------ + + Bug #244: Clipping/static in relation to the ghostgate/fence sound. + Bug #531: Missing transparent menu items + Bug #811: Content Lists in openmw.cfg are overwritten + Bug #925: OpenCS doesn't launch because it thinks its already started + Bug #969: Water shader strange behaviour on AMD card + Bug #1049: Partially highlighted word in dialogue may cause incorrect line break + Bug #1069: omwlauncher.exe crashes due to file lock + Bug #1192: It is possible to jump on top of hostile creatures in combat + Bug #1342: Loud ambient sounds + Bug #1431: Creatures can climb the player + Bug #1605: Guard in CharGen doesn't turn around to face you when reaching stairs + Bug #1624: Moon edges don't transition properly + Bug #1634: Items dropped by PC have collision + Bug #1637: Weird NPC behaviour in Vivec, Hlaalu Ancestral Vaults? + Bug #1638: Cannot climb staircases + Bug #1648: Enchanted equipment badly handled at game reload + Bug #1663: Crash when casting spell at enemy near you + Bug #1683: Scale doesn't apply to animated collision nodes + Bug #1702: Active enchanted item forgotten + Bug #1730: Scripts names starting with digit(s) fail to compile + Bug #1743: Moons are transparent + Bug #1745: Shadows crash: Assertion `mEffects.empty()' failed. + Bug #1785: Can't equip two-handed weapon and shield + Bug #1809: Player falls too easily + Bug #1825: Sword of Perithia can´t run in OpenMW + Bug #1899: The launcher resets any alterations you´ve made in the mod list order, + Bug #1964: Idle voices/dialogs not triggered correctly + Bug #1980: Please, change default click behavior in OpenMW Launchers Data Files list + Bug #1984: Vampire corpses standing up when looting the first item + Bug #1985: Calm spell does nothing + Bug #1986: Spell name lights up on mouseover but spell cost does not + Bug #1989: Tooltip still shown when menu toggled off + Bug #2010: Raindrops Displayed While Underwater + Bug #2023: Walking into plants causes massive framedrop + Bug #2031: [MOD: Shrines - Restore Health and Cancel Options]: Restore health option doesn't work + Bug #2039: Lake Fjalding pillar of fire not rendered + Bug #2040: AI_follow should stop further from the target + Bug #2076: Slaughterfish AI + Bug #2077: Direction of long jump can be changed much more than it is possible in vanilla + Bug #2078: error during rendering: Object '' not found (const) + Bug #2105: Lockpicking causes screen sync glitch + Bug #2113: [MOD: Julan Ashlander Companion] Julan does not act correctly within the Ghostfence. + Bug #2123: Window glow mod: Collision issues + Bug #2133: Missing collision for bridges in Balmora when using Morrowind Rebirth 2.81 + Bug #2135: Casting a summon spell while the summon is active does not reset the summon. + Bug #2144: Changing equipment will unequip drawn arrows/bolts + Bug #2169: Yellow on faces when using opengl renderer and mods from overhaul on windows + Bug #2175: Pathgrid mods do not overwrite the existing pathgrid + Bug #2176: Morrowind -Russian localization end add-on ChaosHeart. Error in framelistener;object ;frenzying toush; not found + Bug #2181: Mod Morrowind crafting merchants die. + Bug #2182: mods changing skill progression double the bonus for class specialization + Bug #2183: Editor: Skills "use value" only allows integer between 0 and 99 + Bug #2184: Animated Morrowind Expanded produces an error on Open MW Launch + Bug #2185: Conditional Operator formats + Bug #2193: Quest: Gateway Ghost + Bug #2194: Cannot summon multiples of the same creature + Bug #2195: Pathgrid in the (0,0) exterior cell not loaded + Bug #2200: Outdoor NPCs can stray away and keep walking into a wall + Bug #2201: Creatures do not receive fall damage + Bug #2202: The enchantment the item can hold is calculated incorrectly + Bug #2203: Having the mod Living Cities of Vvardenfall running causes the game world to fail to load after leaving the prison ship + Bug #2204: Abot's Water Life - Book rendered incorrectly + Bug #2205: sound_waterfall script no longer compiles + Bug #2206: Dialogue script fails to compile (extra .) + Bug #2207: Script using – instead of - character does not compile + Bug #2208: Failing dialogue scripts in french Morrowind.esm + Bug #2214: LGNPC Vivec Redoran 1.62 and The King Rat (Size and inventory Issues) + Bug #2215: Beast races can use enchanted boots + Bug #2218: Incorrect names body parts in 3D models for open helmet with skinning + Bug #2219: Orcs in Ghorak Manor in Caldera don't attack if you pick their pockets. + Bug #2220: Chargen race preview head incorrect orientation + Bug #2223: Reseting rock falling animation + Bug #2224: Fortify Attribute effects do not stack when Spellmaking. + Bug #2226: OpenCS pseudo-crash + Bug #2230: segfaulting when entering Ald'ruhn with a specific mod: "fermeture la nuit" (closed by night) + Bug #2233: Area effect spells on touch do not have the area effect + Bug #2234: Dwarven Crossbow clips through the ground when dropped + Bug #2235: class SettingsBase<> reverses the order of entries with multiple keys. + Bug #2236: Weird two handed longsword + torch interaction + Bug #2237: Shooting arrows while sneaking do not agro + Bug #2238: Bipedal creatures not using weapons are not handled properly + Bug #2245: Incorrect topic highlighting in HT_SpyBaladas quest + Bug #2252: Tab completion incomplete for places using COC from the console. + Bug #2255: Camera reverts to first person on load + Bug #2259: enhancement: the save/load progress bar is not very progressive + Bug #2263: TogglePOV can not be bound to Alt key + Bug #2267: dialogue disabling via mod + Bug #2268: Highlighting Files with load order problems in Data Files tab of Launcher + Bug #2276: [Mod]ShotN issues with Karthwasten + Bug #2283: Count argument for PlaceAt functions not working + Bug #2284: Local map notes should be visible on door marker leading to the cell with the note + Bug #2293: There is a graphical glitch at the end of the spell's animation in 3rd Person (looking over the shoulder) view + Bug #2294: When using Skyrim UI Overhaul, the tops of pinnable menus are invisible + Bug #2302: Random leveled items repeat way too often in a single dungeon + Bug #2306: Enchanted arrows should not be retrievable from corpses + Bug #2308: No sound effect when drawing the next throwing knife + Bug #2309: Guards chase see the player character even if they're invisible + Bug #2319: Inverted controls and other issues after becoming a vampire + Bug #2324: Spells cast when crossing cell border are imprinted on the local map + Bug #2330: Actors with Drain Health effect retain health after dying + Bug #2331: tgm (god mode) won't allow the player to cast spells if the player doesn't have enough mana + Bug #2332: Error in framelistener: Need a skeleton to attach the arrow to + Feature #114: ess-Importer + Feature #504: Editor: Delete selected rows from result windows + Feature #1024: Addition of remaining equipping hotkeys + Feature #1067: Handle NIF interpolation type 4 (XYZ_ROTATION_KEY) + Feature #1125: AI fast-forward + Feature #1228: Drowning while knocked out + Feature #1325: Editor: Opening window and User Settings window cleanup + Feature #1537: Ability to change the grid size from 3x3 to 5x5 (or more with good pc) + Feature #1546: Leveled list script functions + Feature #1659: Test dialogue scripts in --script-all + Feature #1720: NPC lookAt controller + Feature #2178: Load initial particle system state from NIF files + Feature #2197: Editor: When clicking on a script error in the report window set cursor in script editor to the respective line/column + Feature #2261: Warn when loading save games with mod mismatch + Feature #2313: ess-Importer: convert global map exploration overlay + Feature #2318: Add commandline option to load a save game + Task #810: Rename "profile" to "content list" + Task #2196: Label local/global openmw.cfg files via comments + +0.34.0 +------ + + Bug #904: omwlauncher doesn't allow installing Tribunal and Bloodmoon if only MW is installed + Bug #986: Launcher: renaming profile names is broken + Bug #1061: "Browse to CD..." launcher crash + Bug #1135: Launcher crashes if user does not have write permission + Bug #1231: Current installer in launcher does not correctly import russian Morrowind.ini settings from setup.inx + Bug #1288: Fix the Alignment of the Resolution Combobox + Bug #1343: BIK videos occasionally out of sync with audio + Bug #1684: Morrowind Grass Mod graphical glitches + Bug #1734: NPC in fight with invisible/sneaking player + Bug #1982: Long class names are cut off in the UI + Bug #2012: Editor: OpenCS script compiler sometimes fails to find IDs + Bug #2015: Running while levitating does not affect speed but still drains fatigue + Bug #2018: OpenMW don´t reset modified cells to vanilla when a plugin is deselected and don´t apply changes to cells already visited. + Bug #2045: ToggleMenus command should close dialogue windows + Bug #2046: Crash: light_de_streetlight_01_223 + Bug #2047: Buglamp tooltip minor correction + Bug #2050: Roobrush floating texture bits + Bug #2053: Slaves react negatively to PC picking up slave's bracers + Bug #2055: Dremora corpses use the wrong model + Bug #2056: Mansilamat Vabdas's corpse is floating in the water + Bug #2057: "Quest: Larius Varro Tells A Little Story": Bounty not completely removed after finishing quest + Bug #2059: Silenced enemies try to cast spells anyway + Bug #2060: Editor: Special case implementation for top level window with single sub-window should be optional + Bug #2061: Editor: SubView closing that is not directly triggered by the user isn't handled properly + Bug #2063: Tribunal: Quest 'The Warlords' doesn't work + Bug #2064: Sneak attack on hostiles causes bounty + Bug #2065: Editor: Qt signal-slot error when closing a dialogue subview + Bug #2070: Loading ESP in OpenMW works but fails in OpenCS + Bug #2071: CTD in 0.33 + Bug #2073: Storm atronach animation stops now and then + Bug #2075: Molag Amur Region, Map shows water on solid ground + Bug #2080: game won't work with fair magicka regen + Bug #2082: NPCs appear frozen or switched off after leaving and quickly reentering a cell + Bug #2088: OpenMW is unable to play OGG files. + Bug #2093: Darth Gares talks to you in Ilunibi even when he's not there, screwing up the Main Quests + Bug #2095: Coordinate and rotation editing in the Reference table does not work. + Bug #2096: Some overflow fun and bartering exploit + Bug #2098: [D3D] Game crash on maximize + Bug #2099: Activate, player seems not to work + Bug #2104: Only labels are sensitive in buttons + Bug #2107: "Slowfall" effect is too weak + Bug #2114: OpenCS doesn't load an ESP file full of errors even though Vanilla MW Construction Set can + Bug #2117: Crash when encountering bandits on opposite side of river from the egg mine south of Balmora + Bug #2124: [Mod: Baldurians Transparent Glass Amor] Armor above head + Bug #2125: Unnamed NiNodes in weapons problem in First Person + Bug #2126: Dirty dialog script in tribunal.esm causing bug in Tribunal MQ + Bug #2128: Crash when picking character's face + Bug #2129: Disable the third-person zoom feature by default + Bug #2130: Ash storm particles shown too long during transition to clear sky + Bug #2137: Editor: exception caused by following the Creature column of a SoundGen record + Bug #2139: Mouse movement should be ignored during intro video + Bug #2143: Editor: Saving is broken + Bug #2145: OpenMW - crash while exiting x64 debug build + Bug #2152: You can attack Almalexia during her final monologue + Bug #2154: Visual effects behave weirdly after loading/taking a screenshot + Bug #2155: Vivec has too little magicka + Bug #2156: Azura's spirit fades away too fast + Bug #2158: [Mod]Julan Ashlander Companion 2.0: Negative magicka + Bug #2161: Editor: combat/magic/stealth values of creature not displayed correctly + Bug #2163: OpenMW can't detect death if the NPC die by the post damage effect of a magic weapon. + Bug #2168: Westly's Master Head Pack X – Some hairs aren't rendered correctly. + Bug #2170: Mods using conversations to update PC inconsistant + Bug #2180: Editor: Verifier doesn't handle Windows-specific path issues when dealing with resources + Bug #2212: Crash or unexpected behavior while closing OpenCS cell render window on OS X + Feature #238: Add UI to run INI-importer from the launcher + Feature #854: Editor: Add user setting to show status bar + Feature #987: Launcher: first launch instructions for CD need to be more explicit + Feature #1232: There is no way to set the "encoding" option using launcher UI. + Feature #1281: Editor: Render cell markers + Feature #1918: Editor: Functionality for Double-Clicking in Tables + Feature #1966: Editor: User Settings dialogue grouping/labelling/tooltips + Feature #2097: Editor: Edit position of references in 3D scene + Feature #2121: Editor: Add edit mode button to scene toolbar + Task #1965: Editor: Improve layout of user settings dialogue + +0.33.1 +------ + + Bug #2108: OpenCS fails to build + +0.33.0 +------ + + Bug #371: If console assigned to ` (probably to any symbolic key), "`" symbol will be added to console every time it closed + Bug #1148: Some books'/scrolls' contents are displayed incorrectly + Bug #1290: Editor: status bar is not updated when record filter is changed + Bug #1292: Editor: Documents are not removed on closing the last view + Bug #1301: Editor: File->Exit only checks the document it was issued from. + Bug #1353: Bluetooth on with no speaker connected results in significantly longer initial load times + Bug #1436: NPCs react from too far distance + Bug #1472: PC is placed on top of following NPC when changing cell + Bug #1487: Tall PC can get stuck in staircases + Bug #1565: Editor: Subviews are deleted on shutdown instead when they are closed + Bug #1623: Door marker on Ghorak Manor's balcony makes PC stuck + Bug #1633: Loaddoor to Sadrith Mora, Telvanni Council House spawns PC in the air + Bug #1655: Use Appropriate Application Icons on Windows + Bug #1679: Tribunal expansion, Meryn Othralas the backstage manager in the theatre group in Mournhold in the great bazaar district is floating a good feet above the ground. + Bug #1705: Rain is broken in third person + Bug #1706: Thunder and lighting still occurs while the game is paused during the rain + Bug #1708: No long jumping + Bug #1710: Editor: ReferenceableID drag to references record filter field creates incorrect filter + Bug #1712: Rest on Water + Bug #1715: "Cancel" button is not always on the same side of menu + Bug #1725: Editor: content file can be opened multiple times from the same dialogue + Bug #1730: [MOD: Less Generic Nerevarine] Compile failure attempting to enter the Corprusarium. + Bug #1733: Unhandled ffmpeg sample formats + Bug #1735: Editor: "Edit Record" context menu button not opening subview for journal infos + Bug #1750: Editor: record edits result in duplicate entries + Bug #1789: Editor: Some characters cannot be used in addon name + Bug #1803: Resizing the map does not keep the pre-resize center at the post-resize center + Bug #1821: Recovering Cloudcleaver quest: attacking Sosia is considered a crime when you side with Hlormar + Bug #1838: Editor: Preferences window appears off screen + Bug #1839: Editor: Record filter title should be moved two pixels to the right + Bug #1849: Subrecord error in MAO_Containers + Bug #1854: Knocked-out actors don't fully act knocked out + Bug #1855: "Soul trapped" sound doesn't play + Bug #1857: Missing sound effect for enchanted items with empty charge + Bug #1859: Missing console command: ResetActors (RA) + Bug #1861: Vendor category "MagicItems" is unhandled + Bug #1862: Launcher doesn't start if a file listed in launcher.cfg has correct name but wrong capitalization + Bug #1864: Editor: Region field for cell record in dialogue subview not working + Bug #1869: Editor: Change label "Musics" to "Music" + Bug #1870: Goblins killed while knocked down remain in knockdown-pose + Bug #1874: CellChanged events should not trigger when crossing exterior cell border + Bug #1877: Spriggans killed instantly if hit while regening + Bug #1878: Magic Menu text not un-highlighting correctly when going from spell to item as active magic + Bug #1881: Stuck in ceiling when entering castle karstaags tower + Bug #1884: Unlit torches still produce a burning sound + Bug #1885: Can type text in price field in barter window + Bug #1887: Equipped items do not emit sounds + Bug #1889: draugr lord aesliip will attack you and remain non-hostile + Bug #1892: Guard asks player to pay bounty of 0 gold + Bug #1895: getdistance should only return max float if ref and target are in different worldspaces + Bug #1896: Crash Report + Bug #1897: Conjured Equipment cant be re-equipped if removed + Bug #1898: Only Gidar Verothan follows you during establish the mine quest + Bug #1900: Black screen when you open the door and breath underwater + Bug #1904: Crash on casting recall spell + Bug #1906: Bound item checks should use the GMSTs + Bug #1907: Bugged door. Mournhold, The Winged Guar + Bug #1908: Crime reported for attacking Drathas Nerus's henchmen while they attack Dilborn + Bug #1909: Weird Quest Flow Infidelities quest + Bug #1910: Follower fighting with gone npc + Bug #1911: Npcs will drown themselves + Bug #1912: World map arrow stays static when inside a building + Bug #1920: Ulyne Henim disappears when game is loaded inside Vas + Bug #1922: alchemy-> potion of paralyze + Bug #1923: "levitation magic cannot be used here" shows outside of tribunal + Bug #1927: AI prefer melee over magic. + Bug #1929: Tamriel Rebuilt: Named cells that lie within the overlap with Morrowind.esm are not shown + Bug #1932: BTB - Spells 14.1 magic effects don´t overwrite the Vanilla ones but are added + Bug #1935: Stacks of items are worth more when sold individually + Bug #1940: Launcher does not list addon files if base game file is renamed to a different case + Bug #1946: Mod "Tel Nechim - moved" breaks savegames + Bug #1947: Buying/Selling price doesn't properly affect the growth of mercantile skill + Bug #1950: followers from east empire company quest will fight each other if combat happens with anything + Bug #1958: Journal can be scrolled indefinitely with a mouse wheel + Bug #1959: Follower not leaving party on quest end + Bug #1960: Key bindings not always saved correctly + Bug #1961: Spell merchants selling racial bonus spells + Bug #1967: segmentation fault on load saves + Bug #1968: Jump sounds are not controlled by footsteps slider, sound weird compared to footsteps + Bug #1970: PC suffers silently when taking damage from lava + Bug #1971: Dwarven Sceptre collision area is not removed after killing one + Bug #1974: Dalin/Daris Norvayne follows player indefinitely + Bug #1975: East Empire Company faction rank breaks during Raven Rock questline + Bug #1979: 0 strength = permanently over encumbered + Bug #1993: Shrine blessing in Maar Gan doesn't work + Bug #2008: Enchanted items do not recharge + Bug #2011: Editor: OpenCS script compiler doesn't handle member variable access properly + Bug #2016: Dagoth Ur already dead in Facility Cavern + Bug #2017: Fighters Guild Quest: The Code Book - dialogue loop when UMP is loaded. + Bug #2019: Animation of 'Correct UV Mudcrabs' broken + Bug #2022: Alchemy window - Removing ingredient doesn't remove the number of ingredients + Bug #2025: Missing mouse-over text for non affordable items + Bug #2028: [MOD: Tamriel Rebuilt] Crashing when trying to enter interior cell "Ruinous Keep, Great Hall" + Bug #2029: Ienith Brothers Thiev's Guild quest journal entry not adding + Feature #471: Editor: Special case implementation for top-level window with single sub-window + Feature #472: Editor: Sub-Window re-use settings + Feature #704: Font colors import from fallback settings + Feature #879: Editor: Open sub-views in a new top-level window + Feature #932: Editor: magic effect table + Feature #937: Editor: Path Grid table + Feature #938: Editor: Sound Gen table + Feature #1117: Death and LevelUp music + Feature #1226: Editor: Request UniversalId editing from table columns + Feature #1545: Targeting console on player + Feature #1597: Editor: Render terrain + Feature #1695: Editor: add column for CellRef's global variable + Feature #1696: Editor: use ESM::Cell's RefNum counter + Feature #1697: Redden player's vision when hit + Feature #1856: Spellcasting for non-biped creatures + Feature #1879: Editor: Run OpenMW with the currently edited content list + Task #1851: Move AI temporary state out of AI packages + Task #1865: Replace char type in records + +0.32.0 +------ + + Bug #1132: Unable to jump when facing a wall + Bug #1341: Summoned Creatures do not immediately disappear when killed. + Bug #1430: CharGen Revamped script does not compile + Bug #1451: NPCs shouldn't equip weapons prior to fighting + Bug #1461: Stopped start scripts do not restart on load + Bug #1473: Dead NPC standing and in 2 pieces + Bug #1482: Abilities are depleted when interrupted during casting + Bug #1503: Behaviour of NPCs facing the player + Bug #1506: Missing character, French edition: three-points + Bug #1528: Inventory very slow after 2 hours + Bug #1540: Extra arguments should be ignored for script functions + Bug #1541: Helseth's Champion: Tribunal + Bug #1570: Journal cannot be opened while in inventory screen + Bug #1573: PC joins factions at random + Bug #1576: NPCs aren't switching their weapons when out of ammo + Bug #1579: Guards detect creatures in far distance, instead on sight + Bug #1588: The Siege of the Skaal Village: bloodmoon + Bug #1593: The script compiler isn't recognising some names that contain a - + Bug #1606: Books: Question marks instead of quotation marks + Bug #1608: Dead bodies prevent door from opening/closing. + Bug #1609: Imperial guards in Sadrith Mora are not using their spears + Bug #1610: The bounty number is not displayed properly with high numbers + Bug #1620: Implement correct formula for auto-calculated NPC spells + Bug #1630: Boats standing vertically in Vivec + Bug #1635: Arrest dialogue is executed second time after I select "Go to jail" + Bug #1637: Weird NPC behaviour in Vivec, Hlaalu Ancestral Vaults? + Bug #1641: Persuasion dialog remains after loading, possibly resulting in crash + Bug #1644: "Goodbye" and similar options on dialogues prevents escape working properly. + Bug #1646: PC skill stats are not updated immediately when changing equipment + Bug #1652: Non-aggressive creature + Bug #1653: Quickloading while the container window is open crashes the game + Bug #1654: Priority of checks in organic containers + Bug #1656: Inventory items merge issue when repairing + Bug #1657: Attacked state of NPCs is not saved properly + Bug #1660: Rank dialogue condition ignored + Bug #1668: Game starts on day 2 instead of day 1 + Bug #1669: Critical Strikes while fighting a target who is currently fighting me + Bug #1672: OpenCS doesn't save the projects + Bug #1673: Fatigue decreasing by only one point when running + Bug #1675: Minimap and localmap graphic glitches + Bug #1676: Pressing the OK button on the travel menu cancels the travel and exits the menu + Bug #1677: Sleeping in a rented bed is considered a crime + Bug #1685: NPCs turn towards player even if invisible/sneaking + Bug #1686: UI bug: cursor is clicking "world/local" map button while inventory window is closed? + Bug #1690: Double clicking on a inventory window header doesn't close it. + Bug #1693: Spell Absorption does not absorb shrine blessings + Bug #1694: journal displays learned topics as quests + Bug #1700: Sideways scroll of text boxes + Bug #1701: Player enchanting requires player hold money, always 100% sucessful. + Bug #1704: self-made Fortify Intelligence/Drain willpower potions are broken + Bug #1707: Pausing the game through the esc menu will silence rain, pausing it by opening the inventory will not. + Bug #1709: Remesa Othril is hostile to Hlaalu members + Bug #1713: Crash on load after death + Bug #1719: Blind effect has slight border at the edge of the screen where it is ineffective. + Bug #1722: Crash after creating enchanted item, reloading saved game + Bug #1723: Content refs that are stacked share the same index after unstacking + Bug #1726: Can't finish Aengoth the Jeweler's quest : Retrieve the Scrap Metal + Bug #1727: Targets almost always resist soultrap scrolls + Bug #1728: Casting a soultrap spell on invalid target yields no message + Bug #1729: Chop attack doesn't work if walking diagonally + Bug #1732: Error handling for missing script function arguments produces weird message + Bug #1736: Alt-tabbing removes detail from overworld map. + Bug #1737: Going through doors with (high magnitude?) leviation will put the player high up, possibly even out of bounds. + Bug #1739: Setting a variable on an NPC from another NPC's dialogue result sets the wrong variable + Bug #1741: The wait dialogue doesn't black the screen out properly during waiting. + Bug #1742: ERROR: Object 'sDifficulty' not found (const) + Bug #1744: Night sky in Skies V.IV (& possibly v3) by SWG rendered incorrectly + Bug #1746: Bow/marksman weapon condition does not degrade with use + Bug #1749: Constant Battle Music + Bug #1752: Alt-Tabbing in the character menus makes the paper doll disappear temporarily + Bug #1753: Cost of training is not added to merchant's inventory + Bug #1755: Disposition changes do not persist if the conversation menu is closed by purchasing training. + Bug #1756: Caught Blight after being cured of Corprus + Bug #1758: Crash Upon Loading New Cell + Bug #1760: Player's Magicka is not recalculated upon drained or boosted intelligence + Bug #1761: Equiped torches lost on reload + Bug #1762: Your spell did not get a target. Soul trap. Gorenea Andrano + Bug #1763: Custom Spell Magicka Cost + Bug #1765: Azuras Star breaks on recharging item + Bug #1767: GetPCRank did not handle ignored explicit references + Bug #1772: Dark Brotherhood Assassins never use their Carved Ebony Dart, sticking to their melee weapon. + Bug #1774: String table overflow also occurs when loading TheGloryRoad.esm + Bug #1776: dagoth uthol runs in slow motion + Bug #1778: Incorrect values in spellmaking window + Bug #1779: Icon of Master Propylon Index is not visible + Bug #1783: Invisible NPC after looting corpse + Bug #1787: Health Calculation + Bug #1788: Skeletons, ghosts etc block doors when we try to open + Bug #1791: [MOD: LGNPC Foreign Quarter] NPC in completely the wrong place. + Bug #1792: Potions should show more effects + Bug #1793: Encumbrance while bartering + Bug #1794: Fortify attribute not affecting fatigue + Bug #1795: Too much magicka + Bug #1796: "Off by default" torch burning + Bug #1797: Fish too slow + Bug #1798: Rest until healed shouldn't show with full health and magicka + Bug #1802: Mark location moved + Bug #1804: stutter with recent builds + Bug #1810: attack gothens dremora doesnt agro the others. + Bug #1811: Regression: Crash Upon Loading New Cell + Bug #1812: Mod: "QuickChar" weird button placement + Bug #1815: Keys show value and weight, Vanilla Morrowind's keys dont. + Bug #1817: Persuasion results do not show using unpatched MW ESM + Bug #1818: Quest B3_ZainabBride moves to stage 47 upon loading save while Falura Llervu is following + Bug #1823: AI response to theft incorrect - only guards react, in vanilla everyone does. + Bug #1829: On-Target Spells Rendered Behind Water Surface Effects + Bug #1830: Galsa Gindu's house is on fire + Bug #1832: Fatal Error: OGRE Exception(2:InvalidParametersException) + Bug #1836: Attacked Guards open "fine/jail/resist"-dialogue after killing you + Bug #1840: Infinite recursion in ActionTeleport + Bug #1843: Escorted people change into player's cell after completion of escort stage + Bug #1845: Typing 'j' into 'Name' fields opens the journal + Bug #1846: Text pasted into the console still appears twice (Windows) + Bug #1847: "setfatigue 0" doesn't render NPC unconscious + Bug #1848: I can talk to unconscious actors + Bug #1866: Crash when player gets killed by a creature summoned by him + Bug #1868: Memory leaking when openmw window is minimized + Feature #47: Magic Effects + Feature #642: Control NPC mouth movement using current Say sound + Feature #939: Editor: Resources tables + Feature #961: AI Combat for magic (spells, potions and enchanted items) + Feature #1111: Collision script instructions (used e.g. by Lava) + Feature #1120: Command creature/humanoid magic effects + Feature #1121: Elemental shield magic effects + Feature #1122: Light magic effect + Feature #1139: AI: Friendly hits + Feature #1141: AI: combat party + Feature #1326: Editor: Add tooltips to all graphical buttons + Feature #1489: Magic effect Get/Mod/Set functions + Feature #1505: Difficulty slider + Feature #1538: Targeted scripts + Feature #1571: Allow creating custom markers on the local map + Feature #1615: Determine local variables from compiled scripts instead of the values in the script record + Feature #1616: Editor: Body part record verifier + Feature #1651: Editor: Improved keyboard navigation for scene toolbar + Feature #1666: Script blacklisting + Feature #1711: Including the Git revision number from the command line "--version" switch. + Feature #1721: NPC eye blinking + Feature #1740: Scene toolbar buttons for selecting which type of elements are rendered + Feature #1790: Mouse wheel scrolling for the journal + Feature #1850: NiBSPArrayController + Task #768: On windows, settings folder should be "OpenMW", not "openmw" + Task #908: Share keyframe data + Task #1716: Remove defunct option for building without FFmpeg + +0.31.0 +------ + + Bug #245: Cloud direction and weather systems differ from Morrowind + Bug #275: Local Map does not always show objects that span multiple cells + Bug #538: Update CenterOnCell (COC) function behavior + Bug #618: Local and World Map Textures are sometimes Black + Bug #640: Water behaviour at night + Bug #668: OpenMW doesn't support non-latin paths on Windows + Bug #746: OpenMW doesn't check if the background music was already played + Bug #747: Door is stuck if cell is left before animation finishes + Bug #772: Disabled statics are visible on map + Bug #829: OpenMW uses up all available vram, when playing for extended time + Bug #869: Dead bodies don't collide with anything + Bug #894: Various character creation issues + Bug #897/#1369: opencs Segmentation Fault after "new" or "load" + Bug #899: Various jumping issues + Bug #952: Reflection effects are one frame delayed + Bug #993: Able to interact with world during Wait/Rest dialog + Bug #995: Dropped items can be placed inside the wall + Bug #1008: Corpses always face up upon reentering the cell + Bug #1035: Random colour patterns appearing in automap + Bug #1037: Footstep volume issues + Bug #1047: Creation of wrong links in dialogue window + Bug #1129: Summoned creature time life duration seems infinite + Bug #1134: Crimes can be committed against hostile NPCs + Bug #1136: Creature run speed formula is incorrect + Bug #1150: Weakness to Fire doesn't apply to Fire Damage in the same spell + Bug #1155: NPCs killing each other + Bug #1166: Bittercup script still does not work + Bug #1178: .bsa file names are case sensitive. + Bug #1179: Crash after trying to load game after being killed + Bug #1180: Changing footstep sound location + Bug #1196: Jumping not disabled when showing messageboxes + Bug #1202: "strange" keys are not shown in binding menu, and are not saved either, but works + Bug #1216: Broken dialog topics in russian Morrowind + Bug #1217: Container content changes based on the current position of the mouse + Bug #1234: Loading/saving issues with dynamic records + Bug #1277: Text pasted into the console appears twice + Bug #1284: Crash on New Game + Bug #1303: It's possible to skip the chargen + Bug #1304: Slaughterfish should not detect the player unless the player is in the water + Bug #1311: Editor: deleting Record Filter line does not reset the filter + Bug #1324: ERROR: ESM Error: String table overflow when loading Animated Morrowind.esp + Bug #1328: Editor: Bogus Filter created when dragging multiple records to filter bar of non-applicable table + Bug #1331: Walking/running sound persist after killing NPC`s that are walking/running. + Bug #1334: Previously equipped items not shown as unequipped after attempting to sell them. + Bug #1335: Actors ignore vertical axis when deciding to attack + Bug #1338: Unknown toggle option for shadows + Bug #1339: "Ashlands Region" is visible when beginning new game during "Loading Area" process + Bug #1340: Guards prompt Player with punishment options after resisting arrest with another guard. + Bug #1348: Regression: Bug #1098 has returned with a vengeance + Bug #1349: [TR] TR_Data mesh tr_ex_imp_gatejamb01 cannot be activated + Bug #1352: Disabling an ESX file does not disable dependent ESX files + Bug #1355: CppCat Checks OpenMW + Bug #1356: Incorrect voice type filtering for sleep interrupts + Bug #1357: Restarting the game clears saves + Bug #1360: Seyda Neen silk rider dialog problem + Bug #1361: Some lights don't work + Bug #1364: It is difficult to bind "Mouse 1" to an action in the options menu + Bug #1370: Animation compilation mod does not work properly + Bug #1371: SL_Pick01.nif from third party fails to load in openmw, but works in Vanilla + Bug #1373: When stealing in front of Sellus Gravius cannot exit the dialog + Bug #1378: Installs to /usr/local are not working + Bug #1380: Loading a save file fail if one of the content files is disabled + Bug #1382: "getHExact() size mismatch" crash on loading official plugin "Siege at Firemoth.esp" + Bug #1386: Arkngthand door will not open + Bug #1388: Segfault when modifying View Distance in Menu options + Bug #1389: Crash when loading a save after dying + Bug #1390: Apostrophe characters not displayed [French version] + Bug #1391: Custom made icon background texture for magical weapons and stuff isn't scaled properly on GUI. + Bug #1393: Coin icon during the level up dialogue are off of the background + Bug #1394: Alt+F4 doesn't work on Win version + Bug #1395: Changing rings switches only the last one put on + Bug #1396: Pauldron parts aren't showing when the robe is equipped + Bug #1402: Dialogue of some shrines have wrong button orientation + Bug #1403: Items are floating in the air when they're dropped onto dead bodies. + Bug #1404: Forearms are not rendered on Argonian females + Bug #1407: Alchemy allows making potions from two of the same item + Bug #1408: "Max sale" button gives you all the items AND all the trader's gold + Bug #1409: Rest "Until Healed" broken for characters with stunted magicka. + Bug #1412: Empty travel window opens while playing through start game + Bug #1413: Save game ignores missing writing permission + Bug #1414: The Underground 2 ESM Error + Bug #1416: Not all splash screens in the Splash directory are used + Bug #1417: Loading saved game does not terminate + Bug #1419: Skyrim: Home of the Nords error + Bug #1422: ClearInfoActor + Bug #1423: ForceGreeting closes existing dialogue windows + Bug #1425: Cannot load save game + Bug #1426: Read skill books aren't stored in savegame + Bug #1427: Useless items can be set under hotkeys + Bug #1429: Text variables in journal + Bug #1432: When attacking friendly NPC, the crime is reported and bounty is raised after each swing + Bug #1435: Stealing priceless items is without punishment + Bug #1437: Door marker at Jobasha's Rare Books is spawning PC in the air + Bug #1440: Topic selection menu should be wider + Bug #1441: Dropping items on the rug makes them inaccessible + Bug #1442: When dropping and taking some looted items, bystanders consider that as a crime + Bug #1444: Arrows and bolts are not dropped where the cursor points + Bug #1445: Security trainers offering acrobatics instead + Bug #1447: Character dash not displayed, French edition + Bug #1448: When the player is killed by the guard while having a bounty on his head, the guard dialogue opens over and over instead of loading dialogue + Bug #1454: Script error in SkipTutorial + Bug #1456: Bad lighting when using certain Morrowind.ini generated by MGE + Bug #1457: Heart of Lorkan comes after you when attacking it + Bug #1458: Modified Keybindings are not remembered + Bug #1459: Dura Gra-Bol doesn't respond to PC attack + Bug #1462: Interior cells not loaded with Morrowind Patch active + Bug #1469: Item tooltip should show the base value, not real value + Bug #1477: Death count is not stored in savegame + Bug #1478: AiActivate does not trigger activate scripts + Bug #1481: Weapon not rendered when partially submerged in water + Bug #1483: Enemies are attacking even while dying + Bug #1486: ESM Error: Don't know what to do with INFO + Bug #1490: Arrows shot at PC can end up in inventory + Bug #1492: Monsters respawn on top of one another + Bug #1493: Dialogue box opens with follower NPC even if NPC is dead + Bug #1494: Paralysed cliffracers remain airbourne + Bug #1495: Dialogue box opens with follower NPC even the game is paused + Bug #1496: GUI messages are not cleared when loading another saved game + Bug #1499: Underwater sound sometimes plays when transitioning from interior. + Bug #1500: Targetted spells and water. + Bug #1502: Console error message on info refusal + Bug #1507: Bloodmoon MQ The Ritual of Beasts: Can't remove the arrow + Bug #1508: Bloodmoon: Fort Frostmoth, cant talk with Carnius Magius + Bug #1516: PositionCell doesn't move actors to current cell + Bug #1518: ForceGreeting broken for explicit references + Bug #1522: Crash after attempting to play non-music file + Bug #1523: World map empty after loading interior save + Bug #1524: Arrows in waiting/resting dialog act like minimum and maximum buttons + Bug #1525: Werewolf: Killed NPC's don't fill werewolfs hunger for blood + Bug #1527: Werewolf: Detect life detects wrong type of actor + Bug #1529: OpenMW crash during "the shrine of the dead" mission (tribunal) + Bug #1530: Selected text in the console has the same color as the background + Bug #1539: Barilzar's Mazed Band: Tribunal + Bug #1542: Looping taunts from NPC`s after death: Tribunal + Bug #1543: OpenCS crash when using drag&drop in script editor + Bug #1547: Bamz-Amschend: Centurion Archers combat problem + Bug #1548: The Missing Hand: Tribunal + Bug #1549: The Mad God: Tribunal, Dome of Serlyn + Bug #1557: A bounty is calculated from actual item cost + Bug #1562: Invisible terrain on top of Red Mountain + Bug #1564: Cave of the hidden music: Bloodmoon + Bug #1567: Editor: Deleting of referenceables does not work + Bug #1568: Picking up a stack of items and holding the enter key and moving your mouse around paints a bunch of garbage on screen. + Bug #1574: Solstheim: Drauger cant inflict damage on player + Bug #1578: Solstheim: Bonewolf running animation not working + Bug #1585: Particle effects on PC are stopped when paralyzed + Bug #1589: Tribunal: Crimson Plague quest does not update when Gedna Relvel is killed + Bug #1590: Failed to save game: compile error + Bug #1598: Segfault when making Drain/Fortify Skill spells + Bug #1599: Unable to switch to fullscreen + Bug #1613: Morrowind Rebirth duplicate objects / vanilla objects not removed + Bug #1618: Death notice fails to show up + Bug #1628: Alt+Tab Segfault + Feature #32: Periodic Cleanup/Refill + Feature #41: Precipitation and weather particles + Feature #568: Editor: Configuration setup + Feature #649: Editor: Threaded loading + Feature #930: Editor: Cell record saving + Feature #934: Editor: Body part table + Feature #935: Editor: Enchantment effect table + Feature #1162: Dialogue merging + Feature #1174: Saved Game: add missing creature state + Feature #1177: Saved Game: fog of war state + Feature #1312: Editor: Combat/Magic/Stealth values for creatures are not displayed + Feature #1314: Make NPCs and creatures fight each other + Feature #1315: Crime: Murder + Feature #1321: Sneak skill enhancements + Feature #1323: Handle restocking items + Feature #1332: Saved Game: levelled creatures + Feature #1347: modFactionReaction script instruction + Feature #1362: Animated main menu support + Feature #1433: Store walk/run toggle + Feature #1449: Use names instead of numbers for saved game files and folders + Feature #1453: Adding Delete button to the load menu + Feature #1460: Enable Journal screen while in dialogue + Feature #1480: Play Battle music when in combat + Feature #1501: Followers unable to fast travel with you + Feature #1520: Disposition and distance-based aggression/ShouldAttack + Feature #1595: Editor: Object rendering in cells + Task #940: Move license to locations where applicable + Task #1333: Remove cmake git tag reading + Task #1566: Editor: Object rendering refactoring + +0.30.0 +------ + + Bug #416: Extreme shaking can occur during cell transitions while moving + Bug #1003: Province Cyrodiil: Ogre Exception in Stirk + Bug #1071: Crash when given a non-existent content file + Bug #1080: OpenMW allows resting/using a bed while in combat + Bug #1097: Wrong punishment for stealing in Census and Excise Office at the start of a new game + Bug #1098: Unlocked evidence chests should get locked after new evidence is put into them + Bug #1099: NPCs that you attacked still fight you after you went to jail/paid your fine + Bug #1100: Taking items from a corpse is considered stealing + Bug #1126: Some creatures can't get close enough to attack + Bug #1144: Killed creatures seem to die again each time player transitions indoors/outdoors + Bug #1181: loading a saved game does not reset the player control status + Bug #1185: Collision issues in Addamasartus + Bug #1187: Athyn Sarethi mission, rescuing varvur sarethi from the doesnt end the mission + Bug #1189: Crash when entering interior cell "Gnisis, Arvs-Drelen" + Bug #1191: Picking up papers without inventory in new game + Bug #1195: NPCs do not equip torches in certain interiors + Bug #1197: mouse wheel makes things scroll too fast + Bug #1200: door blocked by monsters + Bug #1201: item's magical charges are only refreshed when they are used + Bug #1203: Scribs do not defend themselves + Bug #1204: creatures life is not empty when they are dead + Bug #1205: armor experience does not progress when hits are taken + Bug #1206: blood particules always red. Undeads and mechanicals should have a different one. + Bug #1209: Tarhiel never falls + Bug #1210: journal adding script is ran again after having saved/loaded + Bug #1224: Names of custom classes are not properly handled in save games + Bug #1227: Editor: Fixed case handling for broken localised versions of Morrowind.esm + Bug #1235: Indoors walk stutter + Bug #1236: Aborting intro movie brings up the menu + Bug #1239: NPCs get stuck when walking past each other + Bug #1240: BTB - Settings 14.1 and Health Bar. + Bug #1241: BTB - Character and Khajiit Prejudice + Bug #1248: GUI Weapon icon is changed to hand-to-hand after save load + Bug #1254: Guild ranks do not show in dialogue + Bug #1255: When opening a container and selecting "Take All", the screen flashes blue + Bug #1260: Level Up menu doesn't show image when using a custom class + Bug #1265: Quit Menu Has Misaligned Buttons + Bug #1270: Active weapon icon is not updated when weapon is repaired + Bug #1271: NPC Stuck in hovering "Jumping" animation + Bug #1272: Crash when attempting to load Big City esm file. + Bug #1276: Editor: Dropping a region into the filter of a cell subview fails + Bug #1286: Dialogue topic list clips with window frame + Bug #1291: Saved game: store faction membership + Bug #1293: Pluginless Khajiit Head Pack by ashiraniir makes OpenMW close. + Bug #1294: Pasting in console adds text to end, not at cursor + Bug #1295: Conversation loop when asking about "specific place" in Vivec + Bug #1296: Caius doesn't leave at start of quest "Mehra Milo and the Lost Prophecies" + Bug #1297: Saved game: map markers + Bug #1302: ring_keley script causes vector::_M_range_check exception + Bug #1309: Bug on "You violated the law" dialog + Bug #1319: Creatures sometimes rendered incorrectly + Feature #50: Ranged Combat + Feature #58: Sneaking Skill + Feature #73: Crime and Punishment + Feature #135: Editor: OGRE integration + Feature #541: Editor: Dialogue Sub-Views + Feature #853: Editor: Rework User Settings + Feature #944: Editor: lighting modes + Feature #945: Editor: Camera navigation mode + Feature #953: Trader gold + Feature #1140: AI: summoned creatures + Feature #1142: AI follow: Run stance + Feature #1154: Not all NPCs get aggressive when one is attacked + Feature #1169: Terrain threading + Feature #1172: Loading screen and progress bars during saved/loading game + Feature #1173: Saved Game: include weather state + Feature #1207: Class creation form does not remember + Feature #1220: Editor: Preview Subview + Feature #1223: Saved Game: Local Variables + Feature #1229: Quicksave, quickload, autosave + Feature #1230: Deleting saves + Feature #1233: Bribe gold is placed into NPCs inventory + Feature #1252: Saved Game: quick key bindings + Feature #1273: Editor: Region Map context menu + Feature #1274: Editor: Region Map drag & drop + Feature #1275: Editor: Scene subview drop + Feature #1282: Non-faction member crime recognition. + Feature #1289: NPCs return to default position + Task #941: Remove unused cmake files + +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 + Bug #417: Apply weather instantly when teleporting + Bug #566: Global Map position marker not updated for interior cells + Bug #712: Looting corpse delay + Bug #716: Problem with the "Vurt's Ascadian Isles Mod" mod + Bug #805: Two TR meshes appear black (v0.24RC) + Bug #841: Third-person activation distance taken from camera rather than head + Bug #845: NPCs hold torches during the day + Bug #855: Vvardenfell Visages Volume I some hairs don´t appear since 0,24 + Bug #856: Maormer race by Mac Kom - The heads are way up + Bug #864: Walk locks during loading in 3rd person + Bug #871: active weapon/magic item icon is not immediately made blank if item is removed during dialog + Bug #882: Hircine's Ring doesn't always work + Bug #909: [Tamriel Rebuilt] crashes in Akamora + Bug #922: Launcher writing merged openmw.cfg files + Bug #943: Random magnitude should be calculated per effect + Bug #948: Negative fatigue level should be allowed + Bug #949: Particles in world space + Bug #950: Hard crash on x64 Linux running --new-game (on startup) + Bug #951: setMagicka and setFatigue have no effect + Bug #954: Problem with equipping inventory items when using a keyboard shortcut + Bug #955: Issues with equipping torches + Bug #966: Shield is visible when casting spell + Bug #967: Game crashes when equipping silver candlestick + Bug #970: Segmentation fault when starting at Bal Isra + Bug #977: Pressing down key in console doesn't go forward in history + Bug #979: Tooltip disappears when changing inventory + Bug #980: Barter: item category is remembered, but not shown + Bug #981: Mod: replacing model has wrong position/orientation + Bug #982: Launcher: Addon unchecking is not saved + Bug #983: Fix controllers to affect objects attached to the base node + Bug #985: Player can talk to NPCs who are in combat + Bug #989: OpenMW crashes when trying to include mod with capital .ESP + Bug #991: Merchants equip items with harmful constant effect enchantments + Bug #994: Don't cap skills/attributes when set via console + Bug #998: Setting the max health should also set the current health + Bug #1005: Torches are visible when casting spells and during hand to hand combat. + Bug #1006: Many NPCs have 0 skill + Bug #1007: Console fills up with text + Bug #1013: Player randomly loses health or dies + Bug #1014: Persuasion window is not centered in maximized window + Bug #1015: Player status window scroll state resets on status change + Bug #1016: Notification window not big enough for all skill level ups + Bug #1020: Saved window positions are not rescaled appropriately on resolution change + Bug #1022: Messages stuck permanently on screen when they pile up + Bug #1023: Journals doesn't open + Bug #1026: Game loses track of torch usage. + Bug #1028: Crash on pickup of jug in Unexplored Shipwreck, Upper level + Bug #1029: Quick keys menu: Select compatible replacement when tool used up + Bug #1042: TES3 header data wrong encoding + Bug #1045: OS X: deployed OpenCS won't launch + Bug #1046: All damaged weaponry is worth 1 gold + Bug #1048: Links in "locked" dialogue are still clickable + Bug #1052: Using color codes when naming your character actually changes the name's color + Bug #1054: Spell effects not visible in front of water + Bug #1055: Power-Spell animation starts even though you already casted it that day + Bug #1059: Cure disease potion removes all effects from player, even your race bonus and race ability + Bug #1063: Crash upon checking out game start ship area in Seyda Neen + Bug #1064: openmw binaries link to unnecessary libraries + Bug #1065: Landing from a high place in water still causes fall damage + Bug #1072: Drawing weapon increases torch brightness + Bug #1073: Merchants sell stacks of gold + Feature #43: Visuals for Magic Effects + Feature #51: Ranged Magic + Feature #52: Touch Range Magic + Feature #53: Self Range Magic + Feature #54: Spell Casting + Feature #70: Vampirism + Feature #100: Combat AI + Feature #171: Implement NIF record NiFlipController + Feature #410: Window to restore enchanted item charge + Feature #647: Enchanted item glow + Feature #723: Invisibility/Chameleon magic effects + Feature #737: Resist Magicka magic effect + Feature #758: GetLOS + Feature #926: Editor: Info-Record tables + Feature #958: Material controllers + Feature #959: Terrain bump, specular, & parallax mapping + Feature #990: Request: unlock mouse when in any menu + Feature #1018: Do not allow view mode switching while performing an action + Feature #1027: Vertex morph animation (NiGeomMorpherController) + Feature #1031: Handle NiBillboardNode + Feature #1051: Implement NIF texture slot DarkTexture + Task #873: Unify OGRE initialisation + +0.27.0 +------ + + Bug #597: Assertion `dialogue->mId == id' failed in esmstore.cpp + Bug #794: incorrect display of decimal numbers + Bug #840: First-person sneaking camera height + Bug #887: Ambient sounds playing while paused + Bug #902: Problems with Polish character encoding + Bug #907: Entering third person using the mousewheel is possible even if it's impossible using the key + Bug #910: Some CDs not working correctly with Unshield installer + Bug #917: Quick character creation plugin does not work + Bug #918: Fatigue does not refill + Bug #919: The PC falls dead in Beshara - OpenMW nightly Win64 (708CDE2) + Feature #57: Acrobatics Skill + Feature #462: Editor: Start Dialogue + Feature #546: Modify ESX selector to handle new content file scheme + Feature #588: Editor: Adjust name/path of edited content files + Feature #644: Editor: Save + Feature #710: Editor: Configure script compiler context + Feature #790: God Mode + Feature #881: Editor: Allow only one instance of OpenCS + Feature #889: Editor: Record filtering + Feature #895: Extinguish torches + Feature #898: Breath meter enhancements + Feature #901: Editor: Default record filter + Feature #913: Merge --master and --plugin switches + +0.26.0 +------ + + Bug #274: Inconsistencies in the terrain + Bug #557: Already-dead NPCs do not equip clothing/items. + Bug #592: Window resizing + Bug #612: [Tamriel Rebuilt] Missing terrain (South of Tel Oren) + Bug #664: Heart of lorkhan acts like a dead body (container) + Bug #767: Wonky ramp physics & water + Bug #780: Swimming out of water + Bug #792: Wrong ground alignment on actors when no clipping + Bug #796: Opening and closing door sound issue + Bug #797: No clipping hinders opening and closing of doors + Bug #799: sliders in enchanting window + Bug #838: Pressing key during startup procedure freezes the game + Bug #839: Combat/magic stances during character creation + Bug #843: [Tribunal] Dark Brotherhood assassin appears without equipment + Bug #844: Resting "until healed" option given even with full stats + Bug #846: Equipped torches are invisible. + Bug #847: Incorrect formula for autocalculated NPC initial health + Bug #850: Shealt weapon sound plays when leaving magic-ready stance + Bug #852: Some boots do not produce footstep sounds + Bug #860: FPS bar misalignment + Bug #861: Unable to print screen + Bug #863: No sneaking and jumping at the same time + Bug #866: Empty variables in [Movies] section of Morrowind.ini gets imported into OpenMW.cfg as blank fallback option and crashes game on start. + Bug #867: Dancing girls in "Suran, Desele's House of Earthly Delights" don't dance. + Bug #868: Idle animations are repeated + Bug #874: Underwater swimming close to the ground is jerky + Bug #875: Animation problem while swimming on the surface and looking up + Bug #876: Always a starting upper case letter in the inventory + Bug #878: Active spell effects don't update the layout properly when ended + Bug #891: Cell 24,-12 (Tamriel Rebuilt) crashes on load + Bug #896: New game sound issue + Feature #49: Melee Combat + Feature #71: Lycanthropy + Feature #393: Initialise MWMechanics::AiSequence from ESM::AIPackageList + Feature #622: Multiple positions for inventory window + Feature #627: Drowning + Feature #786: Allow the 'Activate' key to close the countdialog window + Feature #798: Morrowind installation via Launcher (Linux/Max OS only) + Feature #851: First/Third person transitions with mouse wheel + Task #689: change PhysicActor::enableCollisions + Task #707: Reorganise Compiler + +0.25.0 +------ + + Bug #411: Launcher crash on OS X < 10.8 + Bug #604: Terrible performance drop in the Census and Excise Office. + Bug #676: Start Scripts fail to load + Bug #677: OpenMW does not accept script names with - + Bug #766: Extra space in front of topic links + Bug #793: AIWander Isn't Being Passed The Repeat Parameter + Bug #795: Sound playing with drawn weapon and crossing cell-border + Bug #800: can't select weapon for enchantment + Bug #801: Player can move while over-encumbered + Bug #802: Dead Keys not working + Bug #808: mouse capture + Bug #809: ini Importer does not work without an existing cfg file + Bug #812: Launcher will run OpenMW with no ESM or ESP selected + Bug #813: OpenMW defaults to Morrowind.ESM with no ESM or ESP selected + Bug #817: Dead NPCs and Creatures still have collision boxes + Bug #820: Incorrect sorting of answers (Dialogue) + Bug #826: mwinimport dumps core when given an unknown parameter + Bug #833: getting stuck in door + Bug #835: Journals/books not showing up properly. + Feature #38: SoundGen + Feature #105: AI Package: Wander + Feature #230: 64-bit compatibility for OS X + Feature #263: Hardware mouse cursors + Feature #449: Allow mouse outside of window while paused + Feature #736: First person animations + Feature #750: Using mouse wheel in third person mode + Feature #822: Autorepeat for slider buttons + +0.24.0 +------ + + Bug #284: Book's text misalignment + Bug #445: Camera able to get slightly below floor / terrain + Bug #582: Seam issue in Red Mountain + Bug #632: Journal Next Button shows white square + Bug #653: IndexedStore ignores index + Bug #694: Parser does not recognize float values starting with . + Bug #699: Resource handling broken with Ogre 1.9 trunk + Bug #718: components/esm/loadcell is using the mwworld subsystem + Bug #729: Levelled item list tries to add nonexistent item + Bug #730: Arrow buttons in the settings menu do not work. + Bug #732: Erroneous behavior when binding keys + Bug #733: Unclickable dialogue topic + Bug #734: Book empty line problem + Bug #738: OnDeath only works with implicit references + Bug #740: Script compiler fails on scripts with special names + Bug #742: Wait while no clipping + Bug #743: Problem with changeweather console command + Bug #744: No wait dialogue after starting a new game + Bug #748: Player is not able to unselect objects with the console + Bug #751: AddItem should only spawn a message box when called from dialogue + Bug #752: The enter button has several functions in trade and looting that is not impelemted. + Bug #753: Fargoth's Ring Quest Strange Behavior + Bug #755: Launcher writes duplicate lines into settings.cfg + Bug #759: Second quest in mages guild does not work + Bug #763: Enchantment cast cost is wrong + Bug #770: The "Take" and "Close" buttons in the scroll GUI are stretched incorrectly + Bug #773: AIWander Isn't Being Passed The Correct idle Values + Bug #778: The journal can be opened at the start of a new game + Bug #779: Divayth Fyr starts as dead + Bug #787: "Batch count" on detailed FPS counter gets cut-off + Bug #788: chargen scroll layout does not match vanilla + Feature #60: Atlethics Skill + Feature #65: Security Skill + Feature #74: Interaction with non-load-doors + Feature #98: Render Weapon and Shield + Feature #102: AI Package: Escort, EscortCell + Feature #182: Advanced Journal GUI + Feature #288: Trading enhancements + Feature #405: Integrate "new game" into the menu + Feature #537: Highlight dialogue topic links + Feature #658: Rotate, RotateWorld script instructions and local rotations + Feature #690: Animation Layering + Feature #722: Night Eye/Blind magic effects + Feature #735: Move, MoveWorld script instructions. + Feature #760: Non-removable corpses + +0.23.0 +------ + + Bug #522: Player collides with placeable items + Bug #553: Open/Close sounds played when accessing main menu w/ Journal Open + Bug #561: Tooltip word wrapping delay + Bug #578: Bribing works incorrectly + Bug #601: PositionCell fails on negative coordinates + Bug #606: Some NPCs hairs not rendered with Better Heads addon + Bug #609: Bad rendering of bone boots + Bug #613: Messagebox causing assert to fail + Bug #631: Segfault on shutdown + Bug #634: Exception when talking to Calvus Horatius in Mournhold, royal palace courtyard + Bug #635: Scale NPCs depending on race + Bug #643: Dialogue Race select function is inverted + Bug #646: Twohanded weapons don't work properly + Bug #654: Crash when dropping objects without a collision shape + Bug #655/656: Objects that were disabled or deleted (but not both) were added to the scene when re-entering a cell + Bug #660: "g" in "change" cut off in Race Menu + Bug #661: Arrille sells me the key to his upstairs room + Bug #662: Day counter starts at 2 instead of 1 + Bug #663: Cannot select "come unprepared" topic in dialog with Dagoth Ur + Bug #665: Pickpocket -> "Grab all" grabs all NPC inventory, even not listed in container window. + Bug #666: Looking up/down problem + Bug #667: Active effects border visible during loading + Bug #669: incorrect player position at new game start + Bug #670: race selection menu: sex, face and hair left button not totally clickable + Bug #671: new game: player is naked + Bug #674: buying or selling items doesn't change amount of gold + Bug #675: fatigue is not set to its maximum when starting a new game + Bug #678: Wrong rotation order causes RefData's rotation to be stored incorrectly + Bug #680: different gold coins in Tel Mara + Bug #682: Race menu ignores playable flag for some hairs and faces + Bug #685: Script compiler does not accept ":" after a function name + Bug #688: dispose corpse makes cross-hair to disappear + Bug #691: Auto equipping ignores equipment conditions + Bug #692: OpenMW doesnt load "loose file" texture packs that places resources directly in data folder + Bug #696: Draugr incorrect head offset + Bug #697: Sail transparency issue + Bug #700: "On the rocks" mod does not load its UV coordinates correctly. + Bug #702: Some race mods don't work + Bug #711: Crash during character creation + Bug #715: Growing Tauryon + Bug #725: Auto calculate stats + Bug #728: Failure to open container and talk dialogue + Bug #731: Crash with Mush-Mere's "background" topic + Feature #55/657: Item Repairing + Feature #62/87: Enchanting + Feature #99: Pathfinding + Feature #104: AI Package: Travel + Feature #129: Levelled items + Feature #204: Texture animations + Feature #239: Fallback-Settings + Feature #535: Console object selection improvements + Feature #629: Add levelup description in levelup layout dialog + Feature #630: Optional format subrecord in (tes3) header + Feature #641: Armor rating + Feature #645: OnDeath script function + Feature #683: Companion item UI + Feature #698: Basic Particles + Task #648: Split up components/esm/loadlocks + Task #695: mwgui cleanup + +0.22.0 +------ + + Bug #311: Potential infinite recursion in script compiler + Bug #355: Keyboard repeat rate (in Xorg) are left disabled after game exit. + Bug #382: Weird effect in 3rd person on water + Bug #387: Always use detailed shape for physics raycasts + Bug #420: Potion/ingredient effects do not stack + Bug #429: Parts of dwemer door not picked up correctly for activation/tooltips + Bug #434/Bug #605: Object movement between cells not properly implemented + Bug #502: Duplicate player collision model at origin + Bug #509: Dialogue topic list shifts inappropriately + Bug #513: Sliding stairs + Bug #515: Launcher does not support non-latin strings + Bug #525: Race selection preview camera wrong position + Bug #526: Attributes / skills should not go below zero + Bug #529: Class and Birthsign menus options should be preselected + Bug #530: Lock window button graphic missing + Bug #532: Missing map menu graphics + Bug #545: ESX selector does not list ESM files properly + Bug #547: Global variables of type short are read incorrectly + Bug #550: Invisible meshes collision and tooltip + Bug #551: Performance drop when loading multiple ESM files + Bug #552: Don't list CG in options if it is not available + Bug #555: Character creation windows "OK" button broken + Bug #558: Segmentation fault when Alt-tabbing with console opened + Bug #559: Dialog window should not be available before character creation is finished + Bug #560: Tooltip borders should be stretched + Bug #562: Sound should not be played when an object cannot be picked up + Bug #565: Water animation speed + timescale + Bug #572: Better Bodies' textures don't work + Bug #573: OpenMW doesn't load if TR_Mainland.esm is enabled (Tamriel Rebuilt mod) + Bug #574: Moving left/right should not cancel auto-run + Bug #575: Crash entering the Chamber of Song + Bug #576: Missing includes + Bug #577: Left Gloves Addon causes ESMReader exception + Bug #579: Unable to open container "Kvama Egg Sack" + Bug #581: Mimicking vanilla Morrowind water + Bug #583: Gender not recognized + Bug #586: Wrong char gen behaviour + Bug #587: "End" script statements with spaces don't work + Bug #589: Closing message boxes by pressing the activation key + Bug #590: Ugly Dagoth Ur rendering + Bug #591: Race selection issues + Bug #593: Persuasion response should be random + Bug #595: Footless guard + Bug #599: Waterfalls are invisible from a certain distance + Bug #600: Waterfalls rendered incorrectly, cut off by water + Bug #607: New beast bodies mod crashes + Bug #608: Crash in cell "Mournhold, Royal Palace" + Bug #611: OpenMW doesn't find some of textures used in Tamriel Rebuilt + Bug #613: Messagebox causing assert to fail + Bug #615: Meshes invisible from above water + Bug #617: Potion effects should be hidden until discovered + Bug #619: certain moss hanging from tree has rendering bug + Bug #621: Batching bloodmoon's trees + Bug #623: NiMaterialProperty alpha unhandled + Bug #628: Launcher in latest master crashes the game + Bug #633: Crash on startup: Better Heads + Bug #636: Incorrect Char Gen Menu Behavior + Feature #29: Allow ESPs and multiple ESMs + Feature #94: Finish class selection-dialogue + Feature #149: Texture Alphas + Feature #237: Run Morrowind-ini importer from launcher + Feature #286: Update Active Spell Icons + Feature #334: Swimming animation + Feature #335: Walking animation + Feature #360: Proper collision shapes for NPCs and creatures + Feature #367: Lights that behave more like original morrowind implementation + Feature #477: Special local scripting variables + Feature #528: Message boxes should close when enter is pressed under certain conditions. + Feature #543: Add bsa files to the settings imported by the ini importer + Feature #594: coordinate space and utility functions + Feature #625: Zoom in vanity mode + Task #464: Refactor launcher ESX selector into a re-usable component + Task #624: Unified implementation of type-variable sub-records + +0.21.0 +------ + + Bug #253: Dialogs don't work for Russian version of Morrowind + Bug #267: Activating creatures without dialogue can still activate the dialogue GUI + Bug #354: True flickering lights + Bug #386: The main menu's first entry is wrong (in french) + Bug #479: Adding the spell "Ash Woe Blight" to the player causes strange attribute oscillations + Bug #495: Activation Range + Bug #497: Failed Disposition check doesn't stop a dialogue entry from being returned + Bug #498: Failing a disposition check shouldn't eliminate topics from the the list of those available + Bug #500: Disposition for most NPCs is 0/100 + Bug #501: Getdisposition command wrongly returns base disposition + Bug #506: Journal UI doesn't update anymore + Bug #507: EnableRestMenu is not a valid command - change it to EnableRest + Bug #508: Crash in Ald Daedroth Shrine + Bug #517: Wrong price calculation when untrading an item + Bug #521: MWGui::InventoryWindow creates a duplicate player actor at the origin + Bug #524: Beast races are able to wear shoes + Bug #527: Background music fails to play + Bug #533: The arch at Gnisis entrance is not displayed + Bug #534: Terrain gets its correct shape only some time after the cell is loaded + Bug #536: The same entry can be added multiple times to the journal + Bug #539: Race selection is broken + Bug #544: Terrain normal map corrupt when the map is rendered + Feature #39: Video Playback + Feature #151: ^-escape sequences in text output + Feature #392: Add AI related script functions + Feature #456: Determine required ini fallback values and adjust the ini importer accordingly + Feature #460: Experimental DirArchives improvements + Feature #540: Execute scripts of objects in containers/inventories in active cells + Task #401: Review GMST fixing + Task #453: Unify case smashing/folding + Task #512: Rewrite utf8 component + +0.20.0 +------ + + Bug #366: Changing the player's race during character creation does not change the look of the player character + Bug #430: Teleporting and using loading doors linking within the same cell reloads the cell + Bug #437: Stop animations when paused + Bug #438: Time displays as "0 a.m." when it should be "12 a.m." + Bug #439: Text in "name" field of potion/spell creation window is persistent + Bug #440: Starting date at a new game is off by one day + Bug #442: Console window doesn't close properly sometimes + Bug #448: Do not break container window formatting when item names are very long + Bug #458: Topics sometimes not automatically added to known topic list + Bug #476: Auto-Moving allows player movement after using DisablePlayerControls + Bug #478: After sleeping in a bed the rest dialogue window opens automtically again + Bug #492: On creating potions the ingredients are removed twice + Feature #63: Mercantile skill + Feature #82: Persuasion Dialogue + Feature #219: Missing dialogue filters/functions + Feature #369: Add a FailedAction + Feature #377: Select head/hair on character creation + Feature #391: Dummy AI package classes + Feature #435: Global Map, 2nd Layer + Feature #450: Persuasion + Feature #457: Add more script instructions + Feature #474: update the global variable pcrace when the player's race is changed + Task #158: Move dynamically generated classes from Player class to World Class + Task #159: ESMStore rework and cleanup + Task #163: More Component Namespace Cleanup + Task #402: Move player data from MWWorld::Player to the player's NPC record + Task #446: Fix no namespace in BulletShapeLoader + +0.19.0 +------ + + Bug #374: Character shakes in 3rd person mode near the origin + Bug #404: Gamma correct rendering + Bug #407: Shoes of St. Rilm do not work + Bug #408: Rugs has collision even if they are not supposed to + Bug #412: Birthsign menu sorted incorrectly + Bug #413: Resolutions presented multiple times in launcher + Bug #414: launcher.cfg file stored in wrong directory + Bug #415: Wrong esm order in openmw.cfg + Bug #418: Sound listener position updates incorrectly + Bug #423: wrong usage of "Version" entry in openmw.desktop + Bug #426: Do not use hardcoded splash images + Bug #431: Don't use markers for raycast + Bug #432: Crash after picking up items from an NPC + Feature #21/#95: Sleeping/resting + Feature #61: Alchemy Skill + Feature #68: Death + Feature #69/#86: Spell Creation + Feature #72/#84: Travel + Feature #76: Global Map, 1st Layer + Feature #120: Trainer Window + Feature #152: Skill Increase from Skill Books + Feature #160: Record Saving + Task #400: Review GMST access + +0.18.0 +------ + + Bug #310: Button of the "preferences menu" are too small + Bug #361: Hand-to-hand skill is always 100 + Bug #365: NPC and creature animation is jerky; Characters float around when they are not supposed to + Bug #372: playSound3D uses original coordinates instead of current coordinates. + Bug #373: Static OGRE build faulty + Bug #375: Alt-tab toggle view + Bug #376: Screenshots are disable + Bug #378: Exception when drinking self-made potions + Bug #380: Cloth visibility problem + Bug #384: Weird character on doors tooltip. + Bug #398: Some objects do not collide in MW, but do so in OpenMW + Feature #22: Implement level-up + Feature #36: Hide Marker + Feature #88: Hotkey Window + Feature #91: Level-Up Dialogue + Feature #118: Keyboard and Mouse-Button bindings + Feature #119: Spell Buying Window + Feature #133: Handle resources across multiple data directories + Feature #134: Generate a suitable default-value for --data-local + Feature #292: Object Movement/Creation Script Instructions + Feature #340: AIPackage data structures + Feature #356: Ingredients use + Feature #358: Input system rewrite + Feature #370: Target handling in actions + Feature #379: Door markers on the local map + Feature #389: AI framework + Feature #395: Using keys to open doors / containers + Feature #396: Loading screens + Feature #397: Inventory avatar image and race selection head preview + Task #339: Move sounds into Action + +0.17.0 +------ + + Bug #225: Valgrind reports about 40MB of leaked memory + Bug #241: Some physics meshes still don't match + Bug #248: Some textures are too dark + Bug #300: Dependency on proprietary CG toolkit + Bug #302: Some objects don't collide although they should + Bug #308: Freeze in Balmora, Meldor: Armorer + Bug #313: openmw without a ~/.config/openmw folder segfault. + Bug #317: adding non-existing spell via console locks game + Bug #318: Wrong character normals + Bug #341: Building with Ogre Debug libraries does not use debug version of plugins + Bug #347: Crash when running openmw with --start="XYZ" + Bug #353: FindMyGUI.cmake breaks path on Windows + Bug #359: WindowManager throws exception at destruction + Bug #364: Laggy input on OS X due to bug in Ogre's event pump implementation + Feature #33: Allow objects to cross cell-borders + Feature #59: Dropping Items (replaced stopgap implementation with a proper one) + Feature #93: Main Menu + Feature #96/329/330/331/332/333: Player Control + Feature #180: Object rotation and scaling. + Feature #272: Incorrect NIF material sharing + Feature #314: Potion usage + Feature #324: Skill Gain + Feature #342: Drain/fortify dynamic stats/attributes magic effects + Feature #350: Allow console only script instructions + Feature #352: Run scripts in console on startup + Task #107: Refactor mw*-subsystems + Task #325: Make CreatureStats into a class + Task #345: Use Ogre's animation system + Task #351: Rewrite Action class to support automatic sound playing + +0.16.0 +------ + + Bug #250: OpenMW launcher erratic behaviour + Bug #270: Crash because of underwater effect on OS X + Bug #277: Auto-equipping in some cells not working + Bug #294: Container GUI ignores disabled inventory menu + Bug #297: Stats review dialog shows all skills and attribute values as 0 + Bug #298: MechanicsManager::buildPlayer does not remove previous bonuses + Bug #299: Crash in World::disable + Bug #306: Non-existent ~/.config/openmw "crash" the launcher. + Bug #307: False "Data Files" location make the launcher "crash" + Feature #81: Spell Window + Feature #85: Alchemy Window + Feature #181: Support for x.y script syntax + Feature #242: Weapon and Spell icons + Feature #254: Ingame settings window + Feature #293: Allow "stacking" game modes + Feature #295: Class creation dialog tooltips + Feature #296: Clicking on the HUD elements should show/hide the respective window + Feature #301: Direction after using a Teleport Door + Feature #303: Allow object selection in the console + Feature #305: Allow the use of = as a synonym for == + Feature #312: Compensation for slow object access in poorly written Morrowind.esm scripts + Task #176: Restructure enabling/disabling of MW-references + Task #283: Integrate ogre.cfg file in settings file + Task #290: Auto-Close MW-reference related GUI windows + +0.15.0 +------ + + Bug #5: Physics reimplementation (fixes various issues) + Bug #258: Resizing arrow's background is not transparent + Bug #268: Widening the stats window in X direction causes layout problems + Bug #269: Topic pane in dialgoue window is too small for some longer topics + Bug #271: Dialog choices are sorted incorrectly + Bug #281: The single quote character is not rendered on dialog windows + Bug #285: Terrain not handled properly in cells that are not predefined + Bug #289: Dialogue filter isn't doing case smashing/folding for item IDs + Feature #15: Collision with Terrain + Feature #17: Inventory-, Container- and Trade-Windows + Feature #44: Floating Labels above Focussed Objects + Feature #80: Tooltips + Feature #83: Barter Dialogue + Feature #90: Book and Scroll Windows + Feature #156: Item Stacking in Containers + Feature #213: Pulsating lights + Feature #218: Feather & Burden + Feature #256: Implement magic effect bookkeeping + Feature #259: Add missing information to Stats window + Feature #260: Correct case for dialogue topics + Feature #280: GUI texture atlasing + Feature #291: Ability to use GMST strings from GUI layout files + Task #255: Make MWWorld::Environment into a singleton + +0.14.0 +------ + + Bug #1: Meshes rendered with wrong orientation + Bug #6/Task #220: Picking up small objects doesn't always work + Bug #127: tcg doesn't work + Bug #178: Compablity problems with Ogre 1.8.0 RC 1 + Bug #211: Wireframe mode (toggleWireframe command) should not apply to Console & other UI + Bug #227: Terrain crashes when moving away from predefined cells + Bug #229: On OS X Launcher cannot launch game if path to binary contains spaces + Bug #235: TGA texture loading problem + Bug #246: wireframe mode does not work in water + Feature #8/#232: Water Rendering + Feature #13: Terrain Rendering + Feature #37: Render Path Grid + Feature #66: Factions + Feature #77: Local Map + Feature #78: Compass/Mini-Map + Feature #97: Render Clothing/Armour + Feature #121: Window Pinning + Feature #205: Auto equip + Feature #217: Contiainer should track changes to its content + Feature #221: NPC Dialogue Window Enhancements + Feature #233: Game settings manager + Feature #240: Spell List and selected spell (no GUI yet) + Feature #243: Draw State + Task #113: Morrowind.ini Importer + Task #215: Refactor the sound code + Task #216: Update MyGUI + +0.13.0 +------ + + Bug #145: Fixed sound problems after cell change + Bug #179: Pressing space in console triggers activation + Bug #186: CMake doesn't use the debug versions of Ogre libraries on Linux + Bug #189: ASCII 16 character added to console on it's activation on Mac OS X + Bug #190: Case Folding fails with music files + Bug #192: Keypresses write Text into Console no matter which gui element is active + Bug #196: Collision shapes out of place + Bug #202: ESMTool doesn't not work with localised ESM files anymore + Bug #203: Torch lights only visible on short distance + Bug #207: Ogre.log not written + Bug #209: Sounds do not play + Bug #210: Ogre crash at Dren plantation + Bug #214: Unsupported file format version + Bug #222: Launcher is writing openmw.cfg file to wrong location + Feature #9: NPC Dialogue Window + Feature #16/42: New sky/weather implementation + Feature #40: Fading + Feature #48: NPC Dialogue System + Feature #117: Equipping Items (backend only, no GUI yet, no rendering of equipped items yet) + Feature #161: Load REC_PGRD records + Feature #195: Wireframe-mode + Feature #198/199: Various sound effects + Feature #206: Allow picking data path from launcher if non is set + Task #108: Refactor window manager class + Task #172: Sound Manager Cleanup + Task #173: Create OpenEngine systems in the appropriate manager classes + Task #184: Adjust MSVC and gcc warning levels + Task #185: RefData rewrite + Task #201: Workaround for transparency issues + Task #208: silenced esm_reader.hpp warning + +0.12.0 +------ + + Bug #154: FPS Drop + Bug #169: Local scripts continue running if associated object is deleted + Bug #174: OpenMW fails to start if the config directory doesn't exist + Bug #187: Missing lighting + Bug #188: Lights without a mesh are not rendered + Bug #191: Taking screenshot causes crash when running installed + Feature #28: Sort out the cell load problem + Feature #31: Allow the player to move away from pre-defined cells + Feature #35: Use alternate storage location for modified object position + Feature #45: NPC animations + Feature #46: Creature Animation + Feature #89: Basic Journal Window + Feature #110: Automatically pick up the path of existing MW-installations + Feature #183: More FPS display settings + Task #19: Refactor engine class + Task #109/Feature #162: Automate Packaging + Task #112: Catch exceptions thrown in input handling functions + Task #128/#168: Cleanup Configuration File Handling + Task #131: NPC Activation doesn't work properly + Task #144: MWRender cleanup + Task #155: cmake cleanup + +0.11.1 +------ + + Bug #2: Resources loading doesn't work outside of bsa files + Bug #3: GUI does not render non-English characters + Bug #7: openmw.cfg location doesn't match + Bug #124: The TCL alias for ToggleCollision is missing. + Bug #125: Some command line options can't be used from a .cfg file + Bug #126: Toggle-type script instructions are less verbose compared with original MW + Bug #130: NPC-Record Loading fails for some NPCs + Bug #167: Launcher sets invalid parameters in ogre config + Feature #10: Journal + Feature #12: Rendering Optimisations + Feature #23: Change Launcher GUI to a tabbed interface + Feature #24: Integrate the OGRE settings window into the launcher + Feature #25: Determine openmw.cfg location (Launcher) + Feature #26: Launcher Profiles + Feature #79: MessageBox + Feature #116: Tab-Completion in Console + Feature #132: --data-local and multiple --data + Feature #143: Non-Rendering Performance-Optimisations + Feature #150: Accessing objects in cells via ID does only work for objects with all lower case IDs + Feature #157: Version Handling + Task #14: Replace tabs with 4 spaces + Task #18: Move components from global namespace into their own namespace + Task #123: refactor header files in components/esm + +0.10.0 +------ + +* NPC dialogue window (not functional yet) +* Collisions with objects +* Refactor the PlayerPos class +* Adjust file locations +* CMake files and test linking for Bullet +* Replace Ogre raycasting test for activation with something more precise +* Adjust player movement according to collision results +* FPS display +* Various Portability Improvements +* Mac OS X support is back! + +0.9.0 +----- + +* Exterior cells loading, unloading and management +* Character Creation GUI +* Character creation +* Make cell names case insensitive when doing internal lookups +* Music player +* NPCs rendering + +0.8.0 +----- + +* GUI +* Complete and working script engine +* In game console +* Sky rendering +* Sound and music +* Tons of smaller stuff + +0.7.0 +----- + +* This release is a complete rewrite in C++. +* All D code has been culled, and all modules have been rewritten. +* The game is now back up to the level of rendering interior cells and moving around, but physics, sound, GUI, and scripting still remain to be ported from the old codebase. + +0.6.0 +----- + +* Coded a GUI system using MyGUI +* Skinned MyGUI to look like Morrowind (work in progress) +* Integrated the Monster script engine +* Rewrote some functions into script code +* Very early MyGUI < > Monster binding +* Fixed Windows sound problems (replaced old openal32.dll) + +0.5.0 +----- + +* Collision detection with Bullet +* Experimental walk & fall character physics +* New key bindings: + * t toggle physics mode (walking, flying, ghost), + * n night eye, brightens the scene +* Fixed incompatability with DMD 1.032 and newer compilers +* * (thanks to tomqyp) +* Various minor changes and updates + +0.4.0 +----- + +* Switched from Audiere to OpenAL +* * (BIG thanks to Chris Robinson) +* Added complete Makefile (again) as a alternative build tool +* More realistic lighting (thanks again to Chris Robinson) +* Various localization fixes tested with Russian and French versions +* Temporary workaround for the Unicode issue: invalid UTF displayed as '?' +* Added ns option to disable sound, for debugging +* Various bug fixes +* Cosmetic changes to placate gdc Wall + +0.3.0 +----- + +* Built and tested on Windows XP +* Partial support for FreeBSD (exceptions do not work) +* You no longer have to download Monster separately +* Made an alternative for building without DSSS (but DSSS still works) +* Renamed main program from 'morro' to 'openmw' +* Made the config system more robust +* Added oc switch for showing Ogre config window on startup +* Removed some config files, these are auto generated when missing. +* Separated plugins.cfg into linux and windows versions. +* Updated Makefile and sources for increased portability +* confirmed to work against OIS 1.0.0 (Ubuntu repository package) + +0.2.0 +----- + +* Compiles with gdc +* Switched to DSSS for building D code +* Includes the program esmtool + +0.1.0 +----- + +first release diff --git a/CI/before_install.osx.sh b/CI/before_install.osx.sh index b1d4f991ba..8bfe2b70f3 100755 --- a/CI/before_install.osx.sh +++ b/CI/before_install.osx.sh @@ -6,4 +6,4 @@ export CC=clang brew tap openmw/openmw brew update brew unlink boost -brew install cmake openmw-mygui openmw-bullet openmw-sdl2 openmw-ffmpeg pkg-config qt unshield +brew install openmw-mygui openmw-bullet openmw-sdl2 openmw-ffmpeg openmw/openmw/qt unshield diff --git a/CMakeLists.txt b/CMakeLists.txt index 5a923c6312..2a117d42d3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,5 +1,12 @@ project(OpenMW) +# If the user doesn't supply a CMAKE_BUILD_TYPE via command line, choose one for them. +IF(NOT CMAKE_BUILD_TYPE) + SET(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING + "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." + FORCE) +ENDIF() + if (APPLE) set(APP_BUNDLE_NAME "${CMAKE_PROJECT_NAME}.app") @@ -12,37 +19,21 @@ set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake/) message(STATUS "Configuring OpenMW...") set(OPENMW_VERSION_MAJOR 0) -set(OPENMW_VERSION_MINOR 33) -set(OPENMW_VERSION_RELEASE 1) +set(OPENMW_VERSION_MINOR 35) +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}") +set(GIT_CHECKOUT FALSE) 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(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") - set(OPENMW_VERSION_TAGHASH "${TAGHASH}") - - message(STATUS "OpenMW version ${OPENMW_VERSION}") - else(MATCH) - message(WARNING "Failed to get valid version information from Git") - endif(MATCH) + set(GIT_CHECKOUT TRUE) else(GIT_FOUND) message(WARNING "Git executable not found") endif(GIT_FOUND) @@ -67,6 +58,8 @@ option(OGRE_STATIC "Link static build of Ogre and Ogre Plugins into the binarie option(BOOST_STATIC "Link static build of Boost into the binaries" FALSE) option(SDL2_STATIC "Link static build of SDL into the binaries" FALSE) +set(CUSTOM_OGRE_PLUGIN_DIR "" CACHE PATH "Specify a custom directory for Ogre plugins (autodetected by default)") + option(OPENMW_UNITY_BUILD "Use fewer compilation units to speed up compile time" FALSE) # Apps and tools @@ -74,10 +67,11 @@ option(BUILD_BSATOOL "build BSA extractor" ON) option(BUILD_ESMTOOL "build ESM inspector" ON) option(BUILD_LAUNCHER "build Launcher" ON) option(BUILD_MWINIIMPORTER "build MWiniImporter" ON) +option(BUILD_ESSIMPORTER "build ESS (Morrowind save game) importer" ON) option(BUILD_OPENCS "build OpenMW Construction Set" ON) option(BUILD_WIZARD "build Installation Wizard" ON) option(BUILD_WITH_CODE_COVERAGE "Enable code coverage with gconv" OFF) -option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest and GMock frameworks" OFF) +option(BUILD_UNITTESTS "Enable Unittests with Google C++ Unittest" OFF) option(BUILD_NIFTEST "build nif file tester" OFF) option(BUILD_MYGUI_PLUGIN "build MyGUI plugin for OpenMW resources, to use with MyGUI tools" ON) @@ -117,7 +111,7 @@ set(OENGINE_OGRE set(OENGINE_GUI ${LIBS_DIR}/openengine/gui/loglistener.cpp ${LIBS_DIR}/openengine/gui/manager.cpp - ${LIBS_DIR}/openengine/gui/layout.hpp + ${LIBS_DIR}/openengine/gui/layout.cpp ) set(OENGINE_BULLET @@ -338,8 +332,10 @@ if (APPLE AND OPENMW_OSX_DEPLOYMENT) # make it empty so plugin loading code can check this and try to find plugins inside app bundle add_definitions(-DOGRE_PLUGIN_DIR="") else() - if (NOT DEFINED ${OGRE_PLUGIN_DIR}) + if (CUSTOM_OGRE_PLUGIN_DIR STREQUAL "") set(OGRE_PLUGIN_DIR ${OGRE_PLUGIN_DIR_REL}) + else() + set(OGRE_PLUGIN_DIR ${CUSTOM_OGRE_PLUGIN_DIR}) endif() add_definitions(-DOGRE_PLUGIN_DIR="${OGRE_PLUGIN_DIR}") @@ -382,8 +378,8 @@ configure_file(${OpenMW_SOURCE_DIR}/files/opencs/defaultfilters if (NOT WIN32 AND NOT APPLE) configure_file(${OpenMW_SOURCE_DIR}/files/openmw.desktop "${OpenMW_BINARY_DIR}/openmw.desktop") - configure_file(${OpenMW_SOURCE_DIR}/files/opencs.desktop - "${OpenMW_BINARY_DIR}/opencs.desktop") + configure_file(${OpenMW_SOURCE_DIR}/files/openmw-cs.desktop + "${OpenMW_BINARY_DIR}/openmw-cs.desktop") endif() # Compiler settings @@ -395,13 +391,19 @@ if (CMAKE_COMPILER_IS_GNUCC) if ("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) SET(CMAKE_CXX_FLAGS "-Wno-unused-but-set-parameter ${CMAKE_CXX_FLAGS}") endif("${GCC_VERSION}" VERSION_GREATER 4.6 OR "${GCC_VERSION}" VERSION_EQUAL 4.6) +elseif (MSVC) + # Enable link-time code generation globally for all linking + set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /GL") + set(CMAKE_EXE_LINKER_FLAGS_RELEASE "${CMAKE_EXE_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_SHARED_LINKER_FLAGS_RELEASE "${CMAKE_SHARED_LINKER_FLAGS_RELEASE} /LTCG") + set(CMAKE_STATIC_LINKER_FLAGS_RELEASE "${CMAKE_STATIC_LINKER_FLAGS_RELEASE} /LTCG") endif (CMAKE_COMPILER_IS_GNUCC) IF(NOT WIN32 AND NOT APPLE) # Linux building # Paths SET(BINDIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH "Where to install binaries") - SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH "Where to install libraries") + SET(LIBDIR "${CMAKE_INSTALL_PREFIX}/lib${LIB_SUFFIX}" CACHE PATH "Where to install libraries") SET(DATAROOTDIR "${CMAKE_INSTALL_PREFIX}/share" CACHE PATH "Sets the root of data directories to a non-default location") SET(GLOBAL_DATA_PATH "${DATAROOTDIR}/games/" CACHE PATH "Set data path prefix") SET(DATADIR "${GLOBAL_DATA_PATH}/openmw" CACHE PATH "Sets the openmw data directories to a non-default location") @@ -417,7 +419,7 @@ IF(NOT WIN32 AND NOT APPLE) # Install binaries INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw" DESTINATION "${BINDIR}" ) IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/omwlauncher" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-launcher" DESTINATION "${BINDIR}" ) ENDIF(BUILD_LAUNCHER) IF(BUILD_BSATOOL) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/bsatool" DESTINATION "${BINDIR}" ) @@ -426,10 +428,13 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/esmtool" DESTINATION "${BINDIR}" ) ENDIF(BUILD_ESMTOOL) IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/mwiniimport" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-iniimporter" DESTINATION "${BINDIR}" ) ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_ESSIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-essimporter" DESTINATION "${BINDIR}" ) + ENDIF(BUILD_ESSIMPORTER) IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/opencs" DESTINATION "${BINDIR}" ) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/openmw-cs" DESTINATION "${BINDIR}" ) ENDIF(BUILD_OPENCS) IF(BUILD_NIFTEST) INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/niftest" DESTINATION "${BINDIR}" ) @@ -446,23 +451,23 @@ IF(NOT WIN32 AND NOT APPLE) INSTALL(FILES "extern/shiny/License.txt" DESTINATION "${LICDIR}" RENAME "Shiny License.txt" ) # Install icon and desktop file - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/launcher/images/openmw.png" DESTINATION "${ICONDIR}" COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.desktop" DESTINATION "${DATAROOTDIR}/applications" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") - INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/opencs.png" DESTINATION "${ICONDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw-cs.desktop" DESTINATION "${DATAROOTDIR}/applications" COMPONENT "opencs") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/files/opencs/openmw-cs.png" DESTINATION "${ICONDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install global configuration files - INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") - INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/settings-default.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/transparency-overrides.cfg" DESTINATION "${SYSCONFDIR}" COMPONENT "openmw") + INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "${SYSCONFDIR}" RENAME "openmw.cfg" COMPONENT "openmw") IF(BUILD_OPENCS) - INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${SYSCONFDIR}" PERMISSIONS OWNER_READ OWNER_WRITE GROUP_READ WORLD_READ COMPONENT "opencs") + INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION "${SYSCONFDIR}" COMPONENT "opencs") ENDIF(BUILD_OPENCS) # Install resources - INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ COMPONENT "Resources") + INSTALL(DIRECTORY "${OpenMW_BINARY_DIR}/resources" DESTINATION "${DATADIR}" COMPONENT "Resources") INSTALL(DIRECTORY DESTINATION "${DATADIR}/data" COMPONENT "Resources") ENDIF(NOT WIN32 AND NOT APPLE) @@ -470,8 +475,9 @@ if(WIN32) FILE(GLOB dll_files "${OpenMW_BINARY_DIR}/Release/*.dll") INSTALL(FILES ${dll_files} DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/openmw.cfg.install" DESTINATION "." RENAME "openmw.cfg") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/CHANGELOG.md" DESTINATION "." RENAME "CHANGELOG.txt") + INSTALL(FILES "${OpenMW_SOURCE_DIR}/README.md" DESTINATION "." RENAME "README.txt") INSTALL(FILES - "${OpenMW_SOURCE_DIR}/readme.txt" "${OpenMW_SOURCE_DIR}/Docs/license/GPL3.txt" "${OpenMW_SOURCE_DIR}/Docs/license/DejaVu Font License.txt" "${OpenMW_BINARY_DIR}/settings-default.cfg" @@ -480,13 +486,16 @@ if(WIN32) DESTINATION ".") IF(BUILD_LAUNCHER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/omwlauncher.exe" DESTINATION ".") + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-launcher.exe" DESTINATION ".") ENDIF(BUILD_LAUNCHER) IF(BUILD_MWINIIMPORTER) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/mwiniimport.exe" DESTINATION ".") + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-iniimporter.exe" DESTINATION ".") ENDIF(BUILD_MWINIIMPORTER) + IF(BUILD_ESSIMPORTER) + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-essimporter.exe" DESTINATION ".") + ENDIF(BUILD_ESSIMPORTER) IF(BUILD_OPENCS) - INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/opencs.exe" DESTINATION ".") + INSTALL(PROGRAMS "${OpenMW_BINARY_DIR}/Release/openmw-cs.exe" DESTINATION ".") INSTALL(FILES "${OpenMW_BINARY_DIR}/opencs.ini" DESTINATION ".") ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) @@ -507,26 +516,26 @@ if(WIN32) SET(CPACK_PACKAGE_VERSION_PATCH ${OPENMW_VERSION_RELEASE}) SET(CPACK_PACKAGE_EXECUTABLES "openmw;OpenMW") IF(BUILD_LAUNCHER) - SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};omwlauncher;OpenMW Launcher") + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-launcher;OpenMW Launcher") ENDIF(BUILD_LAUNCHER) IF(BUILD_OPENCS) - SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};opencs;OpenMW Construction Set") + SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-cs;OpenMW Construction Set") ENDIF(BUILD_OPENCS) IF(BUILD_WIZARD) SET(CPACK_PACKAGE_EXECUTABLES "${CPACK_PACKAGE_EXECUTABLES};openmw-wizard;OpenMW Wizard") ENDIF(BUILD_WIZARD) - SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\readme.txt'") + SET(CPACK_NSIS_CREATE_ICONS_EXTRA "CreateShortCut '\$SMPROGRAMS\\\\$STARTMENU_FOLDER\\\\Readme.lnk' '\$INSTDIR\\\\README.txt'") SET(CPACK_NSIS_DELETE_ICONS_EXTRA " !insertmacro MUI_STARTMENU_GETFOLDER Application $MUI_TEMP Delete \\\"$SMPROGRAMS\\\\$MUI_TEMP\\\\Readme.lnk\\\" ") - SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/readme.txt") - SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/readme.txt") + SET(CPACK_RESOURCE_FILE_README "${OpenMW_SOURCE_DIR}/README.md") + SET(CPACK_PACKAGE_DESCRIPTION_FILE "${OpenMW_SOURCE_DIR}/README.md") SET(CPACK_NSIS_EXECUTABLES_DIRECTORY ".") SET(CPACK_NSIS_DISPLAY_NAME "OpenMW ${OPENMW_VERSION}") SET(CPACK_NSIS_HELP_LINK "http:\\\\\\\\www.openmw.org") SET(CPACK_NSIS_URL_INFO_ABOUT "http:\\\\\\\\www.openmw.org") - SET(CPACK_NSIS_INSTALLED_ICON_NAME "omwlauncher.exe") + SET(CPACK_NSIS_INSTALLED_ICON_NAME "openmw-launcher.exe") SET(CPACK_NSIS_MUI_ICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") SET(CPACK_NSIS_MUI_UNIICON "${OpenMW_SOURCE_DIR}/files/windows/openmw.ico") SET(CPACK_PACKAGE_ICON "${OpenMW_SOURCE_DIR}\\\\files\\\\openmw.bmp") @@ -595,6 +604,10 @@ if (BUILD_MWINIIMPORTER) add_subdirectory( apps/mwiniimporter ) endif() +if (BUILD_ESSIMPORTER) + add_subdirectory (apps/essimporter ) +endif() + if (BUILD_OPENCS) add_subdirectory (apps/opencs) endif() @@ -696,7 +709,7 @@ if (WIN32) set_target_properties(oics PROPERTIES COMPILE_FLAGS "${OICS_WARNINGS} ${MT_BUILD}") set_target_properties(components PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") if (BUILD_LAUNCHER) - set_target_properties(omwlauncher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-launcher PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_LAUNCHER) set_target_properties(openmw PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") if (BUILD_BSATOOL) @@ -711,10 +724,10 @@ if (WIN32) if (BUILD_OPENCS) # QT triggers an informational warning that the object layout may differ when compiled with /vd2 set(OPENCS_WARNINGS "${WARNINGS} ${MT_BUILD} /wd4435") - set_target_properties(opencs PROPERTIES COMPILE_FLAGS ${OPENCS_WARNINGS}) + set_target_properties(openmw-cs PROPERTIES COMPILE_FLAGS ${OPENCS_WARNINGS}) endif (BUILD_OPENCS) if (BUILD_MWINIIMPORTER) - set_target_properties(mwiniimport PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") + set_target_properties(openmw-iniimporter PROPERTIES COMPILE_FLAGS "${WARNINGS} ${MT_BUILD}") endif (BUILD_MWINIIMPORTER) endif(MSVC) @@ -758,7 +771,7 @@ if (APPLE) set(OPENMW_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${APP_BUNDLE_NAME}") - set(OPENCS_BUNDLE_NAME "OpenCS.app") + set(OPENCS_BUNDLE_NAME "OpenMW-CS.app") set(OPENCS_APP "\${CMAKE_INSTALL_PREFIX}/${INSTALL_SUBDIR}/${OPENCS_BUNDLE_NAME}") set(ABSOLUTE_PLUGINS "") @@ -870,4 +883,3 @@ if (DOXYGEN_FOUND) WORKING_DIRECTORY ${OpenMW_BINARY_DIR} COMMENT "Generating documentation for the github-pages at ${DOXYGEN_PAGES_OUTPUT_DIR}" VERBATIM) endif () - diff --git a/README.md b/README.md new file mode 100644 index 0000000000..63a3138960 --- /dev/null +++ b/README.md @@ -0,0 +1,99 @@ +OpenMW +====== + +[![Build Status](https://img.shields.io/travis/OpenMW/openmw.svg?style=plastic)](https://travis-ci.org/OpenMW/openmw) [![Coverity Scan Build Status](https://scan.coverity.com/projects/3740/badge.svg)](https://scan.coverity.com/projects/3740) + +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.35.0 +* License: GPL (see docs/license/GPL3.txt for more information) +* Website: http://www.openmw.org +* IRC: #openmw on irc.freenode.net + +Font Licenses: +* DejaVuLGCSansMono.ttf: custom (see docs/license/DejaVu Font License.txt for more information) + +Getting Started +--------------- + +* [Official forums](https://forum.openmw.org/) +* [Installation instructions](https://wiki.openmw.org/index.php?title=Installation_Instructions) +* [Build from source](https://wiki.openmw.org/index.php?title=Development_Environment_Setup) +* [Testing the game](https://wiki.openmw.org/index.php?title=Testing) +* [How to contribute](https://wiki.openmw.org/index.php?title=Contribution_Wanted) +* [Report a bug](http://bugs.openmw.org/projects/openmw) - read the [guidelines](https://wiki.openmw.org/index.php?title=Bug_Reporting_Guidelines) before submitting your first bug! +* [Known issues] (http://bugs.openmw.org/projects/openmw/issues?utf8=%E2%9C%93&set_filter=1&f%5B%5D=status_id&op%5Bstatus_id%5D=%3D&v%5Bstatus_id%5D%5B%5D=7&f%5B%5D=tracker_id&op%5Btracker_id%5D=%3D&v%5Btracker_id%5D%5B%5D=1&f%5B%5D=&c%5B%5D=project&c%5B%5D=tracker&c%5B%5D=status&c%5B%5D=priority&c%5B%5D=subject&c%5B%5D=assigned_to&c%5B%5D=updated_on&group_by=tracker) + +The data path +------------- + +The data path tells OpenMW where to find your Morrowind files. If you run the launcher, OpenMW should be able to pick up the location of these files on its own, if both Morrowind and OpenMW are installed properly (installing Morrowind under WINE is considered a proper install). + +Command line options +-------------------- + + Syntax: openmw + Allowed options: + --help print help message + --version print version information and quit + --data arg (=data) set data directories (later directories + have higher priority) + --data-local arg set local data directory (highest + priority) + --fallback-archive arg (=fallback-archive) + set fallback BSA archives (later + archives have higher priority) + --resources arg (=resources) set resources directory + --start arg set initial cell + --content arg content file(s): esm/esp, or + omwgame/omwaddon + --no-sound [=arg(=1)] (=0) disable all sounds + --script-verbose [=arg(=1)] (=0) verbose script output + --script-all [=arg(=1)] (=0) compile all scripts (excluding dialogue + scripts) at startup + --script-all-dialogue [=arg(=1)] (=0) compile all dialogue scripts at startup + --script-console [=arg(=1)] (=0) enable console-only script + functionality + --script-run arg select a file containing a list of + console commands that is executed on + startup + --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 + --script-blacklist arg ignore the specified script (if the use + of the blacklist is enabled) + --script-blacklist-use [=arg(=1)] (=1) + enable script blacklisting + --load-savegame arg load a save game file on game startup + (specify an absolute filename or a + filename relative to the current + working directory) + --skip-menu [=arg(=1)] (=0) skip main menu on game startup + --new-game [=arg(=1)] (=0) run new game sequence (ignored if + skip-menu=0) + --fs-strict [=arg(=1)] (=0) strict file system handling (no case + folding) + --encoding arg (=win1252) Character encoding used in OpenMW game + messages: + + win1250 - Central and Eastern European + such as Polish, Czech, Slovak, + Hungarian, Slovene, Bosnian, Croatian, + Serbian (Latin script), Romanian and + Albanian languages + + win1251 - Cyrillic alphabet such as + Russian, Bulgarian, Serbian Cyrillic + and other languages + + win1252 - Western European (Latin) + alphabet, used by default + --fallback arg fallback values + --no-grab Don't grab mouse cursor + --export-fonts [=arg(=1)] (=0) Export Morrowind .fnt fonts to PNG + image and XML file in current directory + --activate-dist arg (=-1) activation distance override diff --git a/apps/bsatool/bsatool.cpp b/apps/bsatool/bsatool.cpp index 7e47d0b8fa..c0a6dcc81f 100644 --- a/apps/bsatool/bsatool.cpp +++ b/apps/bsatool/bsatool.cpp @@ -27,8 +27,8 @@ struct Arguments void replaceAll(std::string& str, const std::string& needle, const std::string& substitute) { - int pos = str.find(needle); - while(pos != -1) + size_t pos = str.find(needle); + while(pos != std::string::npos) { str.replace(pos, needle.size(), substitute); pos = str.find(needle); @@ -138,8 +138,8 @@ bool parseOptions (int argc, char** argv, Arguments &info) else if (variables["input-file"].as< std::vector >().size() > 1) info.outdir = variables["input-file"].as< std::vector >()[1]; - info.longformat = variables.count("long"); - info.fullpath = variables.count("full-path"); + info.longformat = variables.count("long") != 0; + info.fullpath = variables.count("full-path") != 0; return true; } @@ -150,33 +150,32 @@ int extractAll(Bsa::BSAFile& bsa, Arguments& info); int main(int argc, char** argv) { - Arguments info; - if(!parseOptions (argc, argv, info)) - return 1; - - // Open file - Bsa::BSAFile bsa; try { - bsa.open(info.filename); - } - catch(std::exception &e) - { - std::cout << "ERROR reading BSA archive '" << info.filename - << "'\nDetails:\n" << e.what() << std::endl; - return 2; - } + Arguments info; + if(!parseOptions (argc, argv, info)) + return 1; - if (info.mode == "list") - return list(bsa, info); - else if (info.mode == "extract") - return extract(bsa, info); - else if (info.mode == "extractall") - return extractAll(bsa, info); - else + // Open file + Bsa::BSAFile bsa; + bsa.open(info.filename); + + if (info.mode == "list") + return list(bsa, info); + else if (info.mode == "extract") + return extract(bsa, info); + else if (info.mode == "extractall") + return extractAll(bsa, info); + else + { + std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; + return 1; + } + } + catch (std::exception& e) { - std::cout << "Unsupported mode. That is not supposed to happen." << std::endl; - return 1; + std::cerr << "ERROR reading BSA archive\nDetails:\n" << e.what() << std::endl; + return 2; } } @@ -189,9 +188,11 @@ int list(Bsa::BSAFile& bsa, Arguments& info) if(info.longformat) { // Long format + std::ios::fmtflags f(std::cout.flags()); std::cout << std::setw(50) << std::left << files[i].name; std::cout << std::setw(8) << std::left << std::dec << files[i].fileSize; std::cout << "@ 0x" << std::hex << files[i].offset << std::endl; + std::cout.flags(f); } else std::cout << files[i].name << std::endl; diff --git a/apps/esmtool/esmtool.cpp b/apps/esmtool/esmtool.cpp index ea908590a4..98e18521e0 100644 --- a/apps/esmtool/esmtool.cpp +++ b/apps/esmtool/esmtool.cpp @@ -22,7 +22,7 @@ struct ESMData { std::string author; std::string description; - int version; + unsigned int version; std::vector masters; std::deque mRecords; @@ -48,9 +48,9 @@ const std::set ESMData::sLabeledRec = // Based on the legacy struct struct Arguments { - unsigned int raw_given; - unsigned int quiet_given; - unsigned int loadcells_given; + bool raw_given; + bool quiet_given; + bool loadcells_given; bool plain_given; std::string mode; @@ -59,6 +59,7 @@ struct Arguments std::string outname; std::vector types; + std::string name; ESMData data; ESM::ESMReader reader; @@ -78,6 +79,8 @@ bool parseOptions (int argc, char** argv, Arguments &info) ("type,t", bpo::value< std::vector >(), "Show only records of this type (four character record code). May " "be specified multiple times. Only affects dump mode.") + ("name,n", bpo::value(), + "Show only the record with this name. Only affects dump mode.") ("plain,p", "Print contents of dialogs, books and scripts. " "(skipped by default)" "Only affects dump mode.") @@ -148,7 +151,9 @@ bool parseOptions (int argc, char** argv, Arguments &info) } if (variables.count("type") > 0) - info.types = variables["type"].as< std::vector >(); + info.types = variables["type"].as< std::vector >(); + if (variables.count("name") > 0) + info.name = variables["name"].as(); info.mode = variables["mode"].as(); if (!(info.mode == "dump" || info.mode == "clone" || info.mode == "comp")) @@ -177,10 +182,10 @@ bool parseOptions (int argc, char** argv, Arguments &info) if (variables["input-file"].as< std::vector >().size() > 1) info.outname = variables["input-file"].as< std::vector >()[1]; - info.raw_given = variables.count ("raw"); - info.quiet_given = variables.count ("quiet"); - info.loadcells_given = variables.count ("loadcells"); - info.plain_given = (variables.count("plain") > 0); + info.raw_given = variables.count ("raw") != 0; + info.quiet_given = variables.count ("quiet") != 0; + info.loadcells_given = variables.count ("loadcells") != 0; + info.plain_given = variables.count("plain") != 0; // Font encoding settings info.encoding = variables["encoding"].as(); @@ -203,19 +208,27 @@ int comp(Arguments& info); int main(int argc, char**argv) { - Arguments info; - if(!parseOptions (argc, argv, info)) - return 1; - - if (info.mode == "dump") - return load(info); - else if (info.mode == "clone") - return clone(info); - else if (info.mode == "comp") - return comp(info); - else + try { - std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl; + Arguments info; + if(!parseOptions (argc, argv, info)) + return 1; + + if (info.mode == "dump") + return load(info); + else if (info.mode == "clone") + return clone(info); + else if (info.mode == "comp") + return comp(info); + else + { + std::cout << "Invalid or no mode specified, dying horribly. Have a nice day." << std::endl; + return 1; + } + } + catch (std::exception& e) + { + std::cerr << "ERROR: " << e.what() << std::endl; return 1; } @@ -253,10 +266,12 @@ void loadCell(ESM::Cell &cell, ESM::ESMReader &esm, Arguments& info) std::cout << " Faction: '" << ref.mFaction << "'" << std::endl; std::cout << " Faction rank: '" << ref.mFactionRank << "'" << std::endl; std::cout << " Enchantment charge: '" << ref.mEnchantmentCharge << "'\n"; - std::cout << " Uses/health: '" << ref.mCharge << "'\n"; + std::cout << " Uses/health: '" << ref.mChargeInt << "'\n"; std::cout << " Gold value: '" << ref.mGoldValue << "'\n"; std::cout << " Blocked: '" << static_cast(ref.mReferenceBlocked) << "'" << std::endl; std::cout << " Deleted: " << deleted << std::endl; + if (!ref.mKey.empty()) + std::cout << " Key: '" << ref.mKey << "'" << std::endl; } } @@ -273,8 +288,10 @@ void printRaw(ESM::ESMReader &esm) esm.getSubName(); esm.skipHSub(); n = esm.retSubName(); + std::ios::fmtflags f(std::cout.flags()); std::cout << " " << n.toString() << " - " << esm.getSubSize() << " bytes @ 0x" << std::hex << offs << "\n"; + std::cout.flags(f); } } } @@ -348,6 +365,9 @@ int load(Arguments& info) if (id.empty()) id = esm.getHNOString("INAM"); + if (!info.name.empty() && !Misc::StringUtils::ciEqual(info.name, id)) + interested = false; + if(!quiet && interested) std::cout << "\nRecord: " << n.toString() << " '" << id << "'\n"; @@ -375,7 +395,7 @@ int load(Arguments& info) record->load(esm); if (!quiet && interested) record->print(); - if (record->getType().val == ESM::REC_CELL && loadCells) { + if (record->getType().val == ESM::REC_CELL && loadCells && interested) { loadCell(record->cast()->get(), esm, info); } @@ -420,7 +440,7 @@ int clone(Arguments& info) return 1; } - int recordCount = info.data.mRecords.size(); + size_t recordCount = info.data.mRecords.size(); int digitCount = 1; // For a nicer output if (recordCount > 9) ++digitCount; @@ -491,9 +511,9 @@ int clone(Arguments& info) if (!info.data.mCellRefs[ptr].empty()) { typedef std::deque RefList; RefList &refs = info.data.mCellRefs[ptr]; - for (RefList::iterator it = refs.begin(); it != refs.end(); ++it) + for (RefList::iterator refIt = refs.begin(); refIt != refs.end(); ++refIt) { - it->save(esm); + refIt->save(esm); } } } @@ -501,7 +521,7 @@ int clone(Arguments& info) esm.endRecord(name.toString()); saved++; - int perc = (saved / (float)recordCount)*100; + int perc = (int)((saved / (float)recordCount)*100); if (perc % 10 == 0) { std::cerr << "\r" << perc << "%"; diff --git a/apps/esmtool/labels.cpp b/apps/esmtool/labels.cpp index 9543628f51..88e188df01 100644 --- a/apps/esmtool/labels.cpp +++ b/apps/esmtool/labels.cpp @@ -13,14 +13,13 @@ #include #include -#include #include std::string bodyPartLabel(int idx) { if (idx >= 0 && idx <= 26) { - const char *bodyPartLabels[] = { + static const char *bodyPartLabels[] = { "Head", "Hair", "Neck", @@ -59,7 +58,7 @@ std::string meshPartLabel(int idx) { if (idx >= 0 && idx <= ESM::BodyPart::MP_Tail) { - const char *meshPartLabels[] = { + static const char *meshPartLabels[] = { "Head", "Hair", "Neck", @@ -86,7 +85,7 @@ std::string meshTypeLabel(int idx) { if (idx >= 0 && idx <= ESM::BodyPart::MT_Armor) { - const char *meshTypeLabels[] = { + static const char *meshTypeLabels[] = { "Skin", "Clothing", "Armor" @@ -101,7 +100,7 @@ std::string clothingTypeLabel(int idx) { if (idx >= 0 && idx <= 9) { - const char *clothingTypeLabels[] = { + static const char *clothingTypeLabels[] = { "Pants", "Shoes", "Shirt", @@ -123,7 +122,7 @@ std::string armorTypeLabel(int idx) { if (idx >= 0 && idx <= 10) { - const char *armorTypeLabels[] = { + static const char *armorTypeLabels[] = { "Helmet", "Cuirass", "Left Pauldron", @@ -146,7 +145,7 @@ std::string dialogTypeLabel(int idx) { if (idx >= 0 && idx <= 4) { - const char *dialogTypeLabels[] = { + static const char *dialogTypeLabels[] = { "Topic", "Voice", "Greeting", @@ -165,7 +164,7 @@ std::string questStatusLabel(int idx) { if (idx >= 0 && idx <= 4) { - const char *questStatusLabels[] = { + static const char *questStatusLabels[] = { "None", "Name", "Finished", @@ -182,7 +181,7 @@ std::string creatureTypeLabel(int idx) { if (idx >= 0 && idx <= 3) { - const char *creatureTypeLabels[] = { + static const char *creatureTypeLabels[] = { "Creature", "Daedra", "Undead", @@ -198,7 +197,7 @@ std::string soundTypeLabel(int idx) { if (idx >= 0 && idx <= 7) { - const char *soundTypeLabels[] = { + static const char *soundTypeLabels[] = { "Left Foot", "Right Foot", "Swim Left", @@ -218,7 +217,7 @@ std::string weaponTypeLabel(int idx) { if (idx >= 0 && idx <= 13) { - const char *weaponTypeLabels[] = { + static const char *weaponTypeLabels[] = { "Short Blade One Hand", "Long Blade One Hand", "Long Blade Two Hand", diff --git a/apps/esmtool/record.cpp b/apps/esmtool/record.cpp index 3f2ebc2ba5..97b0635fe5 100644 --- a/apps/esmtool/record.cpp +++ b/apps/esmtool/record.cpp @@ -2,6 +2,8 @@ #include "labels.hpp" #include +#include + #include void printAIPackage(ESM::AIPackage p) @@ -25,7 +27,7 @@ void printAIPackage(ESM::AIPackage p) { std::cout << " Travel Coordinates: (" << p.mTravel.mX << "," << p.mTravel.mY << "," << p.mTravel.mZ << ")" << std::endl; - std::cout << " Travel Unknown: " << (int)p.mTravel.mUnk << std::endl; + std::cout << " Travel Unknown: " << p.mTravel.mUnk << std::endl; } else if (p.mType == ESM::AI_Follow || p.mType == ESM::AI_Escort) { @@ -33,12 +35,12 @@ void printAIPackage(ESM::AIPackage p) << p.mTarget.mY << "," << p.mTarget.mZ << ")" << std::endl; std::cout << " Duration: " << p.mTarget.mDuration << std::endl; std::cout << " Target ID: " << p.mTarget.mId.toString() << std::endl; - std::cout << " Unknown: " << (int)p.mTarget.mUnk << std::endl; + std::cout << " Unknown: " << p.mTarget.mUnk << std::endl; } else if (p.mType == ESM::AI_Activate) { std::cout << " Name: " << p.mActivate.mName.toString() << std::endl; - std::cout << " Activate Unknown: " << (int)p.mActivate.mUnk << std::endl; + std::cout << " Activate Unknown: " << p.mActivate.mUnk << std::endl; } else { std::cout << " BadPackage: " << boost::format("0x%08x") % p.mType << std::endl; @@ -89,6 +91,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) case 'A': if (indicator == 'R') type_str = "Not Race"; break; case 'B': if (indicator == 'L') type_str = "Not Cell"; break; case 'C': if (indicator == 's') type_str = "Not Local"; break; + default: break; } // Append the variable name to the function string if any. @@ -110,6 +113,7 @@ std::string ruleString(ESM::DialInfo::SelectStruct ss) case '3': oper_str = ">="; break; case '4': oper_str = "< "; break; case '5': oper_str = "<="; break; + default: break; } std::ostringstream stream; @@ -430,7 +434,7 @@ void Record::print() std::cout << " Icon: " << mData.mIcon << std::endl; std::cout << " Script: " << mData.mScript << std::endl; std::cout << " Type: " << apparatusTypeLabel(mData.mData.mType) - << " (" << (int)mData.mData.mType << ")" << std::endl; + << " (" << mData.mData.mType << ")" << std::endl; std::cout << " Weight: " << mData.mData.mWeight << std::endl; std::cout << " Value: " << mData.mData.mValue << std::endl; std::cout << " Quality: " << mData.mData.mQuality << std::endl; @@ -531,10 +535,10 @@ void Record::print() std::cout << " Specialization: " << specializationLabel(mData.mData.mSpecialization) << " (" << mData.mData.mSpecialization << ")" << std::endl; for (int i = 0; i != 5; i++) - std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][0]) + std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][0]) << " (" << mData.mData.mSkills[i][0] << ")" << std::endl; for (int i = 0; i != 5; i++) - std::cout << " Minor Skill: " << skillLabel(mData.mData.mSkills[i][1]) + std::cout << " Major Skill: " << skillLabel(mData.mData.mSkills[i][1]) << " (" << mData.mData.mSkills[i][1] << ")" << std::endl; } @@ -750,7 +754,7 @@ void Record::print() if (mData.mCell != "") std::cout << " Cell: " << mData.mCell << std::endl; if (mData.mData.mDisposition > 0) - std::cout << " Disposition: " << mData.mData.mDisposition << std::endl; + std::cout << " Disposition/Journal index: " << mData.mData.mDisposition << std::endl; if (mData.mData.mGender != ESM::DialInfo::NA) std::cout << " Gender: " << mData.mData.mGender << std::endl; if (mData.mSound != "") @@ -810,13 +814,12 @@ void Record::print() { std::cout << " Coordinates: (" << mData.mX << "," << mData.mY << ")" << std::endl; std::cout << " Flags: " << landFlags(mData.mFlags) << std::endl; - std::cout << " HasData: " << mData.mHasData << std::endl; std::cout << " DataTypes: " << mData.mDataTypes << std::endl; // Seems like this should done with reference counting in the // loader to me. But I'm not really knowledgable about this // record type yet. --Cory - bool wasLoaded = mData.mDataLoaded; + bool wasLoaded = (mData.mDataLoaded != 0); if (mData.mDataTypes) mData.loadData(mData.mDataTypes); if (mData.mDataLoaded) { @@ -834,7 +837,7 @@ void Record::print() std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; std::cout << " Flags: " << creatureListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; - std::vector::iterator iit; + std::vector::iterator iit; for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Creature: Level: " << iit->mLevel << " Creature: " << iit->mId << std::endl; @@ -846,7 +849,7 @@ void Record::print() std::cout << " Chance for None: " << (int)mData.mChanceNone << std::endl; std::cout << " Flags: " << itemListFlags(mData.mFlags) << std::endl; std::cout << " Number of items: " << mData.mList.size() << std::endl; - std::vector::iterator iit; + std::vector::iterator iit; for (iit = mData.mList.begin(); iit != mData.mList.end(); ++iit) std::cout << " Inventory: Level: " << iit->mLevel << " Item: " << iit->mId << std::endl; @@ -999,7 +1002,7 @@ void Record::print() << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown2) << std::endl; std::cout << " Unknown3: " << (unsigned int)((unsigned char)mData.mNpdt12.mUnknown3) << std::endl; - std::cout << " Gold: " << (int)mData.mNpdt12.mGold << std::endl; + std::cout << " Gold: " << mData.mNpdt12.mGold << std::endl; } else { std::cout << " Level: " << mData.mNpdt52.mLevel << std::endl; @@ -1021,7 +1024,7 @@ void Record::print() std::cout << " Skills:" << std::endl; for (int i = 0; i != ESM::Skill::Length; i++) std::cout << " " << skillLabel(i) << ": " - << (int)((unsigned char)mData.mNpdt52.mSkills[i]) << std::endl; + << (int)(mData.mNpdt52.mSkills[i]) << std::endl; std::cout << " Health: " << mData.mNpdt52.mHealth << std::endl; std::cout << " Magicka: " << mData.mNpdt52.mMana << std::endl; @@ -1123,9 +1126,9 @@ void Record::print() std::cout << (male ? " Male:" : " Female:") << std::endl; - for (int i=0; i<8; ++i) - std::cout << " " << sAttributeNames[i] << ": " - << mData.mData.mAttributeValues[i].getValue (male) << std::endl; + for (int j=0; j<8; ++j) + std::cout << " " << sAttributeNames[j] << ": " + << mData.mData.mAttributeValues[j].getValue (male) << std::endl; std::cout << " Height: " << mData.mData.mHeight.getValue (male) << std::endl; std::cout << " Weight: " << mData.mData.mWeight.getValue (male) << std::endl; diff --git a/apps/esmtool/record.hpp b/apps/esmtool/record.hpp index 45b6d04266..c1b90ac2bc 100644 --- a/apps/esmtool/record.hpp +++ b/apps/esmtool/record.hpp @@ -19,7 +19,7 @@ namespace EsmTool { protected: std::string mId; - int mFlags; + uint32_t mFlags; ESM::NAME mType; bool mPrintPlain; @@ -40,11 +40,11 @@ namespace EsmTool mId = id; } - int getFlags() const { + uint32_t getFlags() const { return mFlags; } - void setFlags(int flags) { + void setFlags(uint32_t flags) { mFlags = flags; } @@ -52,10 +52,6 @@ namespace EsmTool return mType; } - bool getPrintPlain() const { - return mPrintPlain; - } - void setPrintPlain(bool plain) { mPrintPlain = plain; } diff --git a/apps/essimporter/CMakeLists.txt b/apps/essimporter/CMakeLists.txt new file mode 100644 index 0000000000..72ef364ee6 --- /dev/null +++ b/apps/essimporter/CMakeLists.txt @@ -0,0 +1,43 @@ +set(ESSIMPORTER_FILES + main.cpp + importer.cpp + importplayer.cpp + importnpcc.cpp + importcrec.cpp + importcellref.cpp + importacdt.cpp + importinventory.cpp + importklst.cpp + importcntc.cpp + importgame.cpp + importinfo.cpp + importdial.cpp + importques.cpp + importjour.cpp + importscri.cpp + importscpt.cpp + importercontext.cpp + converter.cpp + convertacdt.cpp + convertnpcc.cpp + convertinventory.cpp + convertcrec.cpp + convertcntc.cpp + convertscri.cpp + convertscpt.cpp + convertplayer.cpp +) + +add_executable(openmw-essimporter + ${ESSIMPORTER_FILES} +) + +target_link_libraries(openmw-essimporter + ${Boost_LIBRARIES} + components +) + +if (BUILD_WITH_CODE_COVERAGE) + add_definitions (--coverage) + target_link_libraries(openmw-essimporter gcov) +endif() diff --git a/apps/essimporter/convertacdt.cpp b/apps/essimporter/convertacdt.cpp new file mode 100644 index 0000000000..718403a8c3 --- /dev/null +++ b/apps/essimporter/convertacdt.cpp @@ -0,0 +1,52 @@ +#include "convertacdt.hpp" + +namespace ESSImport +{ + + int translateDynamicIndex(int mwIndex) + { + if (mwIndex == 1) + return 2; + else if (mwIndex == 2) + return 1; + return mwIndex; + } + + void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats) + { + for (int i=0; i<3; ++i) + { + int writeIndex = translateDynamicIndex(i); + cStats.mDynamic[writeIndex].mBase = acdt.mDynamic[i][1]; + cStats.mDynamic[writeIndex].mMod = acdt.mDynamic[i][1]; + cStats.mDynamic[writeIndex].mCurrent = acdt.mDynamic[i][0]; + } + for (int i=0; i<8; ++i) + { + cStats.mAttributes[i].mBase = acdt.mAttributes[i][1]; + cStats.mAttributes[i].mMod = acdt.mAttributes[i][0]; + cStats.mAttributes[i].mCurrent = acdt.mAttributes[i][0]; + } + cStats.mGoldPool = acdt.mGoldPool; + cStats.mTalkedTo = acdt.mFlags & TalkedToPlayer; + cStats.mAttacked = acdt.mFlags & Attacked; + } + + void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats) + { + cStats.mDead = acsc.mFlags & Dead; + } + + void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats) + { + for (int i=0; i +#include +#include + +#include "importacdt.hpp" + +namespace ESSImport +{ + + // OpenMW uses Health,Magicka,Fatigue, MW uses Health,Fatigue,Magicka + int translateDynamicIndex(int mwIndex); + + + void convertACDT (const ACDT& acdt, ESM::CreatureStats& cStats); + void convertACSC (const ACSC& acsc, ESM::CreatureStats& cStats); + + void convertNpcData (const ActorData& actorData, ESM::NpcStats& npcStats); +} + +#endif diff --git a/apps/essimporter/convertcntc.cpp b/apps/essimporter/convertcntc.cpp new file mode 100644 index 0000000000..426ef44966 --- /dev/null +++ b/apps/essimporter/convertcntc.cpp @@ -0,0 +1,13 @@ +#include "convertcntc.hpp" + +#include "convertinventory.hpp" + +namespace ESSImport +{ + + void convertCNTC(const CNTC &cntc, ESM::ContainerState &state) + { + convertInventory(cntc.mInventory, state.mInventory); + } + +} diff --git a/apps/essimporter/convertcntc.hpp b/apps/essimporter/convertcntc.hpp new file mode 100644 index 0000000000..c299d87a1e --- /dev/null +++ b/apps/essimporter/convertcntc.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTCNTC_H +#define OPENMW_ESSIMPORT_CONVERTCNTC_H + +#include "importcntc.hpp" + +#include + +namespace ESSImport +{ + + void convertCNTC(const CNTC& cntc, ESM::ContainerState& state); + +} + +#endif diff --git a/apps/essimporter/convertcrec.cpp b/apps/essimporter/convertcrec.cpp new file mode 100644 index 0000000000..34e1c00708 --- /dev/null +++ b/apps/essimporter/convertcrec.cpp @@ -0,0 +1,13 @@ +#include "convertcrec.hpp" + +#include "convertinventory.hpp" + +namespace ESSImport +{ + + void convertCREC(const CREC &crec, ESM::CreatureState &state) + { + convertInventory(crec.mInventory, state.mInventory); + } + +} diff --git a/apps/essimporter/convertcrec.hpp b/apps/essimporter/convertcrec.hpp new file mode 100644 index 0000000000..7d317f03e8 --- /dev/null +++ b/apps/essimporter/convertcrec.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTCREC_H +#define OPENMW_ESSIMPORT_CONVERTCREC_H + +#include "importcrec.hpp" + +#include + +namespace ESSImport +{ + + void convertCREC(const CREC& crec, ESM::CreatureState& state); + +} + +#endif diff --git a/apps/essimporter/converter.cpp b/apps/essimporter/converter.cpp new file mode 100644 index 0000000000..91d290f331 --- /dev/null +++ b/apps/essimporter/converter.cpp @@ -0,0 +1,389 @@ +#include "converter.hpp" + +#include + +#include +#include + +#include +#include + +#include "convertcrec.hpp" +#include "convertcntc.hpp" +#include "convertscri.hpp" + +namespace +{ + + void convertImage(char* data, int size, int width, int height, Ogre::PixelFormat pf, const std::string& out) + { + Ogre::Image screenshot; + Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(data, size)); + screenshot.loadRawData(stream, width, height, 1, pf); + screenshot.save(out); + } + + + void convertCellRef(const ESSImport::CellRef& cellref, ESM::ObjectState& objstate) + { + objstate.mEnabled = cellref.mEnabled; + objstate.mPosition = cellref.mPos; + objstate.mRef.mRefNum = cellref.mRefNum; + if (cellref.mDeleted) + objstate.mCount = 0; + convertSCRI(cellref.mSCRI, objstate.mLocals); + objstate.mHasLocals = !objstate.mLocals.mVariables.empty(); + } + + bool isIndexedRefId(const std::string& indexedRefId) + { + if (indexedRefId.size() <= 8) + return false; + + if (indexedRefId.find_first_not_of("0123456789") == std::string::npos) + return false; // entirely numeric refid, this is a reference to + // a dynamically created record e.g. player-enchanted weapon + + std::string index = indexedRefId.substr(indexedRefId.size()-8); + if(index.find_first_not_of("0123456789ABCDEF") == std::string::npos ) + return true; + return false; + } +} + +namespace ESSImport +{ + + + struct MAPH + { + unsigned int size; + unsigned int value; + }; + + void ConvertFMAP::read(ESM::ESMReader &esm) + { + MAPH maph; + esm.getHNT(maph, "MAPH"); + std::vector data; + esm.getSubNameIs("MAPD"); + esm.getSubHeader(); + data.resize(esm.getSubSize()); + esm.getExact(&data[0], data.size()); + + Ogre::DataStreamPtr stream (new Ogre::MemoryDataStream(&data[0], data.size())); + mGlobalMapImage.loadRawData(stream, maph.size, maph.size, 1, Ogre::PF_BYTE_RGB); + // to match openmw size + mGlobalMapImage.resize(maph.size*2, maph.size*2, Ogre::Image::FILTER_BILINEAR); + } + + void ConvertFMAP::write(ESM::ESMWriter &esm) + { + int numcells = mGlobalMapImage.getWidth() / 18; // NB truncating, doesn't divide perfectly + // with the 512x512 map the game has by default + int cellSize = mGlobalMapImage.getWidth()/numcells; + + // Note the upper left corner of the (0,0) cell should be at (width/2, height/2) + + mContext->mGlobalMapState.mBounds.mMinX = -numcells/2; + mContext->mGlobalMapState.mBounds.mMaxX = (numcells-1)/2; + mContext->mGlobalMapState.mBounds.mMinY = -(numcells-1)/2; + mContext->mGlobalMapState.mBounds.mMaxY = numcells/2; + + Ogre::Image image2; + std::vector data; + int width = cellSize*numcells; + int height = cellSize*numcells; + data.resize(width*height*4, 0); + image2.loadDynamicImage(&data[0], width, height, Ogre::PF_BYTE_RGBA); + + for (std::set >::const_iterator it = mContext->mExploredCells.begin(); it != mContext->mExploredCells.end(); ++it) + { + if (it->first > mContext->mGlobalMapState.mBounds.mMaxX + || it->first < mContext->mGlobalMapState.mBounds.mMinX + || it->second > mContext->mGlobalMapState.mBounds.mMaxY + || it->second < mContext->mGlobalMapState.mBounds.mMinY) + { + // out of bounds, I think this could happen, since the original engine had a fixed-size map + continue; + } + + int imageLeftSrc = mGlobalMapImage.getWidth()/2; + int imageTopSrc = mGlobalMapImage.getHeight()/2; + imageLeftSrc += it->first * cellSize; + imageTopSrc -= it->second * cellSize; + int imageLeftDst = width/2; + int imageTopDst = height/2; + imageLeftDst += it->first * cellSize; + imageTopDst -= it->second * cellSize; + for (int x=0; xmGlobalMapState.mImageData.resize(encoded->size()); + encoded->read(&mContext->mGlobalMapState.mImageData[0], encoded->size()); + + esm.startRecord(ESM::REC_GMAP); + mContext->mGlobalMapState.save(esm); + esm.endRecord(ESM::REC_GMAP); + } + + void ConvertCell::read(ESM::ESMReader &esm) + { + ESM::Cell cell; + std::string id = esm.getHNString("NAME"); + cell.mName = id; + cell.load(esm, false); + + // I wonder what 0x40 does? + if (cell.isExterior() && cell.mData.mFlags & 0x20) + { + mContext->mGlobalMapState.mMarkers.insert(std::make_pair(cell.mData.mX, cell.mData.mY)); + } + + // note if the player is in a nameless exterior cell, we will assign the cellId later based on player position + if (id == mContext->mPlayerCellName) + { + mContext->mPlayer.mCellId = cell.getCellId(); + } + + Cell newcell; + newcell.mCell = cell; + + // fog of war + // seems to be a 1-bit pixel format, 16*16 pixels + // TODO: add bleeding of FOW into neighbouring cells (openmw handles this by writing to the textures, + // MW handles it when rendering only) + unsigned char nam8[32]; + // exterior has 1 NAM8, interior can have multiple ones, and have an extra 4 byte flag at the start + // (probably offset of that specific fog texture?) + while (esm.isNextSub("NAM8")) + { + if (cell.isExterior()) // TODO: NAM8 occasionally exists for cells that haven't been explored. + // are there any flags marking explored cells? + mContext->mExploredCells.insert(std::make_pair(cell.mData.mX, cell.mData.mY)); + + esm.getSubHeader(); + + if (esm.getSubSize() == 36) + { + // flag on interiors + esm.skip(4); + } + + esm.getExact(nam8, 32); + + newcell.mFogOfWar.reserve(16*16); + for (int x=0; x<16; ++x) + { + for (int y=0; y<16; ++y) + { + size_t pos = x*16+y; + size_t bytepos = pos/8; + assert(bytepos<32); + int bit = pos%8; + newcell.mFogOfWar.push_back(((nam8[bytepos] >> bit) & (0x1)) ? 0xffffffff : 0x000000ff); + } + } + + if (cell.isExterior()) + { + std::ostringstream filename; + filename << "fog_" << cell.mData.mX << "_" << cell.mData.mY << ".tga"; + + convertImage((char*)&newcell.mFogOfWar[0], newcell.mFogOfWar.size()*4, 16, 16, Ogre::PF_BYTE_RGBA, filename.str()); + } + } + + // moved reference, not handled yet + // NOTE: MVRF can also occur in within normal references (importcellref.cpp)? + // this does not match the ESM file implementation, + // verify if that can happen with ESM files too + while (esm.isNextSub("MVRF")) + { + esm.skipHSub(); // skip MVRF + esm.getSubName(); + esm.skipHSub(); // skip CNDT + } + + std::vector cellrefs; + while (esm.hasMoreSubs() && esm.isNextSub("FRMR")) + { + CellRef ref; + ref.load (esm); + cellrefs.push_back(ref); + } + + while (esm.isNextSub("MPCD")) + { + float notepos[3]; + esm.getHT(notepos, 3*sizeof(float)); + + // Markers seem to be arranged in a 32*32 grid, notepos has grid-indices. + // This seems to be the reason markers can't be placed everywhere in interior cells, + // i.e. when the grid is exceeded. + // Converting the interior markers correctly could be rather tricky, but is probably similar logic + // as used for the FoW texture placement, which we need to figure out anyway + notepos[1] += 31.f; + notepos[0] += 0.5; + notepos[1] += 0.5; + notepos[0] = 8192 * notepos[0] / 32.f; + notepos[1] = 8192 * notepos[1] / 32.f; + if (cell.isExterior()) + { + notepos[0] += 8192 * cell.mData.mX; + notepos[1] += 8192 * cell.mData.mY; + } + // TODO: what encoding is this in? + std::string note = esm.getHNString("MPNT"); + ESM::CustomMarker marker; + marker.mWorldX = notepos[0]; + marker.mWorldY = notepos[1]; + marker.mNote = note; + marker.mCell = cell.getCellId(); + mMarkers.push_back(marker); + } + + newcell.mRefs = cellrefs; + + + if (cell.isExterior()) + mExtCells[std::make_pair(cell.mData.mX, cell.mData.mY)] = newcell; + else + mIntCells[id] = newcell; + } + + void ConvertCell::writeCell(const Cell &cell, ESM::ESMWriter& esm) + { + ESM::Cell esmcell = cell.mCell; + esm.startRecord(ESM::REC_CSTA); + ESM::CellState csta; + csta.mHasFogOfWar = 0; + csta.mId = esmcell.getCellId(); + csta.mId.save(esm); + // TODO csta.mLastRespawn; + // shouldn't be needed if we respawn on global schedule like in original MW + csta.mWaterLevel = esmcell.mWater; + csta.save(esm); + + for (std::vector::const_iterator refIt = cell.mRefs.begin(); refIt != cell.mRefs.end(); ++refIt) + { + const CellRef& cellref = *refIt; + ESM::CellRef out (cellref); + + // TODO: use mContext->mCreatures/mNpcs + + if (!isIndexedRefId(cellref.mIndexedRefId)) + { + // non-indexed RefNum, i.e. no CREC/NPCC/CNTC record associated with it + // this could be any type of object really (even creatures/npcs too) + out.mRefID = cellref.mIndexedRefId; + std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); + + ESM::ObjectState objstate; + objstate.blank(); + objstate.mRef = out; + objstate.mRef.mRefID = idLower; + objstate.mHasCustomState = false; + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", 0); + objstate.save(esm); + continue; + } + else + { + std::stringstream stream; + stream << std::hex << cellref.mIndexedRefId.substr(cellref.mIndexedRefId.size()-8,8); + int refIndex; + stream >> refIndex; + + out.mRefID = cellref.mIndexedRefId.substr(0,cellref.mIndexedRefId.size()-8); + std::string idLower = Misc::StringUtils::lowerCase(out.mRefID); + + std::map, NPCC>::const_iterator npccIt = mContext->mNpcChanges.find( + std::make_pair(refIndex, out.mRefID)); + if (npccIt != mContext->mNpcChanges.end()) + { + ESM::NpcState objstate; + objstate.blank(); + objstate.mRef = out; + objstate.mRef.mRefID = idLower; + // TODO: need more micromanagement here so we don't overwrite values + // from the ESM with default values + if (cellref.mHasACDT) + convertACDT(cellref.mACDT, objstate.mCreatureStats); + if (cellref.mHasACSC) + convertACSC(cellref.mACSC, objstate.mCreatureStats); + convertNpcData(cellref, objstate.mNpcStats); + convertNPCC(npccIt->second, objstate); + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", ESM::REC_NPC_); + objstate.save(esm); + continue; + } + + std::map, CNTC>::const_iterator cntcIt = mContext->mContainerChanges.find( + std::make_pair(refIndex, out.mRefID)); + if (cntcIt != mContext->mContainerChanges.end()) + { + ESM::ContainerState objstate; + objstate.blank(); + objstate.mRef = out; + objstate.mRef.mRefID = idLower; + convertCNTC(cntcIt->second, objstate); + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", ESM::REC_CONT); + objstate.save(esm); + continue; + } + + std::map, CREC>::const_iterator crecIt = mContext->mCreatureChanges.find( + std::make_pair(refIndex, out.mRefID)); + if (crecIt != mContext->mCreatureChanges.end()) + { + ESM::CreatureState objstate; + objstate.blank(); + objstate.mRef = out; + objstate.mRef.mRefID = idLower; + // TODO: need more micromanagement here so we don't overwrite values + // from the ESM with default values + if (cellref.mHasACDT) + convertACDT(cellref.mACDT, objstate.mCreatureStats); + if (cellref.mHasACSC) + convertACSC(cellref.mACSC, objstate.mCreatureStats); + convertCREC(crecIt->second, objstate); + convertCellRef(cellref, objstate); + esm.writeHNT ("OBJE", ESM::REC_CREA); + objstate.save(esm); + continue; + } + + std::stringstream error; + error << "Can't find type for " << cellref.mIndexedRefId << std::endl; + throw std::runtime_error(error.str()); + } + } + + esm.endRecord(ESM::REC_CSTA); + } + + void ConvertCell::write(ESM::ESMWriter &esm) + { + for (std::map::const_iterator it = mIntCells.begin(); it != mIntCells.end(); ++it) + writeCell(it->second, esm); + + for (std::map, Cell>::const_iterator it = mExtCells.begin(); it != mExtCells.end(); ++it) + writeCell(it->second, esm); + + for (std::vector::const_iterator it = mMarkers.begin(); it != mMarkers.end(); ++it) + { + esm.startRecord(ESM::REC_MARK); + it->save(esm); + esm.endRecord(ESM::REC_MARK); + } + } + +} diff --git a/apps/essimporter/converter.hpp b/apps/essimporter/converter.hpp new file mode 100644 index 0000000000..c23083f8e9 --- /dev/null +++ b/apps/essimporter/converter.hpp @@ -0,0 +1,597 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTER_H +#define OPENMW_ESSIMPORT_CONVERTER_H + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "importcrec.hpp" +#include "importcntc.hpp" + +#include "importercontext.hpp" +#include "importcellref.hpp" +#include "importklst.hpp" +#include "importgame.hpp" +#include "importinfo.hpp" +#include "importdial.hpp" +#include "importques.hpp" +#include "importjour.hpp" +#include "importscpt.hpp" + +#include "convertacdt.hpp" +#include "convertnpcc.hpp" +#include "convertscpt.hpp" +#include "convertplayer.hpp" + +namespace ESSImport +{ + +class Converter +{ +public: + /// @return the order for writing this converter's records to the output file, in relation to other converters + virtual int getStage() { return 1; } + + virtual ~Converter() {} + + void setContext(Context& context) { mContext = &context; } + + virtual void read(ESM::ESMReader& esm) + { + } + + /// Called after the input file has been read in completely, which may be necessary + /// if the conversion process relies on information in other records + virtual void write(ESM::ESMWriter& esm) + { + + } + +protected: + Context* mContext; +}; + +/// Default converter: simply reads the record and writes it unmodified to the output +template +class DefaultConverter : public Converter +{ +public: + virtual int getStage() { return 0; } + + virtual void read(ESM::ESMReader& esm) + { + std::string id = esm.getHNString("NAME"); + T record; + record.load(esm); + mRecords[id] = record; + } + + virtual void write(ESM::ESMWriter& esm) + { + for (typename std::map::const_iterator it = mRecords.begin(); it != mRecords.end(); ++it) + { + esm.startRecord(T::sRecordId); + esm.writeHNString("NAME", it->first); + it->second.save(esm); + esm.endRecord(T::sRecordId); + } + } + +protected: + std::map mRecords; +}; + +class ConvertNPC : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + ESM::NPC npc; + std::string id = esm.getHNString("NAME"); + npc.load(esm); + if (id != "player") + { + // Handles changes to the NPC struct, but since there is no index here + // it will apply to ALL instances of the class. seems to be the reason for the + // "feature" in MW where changing AI settings of one guard will change it for all guards of that refID. + mContext->mNpcs[Misc::StringUtils::lowerCase(id)] = npc; + } + else + { + mContext->mPlayer.mObject.mCreatureStats.mLevel = npc.mNpdt52.mLevel; + mContext->mPlayerBase = npc; + std::map empty; + // FIXME: player start spells and birthsign spells aren't listed here, + // need to fix openmw to account for this + for (std::vector::const_iterator it = npc.mSpells.mList.begin(); it != npc.mSpells.mList.end(); ++it) + mContext->mPlayer.mObject.mCreatureStats.mSpells.mSpells[*it] = empty; + + // Clear the list now that we've written it, this prevents issues cropping up with + // ensureCustomData() in OpenMW tripping over no longer existing spells, where an error would be fatal. + mContext->mPlayerBase.mSpells.mList.clear(); + + // Same with inventory. Actually it's strange this would contain something, since there's already an + // inventory list in NPCC. There seems to be a fair amount of redundancy in this format. + mContext->mPlayerBase.mInventory.mList.clear(); + } + } +}; + +class ConvertCREA : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + // See comment in ConvertNPC + ESM::Creature creature; + std::string id = esm.getHNString("NAME"); + creature.load(esm); + mContext->mCreatures[Misc::StringUtils::lowerCase(id)] = creature; + } +}; + +// Do we need ConvertCONT? +// I've seen a CONT record in a certain save file, but the container contents in it +// were identical to a corresponding CNTC record. See previous comment about redundancy... + +class ConvertGlobal : public DefaultConverter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + std::string id = esm.getHNString("NAME"); + ESM::Global global; + global.load(esm); + if (Misc::StringUtils::ciEqual(id, "gamehour")) + mContext->mHour = global.mValue.getFloat(); + if (Misc::StringUtils::ciEqual(id, "day")) + mContext->mDay = global.mValue.getInteger(); + if (Misc::StringUtils::ciEqual(id, "month")) + mContext->mMonth = global.mValue.getInteger(); + if (Misc::StringUtils::ciEqual(id, "year")) + mContext->mYear = global.mValue.getInteger(); + mRecords[id] = global; + } +}; + +class ConvertClass : public DefaultConverter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + std::string id = esm.getHNString("NAME"); + ESM::Class class_; + class_.load(esm); + + if (id == "NEWCLASSID_CHARGEN") + mContext->mCustomPlayerClassName = class_.mName; + + mRecords[id] = class_; + } +}; + +class ConvertBook : public DefaultConverter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + std::string id = esm.getHNString("NAME"); + ESM::Book book; + book.load(esm); + if (book.mData.mSkillID == -1) + mContext->mPlayer.mObject.mNpcStats.mUsedIds.push_back(Misc::StringUtils::lowerCase(id)); + + mRecords[id] = book; + } +}; + +class ConvertNPCC : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + std::string id = esm.getHNString("NAME"); + NPCC npcc; + npcc.load(esm); + if (id == "PlayerSaveGame") + { + convertNPCC(npcc, mContext->mPlayer.mObject); + } + else + { + int index = npcc.mNPDT.mIndex; + mContext->mNpcChanges.insert(std::make_pair(std::make_pair(index,id), npcc)); + } + } +}; + +class ConvertREFR : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + REFR refr; + refr.load(esm); + assert(refr.mRefID == "PlayerSaveGame"); + mContext->mPlayer.mObject.mPosition = refr.mPos; + + ESM::CreatureStats& cStats = mContext->mPlayer.mObject.mCreatureStats; + convertACDT(refr.mActorData.mACDT, cStats); + + ESM::NpcStats& npcStats = mContext->mPlayer.mObject.mNpcStats; + convertNpcData(refr.mActorData, npcStats); + + mSelectedSpell = refr.mActorData.mSelectedSpell; + if (!refr.mActorData.mSelectedEnchantItem.empty()) + { + ESM::InventoryState& invState = mContext->mPlayer.mObject.mInventory; + + for (unsigned int i=0; imPlayer, mContext->mDialogueState.mKnownTopics, mFirstPersonCam); + } + virtual void write(ESM::ESMWriter &esm) + { + esm.startRecord(ESM::REC_CAM_); + esm.writeHNT("FIRS", mFirstPersonCam); + esm.endRecord(ESM::REC_CAM_); + } +private: + bool mFirstPersonCam; +}; + +class ConvertCNTC : public Converter +{ + virtual void read(ESM::ESMReader &esm) + { + std::string id = esm.getHNString("NAME"); + CNTC cntc; + cntc.load(esm); + mContext->mContainerChanges.insert(std::make_pair(std::make_pair(cntc.mIndex,id), cntc)); + } +}; + +class ConvertCREC : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + std::string id = esm.getHNString("NAME"); + CREC crec; + crec.load(esm); + mContext->mCreatureChanges.insert(std::make_pair(std::make_pair(crec.mIndex,id), crec)); + } +}; + +class ConvertFMAP : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm); + virtual void write(ESM::ESMWriter &esm); + +private: + Ogre::Image mGlobalMapImage; +}; + +class ConvertCell : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm); + virtual void write(ESM::ESMWriter& esm); + +private: + struct Cell + { + ESM::Cell mCell; + std::vector mRefs; + std::vector mFogOfWar; + }; + + std::map mIntCells; + std::map, Cell> mExtCells; + + std::vector mMarkers; + + void writeCell(const Cell& cell, ESM::ESMWriter &esm); +}; + +class ConvertKLST : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + KLST klst; + klst.load(esm); + mKillCounter = klst.mKillCounter; + + mContext->mPlayer.mObject.mNpcStats.mWerewolfKills = klst.mWerewolfKills; + } + + virtual void write(ESM::ESMWriter &esm) + { + esm.startRecord(ESM::REC_DCOU); + for (std::map::const_iterator it = mKillCounter.begin(); it != mKillCounter.end(); ++it) + { + esm.writeHNString("ID__", it->first); + esm.writeHNT ("COUN", it->second); + } + esm.endRecord(ESM::REC_DCOU); + } + +private: + std::map mKillCounter; +}; + +class ConvertFACT : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + std::string id = esm.getHNString("NAME"); + ESM::Faction faction; + faction.load(esm); + + Misc::StringUtils::toLower(id); + for (std::map::const_iterator it = faction.mReactions.begin(); it != faction.mReactions.end(); ++it) + { + std::string faction2 = Misc::StringUtils::lowerCase(it->first); + mContext->mDialogueState.mChangedFactionReaction[id].insert(std::make_pair(faction2, it->second)); + } + } +}; + +/// Stolen items +class ConvertSTLN : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + std::string itemid = esm.getHNString("NAME"); + Misc::StringUtils::toLower(itemid); + + while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) + { + if (esm.retSubName().toString() == "FNAM") + { + std::string factionid = esm.getHString(); + mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(factionid), true)); + } + else + { + std::string ownerid = esm.getHString(); + mStolenItems[itemid].insert(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); + } + } + } + virtual void write(ESM::ESMWriter &esm) + { + ESM::StolenItems items; + for (std::map >::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) + { + std::map, int> owners; + for (std::set::const_iterator ownerIt = it->second.begin(); ownerIt != it->second.end(); ++ownerIt) + { + owners.insert(std::make_pair(std::make_pair(ownerIt->first, ownerIt->second) + // Since OpenMW doesn't suffer from the owner contamination bug, + // it needs a count argument. But for legacy savegames, we don't know + // this count, so must assume all items of that ID are stolen, + // like vanilla MW did. + ,std::numeric_limits::max())); + } + + items.mStolenItems.insert(std::make_pair(it->first, owners)); + } + + esm.startRecord(ESM::REC_STLN); + items.write(esm); + esm.endRecord(ESM::REC_STLN); + } + +private: + typedef std::pair Owner; // + + std::map > mStolenItems; +}; + +/// Seen responses for a dialogue topic? +/// Each DIAL record is followed by a number of INFO records, I believe, just like in ESMs +/// Dialogue conversion problems: +/// - Journal is stored in one continuous HTML markup rather than each entry separately with associated info ID. +/// - Seen dialogue responses only store the INFO id, rather than the fulltext. +/// - Quest stages only store the INFO id, rather than the journal entry fulltext. +class ConvertINFO : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + INFO info; + info.load(esm); + } +}; + +class ConvertDIAL : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + std::string id = esm.getHNString("NAME"); + DIAL dial; + dial.load(esm); + if (dial.mIndex > 0) + mDials[id] = dial; + } + virtual void write(ESM::ESMWriter &esm) + { + for (std::map::const_iterator it = mDials.begin(); it != mDials.end(); ++it) + { + esm.startRecord(ESM::REC_QUES); + ESM::QuestState state; + state.mFinished = 0; + state.mState = it->second.mIndex; + state.mTopic = Misc::StringUtils::lowerCase(it->first); + state.save(esm); + esm.endRecord(ESM::REC_QUES); + } + } +private: + std::map mDials; +}; + +class ConvertQUES : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + std::string id = esm.getHNString("NAME"); + QUES quest; + quest.load(esm); + } +}; + +class ConvertJOUR : public Converter +{ +public: + virtual void read(ESM::ESMReader& esm) + { + JOUR journal; + journal.load(esm); + } +}; + +class ConvertGAME : public Converter +{ +public: + ConvertGAME() : mHasGame(false) {} + + std::string toString(int weatherId) + { + switch (weatherId) + { + case 0: + return "clear"; + case 1: + return "cloudy"; + case 2: + return "foggy"; + case 3: + return "overcast"; + case 4: + return "rain"; + case 5: + return "thunderstorm"; + case 6: + return "ashstorm"; + case 7: + return "blight"; + case 8: + return "snow"; + case 9: + return "blizzard"; + case -1: + return ""; + default: + { + std::stringstream error; + error << "unknown weather id: " << weatherId; + throw std::runtime_error(error.str()); + } + } + } + + virtual void read(ESM::ESMReader &esm) + { + mGame.load(esm); + mHasGame = true; + } + + virtual void write(ESM::ESMWriter &esm) + { + if (!mHasGame) + return; + esm.startRecord(ESM::REC_WTHR); + ESM::WeatherState weather; + weather.mCurrentWeather = toString(mGame.mGMDT.mCurrentWeather); + weather.mNextWeather = toString(mGame.mGMDT.mNextWeather); + weather.mRemainingTransitionTime = mGame.mGMDT.mWeatherTransition/100.f*(0.015*24*3600); + weather.mHour = mContext->mHour; + weather.mWindSpeed = 0.f; + weather.mTimePassed = 0.0; + weather.mFirstUpdate = false; + weather.save(esm); + esm.endRecord(ESM::REC_WTHR); + } + +private: + bool mHasGame; + GAME mGame; +}; + +/// Running global script +class ConvertSCPT : public Converter +{ +public: + virtual void read(ESM::ESMReader &esm) + { + SCPT script; + script.load(esm); + ESM::GlobalScript out; + convertSCPT(script, out); + mScripts.push_back(out); + } + virtual void write(ESM::ESMWriter &esm) + { + for (std::vector::const_iterator it = mScripts.begin(); it != mScripts.end(); ++it) + { + esm.startRecord(ESM::REC_GSCR); + it->save(esm); + esm.endRecord(ESM::REC_GSCR); + } + } +private: + std::vector mScripts; +}; + +} + +#endif diff --git a/apps/essimporter/convertinventory.cpp b/apps/essimporter/convertinventory.cpp new file mode 100644 index 0000000000..f476fe1ee2 --- /dev/null +++ b/apps/essimporter/convertinventory.cpp @@ -0,0 +1,30 @@ +#include "convertinventory.hpp" + +#include + +namespace ESSImport +{ + + void convertInventory(const Inventory &inventory, ESM::InventoryState &state) + { + int index = 0; + for (std::vector::const_iterator it = inventory.mItems.begin(); + it != inventory.mItems.end(); ++it) + { + ESM::ObjectState objstate; + objstate.blank(); + objstate.mRef = *it; + objstate.mRef.mRefID = Misc::StringUtils::lowerCase(it->mId); + objstate.mCount = std::abs(it->mCount); // restocking items have negative count in the savefile + // openmw handles them differently, so no need to set any flags + state.mItems.push_back(objstate); + if (it->mRelativeEquipmentSlot != -1) + // Note we should really write the absolute slot here, which we do not know about + // Not a big deal, OpenMW will auto-correct to a valid slot, the only problem is when + // an item could be equipped in two different slots (e.g. equipped two rings) + state.mEquipmentSlots[index] = it->mRelativeEquipmentSlot; + ++index; + } + } + +} diff --git a/apps/essimporter/convertinventory.hpp b/apps/essimporter/convertinventory.hpp new file mode 100644 index 0000000000..8abe85a44a --- /dev/null +++ b/apps/essimporter/convertinventory.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTINVENTORY_H +#define OPENMW_ESSIMPORT_CONVERTINVENTORY_H + +#include "importinventory.hpp" + +#include + +namespace ESSImport +{ + + void convertInventory (const Inventory& inventory, ESM::InventoryState& state); + +} + +#endif diff --git a/apps/essimporter/convertnpcc.cpp b/apps/essimporter/convertnpcc.cpp new file mode 100644 index 0000000000..48d3d9232e --- /dev/null +++ b/apps/essimporter/convertnpcc.cpp @@ -0,0 +1,15 @@ +#include "convertnpcc.hpp" + +#include "convertinventory.hpp" + +namespace ESSImport +{ + + void convertNPCC(const NPCC &npcc, ESM::NpcState &npcState) + { + npcState.mNpcStats.mDisposition = npcc.mNPDT.mDisposition; + npcState.mNpcStats.mReputation = npcc.mNPDT.mReputation; + + convertInventory(npcc.mInventory, npcState.mInventory); + } +} diff --git a/apps/essimporter/convertnpcc.hpp b/apps/essimporter/convertnpcc.hpp new file mode 100644 index 0000000000..eb12d8f3bc --- /dev/null +++ b/apps/essimporter/convertnpcc.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTNPCC_H +#define OPENMW_ESSIMPORT_CONVERTNPCC_H + +#include "importnpcc.hpp" + +#include + +namespace ESSImport +{ + + void convertNPCC (const NPCC& npcc, ESM::NpcState& npcState); + +} + +#endif diff --git a/apps/essimporter/convertplayer.cpp b/apps/essimporter/convertplayer.cpp new file mode 100644 index 0000000000..532efa7f5b --- /dev/null +++ b/apps/essimporter/convertplayer.cpp @@ -0,0 +1,38 @@ +#include "convertplayer.hpp" + +namespace ESSImport +{ + + void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam) + { + out.mBirthsign = pcdt.mBirthsign; + out.mObject.mNpcStats.mBounty = pcdt.mBounty; + for (std::vector::const_iterator it = pcdt.mFactions.begin(); it != pcdt.mFactions.end(); ++it) + { + ESM::NpcStats::Faction faction; + faction.mExpelled = (it->mFlags & 0x2) != 0; + faction.mRank = it->mRank; + faction.mReputation = it->mReputation; + out.mObject.mNpcStats.mFactions[Misc::StringUtils::lowerCase(it->mFactionName.toString())] = faction; + } + for (int i=0; i<8; ++i) + out.mObject.mNpcStats.mSkillIncrease[i] = pcdt.mPNAM.mSkillIncreases[i]; + for (int i=0; i<27; ++i) + out.mObject.mNpcStats.mSkills[i].mRegular.mProgress = pcdt.mPNAM.mSkillProgress[i]; + out.mObject.mNpcStats.mLevelProgress = pcdt.mPNAM.mLevelProgress; + + if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Weapon) + out.mObject.mCreatureStats.mDrawState = 1; + if (pcdt.mPNAM.mDrawState & PCDT::DrawState_Spell) + out.mObject.mCreatureStats.mDrawState = 2; + + firstPersonCam = (pcdt.mPNAM.mCameraState == PCDT::CameraState_FirstPerson); + + for (std::vector::const_iterator it = pcdt.mKnownDialogueTopics.begin(); + it != pcdt.mKnownDialogueTopics.end(); ++it) + { + outDialogueTopics.push_back(Misc::StringUtils::lowerCase(*it)); + } + } + +} diff --git a/apps/essimporter/convertplayer.hpp b/apps/essimporter/convertplayer.hpp new file mode 100644 index 0000000000..f6731eed76 --- /dev/null +++ b/apps/essimporter/convertplayer.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTPLAYER_H +#define OPENMW_ESSIMPORT_CONVERTPLAYER_H + +#include "importplayer.hpp" + +#include + +namespace ESSImport +{ + + void convertPCDT(const PCDT& pcdt, ESM::Player& out, std::vector& outDialogueTopics, bool& firstPersonCam); + +} + +#endif diff --git a/apps/essimporter/convertscpt.cpp b/apps/essimporter/convertscpt.cpp new file mode 100644 index 0000000000..ca81ebbbf2 --- /dev/null +++ b/apps/essimporter/convertscpt.cpp @@ -0,0 +1,17 @@ +#include "convertscpt.hpp" + +#include + +#include "convertscri.hpp" + +namespace ESSImport +{ + + void convertSCPT(const SCPT &scpt, ESM::GlobalScript &out) + { + out.mId = Misc::StringUtils::lowerCase(scpt.mSCHD.mName.toString()); + out.mRunning = scpt.mRunning; + convertSCRI(scpt.mSCRI, out.mLocals); + } + +} diff --git a/apps/essimporter/convertscpt.hpp b/apps/essimporter/convertscpt.hpp new file mode 100644 index 0000000000..3390bd6070 --- /dev/null +++ b/apps/essimporter/convertscpt.hpp @@ -0,0 +1,15 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTSCPT_H +#define OPENMW_ESSIMPORT_CONVERTSCPT_H + +#include + +#include "importscpt.hpp" + +namespace ESSImport +{ + +void convertSCPT(const SCPT& scpt, ESM::GlobalScript& out); + +} + +#endif diff --git a/apps/essimporter/convertscri.cpp b/apps/essimporter/convertscri.cpp new file mode 100644 index 0000000000..dbe35a0108 --- /dev/null +++ b/apps/essimporter/convertscri.cpp @@ -0,0 +1,32 @@ +#include "convertscri.hpp" + +#include + +namespace +{ + + template + void storeVariables(const std::vector& variables, ESM::Locals& locals, const std::string& scriptname) + { + for (typename std::vector::const_iterator it = variables.begin(); it != variables.end(); ++it) + { + ESM::Variant val(*it); + val.setType(VariantType); + locals.mVariables.push_back(std::make_pair(std::string(), val)); + } + } + +} + +namespace ESSImport +{ + + void convertSCRI(const SCRI &scri, ESM::Locals &locals) + { + // order *is* important, as we do not have variable names available in this format + storeVariables (scri.mShorts, locals, scri.mScript); + storeVariables (scri.mLongs, locals, scri.mScript); + storeVariables (scri.mFloats, locals, scri.mScript); + } + +} diff --git a/apps/essimporter/convertscri.hpp b/apps/essimporter/convertscri.hpp new file mode 100644 index 0000000000..2d89456662 --- /dev/null +++ b/apps/essimporter/convertscri.hpp @@ -0,0 +1,16 @@ +#ifndef OPENMW_ESSIMPORT_CONVERTSCRI_H +#define OPENMW_ESSIMPORT_CONVERTSCRI_H + +#include "importscri.hpp" + +#include + +namespace ESSImport +{ + + /// Convert script variable assignments + void convertSCRI (const SCRI& scri, ESM::Locals& locals); + +} + +#endif diff --git a/apps/essimporter/importacdt.cpp b/apps/essimporter/importacdt.cpp new file mode 100644 index 0000000000..9d881515dd --- /dev/null +++ b/apps/essimporter/importacdt.cpp @@ -0,0 +1,115 @@ +#include "importacdt.hpp" + +#include + +#include + +namespace ESSImport +{ + + void ActorData::load(ESM::ESMReader &esm) + { + if (esm.isNextSub("ACTN")) + esm.skipHSub(); + + if (esm.isNextSub("STPR")) + esm.skipHSub(); + + if (esm.isNextSub("MNAM")) + esm.skipHSub(); + + ESM::CellRef::loadData(esm); + + mHasACDT = false; + if (esm.isNextSub("ACDT")) + { + mHasACDT = true; + esm.getHT(mACDT); + } + + mHasACSC = false; + if (esm.isNextSub("ACSC")) + { + mHasACSC = true; + esm.getHT(mACSC); + } + + if (esm.isNextSub("ACSL")) + esm.skipHSubSize(112); + + if (esm.isNextSub("CSTN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + if (esm.isNextSub("LSTN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + // unsure at which point between LSTN and TGTN + if (esm.isNextSub("CSHN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + // unsure if before or after CSTN/LSTN + if (esm.isNextSub("LSHN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + while (esm.isNextSub("TGTN")) + esm.skipHSub(); // "PlayerSaveGame", link to some object? + + while (esm.isNextSub("FGTN")) + esm.getHString(); // fight target? + + // unsure at which point between TGTN and CRED + if (esm.isNextSub("AADT")) + { + // occured when a creature was in the middle of its attack, 44 bytes + esm.skipHSub(); + } + + // unsure at which point between FGTN and CHRD + if (esm.isNextSub("PWPC")) + esm.skipHSub(); + if (esm.isNextSub("PWPS")) + esm.skipHSub(); + + if (esm.isNextSub("WNAM")) + { + std::string id = esm.getHString(); + + if (esm.isNextSub("XNAM")) + mSelectedEnchantItem = esm.getHString(); + else + mSelectedSpell = id; + + if (esm.isNextSub("YNAM")) + esm.skipHSub(); // 4 byte, 0 + } + + while (esm.isNextSub("APUD")) + { + // used power + esm.getSubHeader(); + std::string id = esm.getString(32); + (void)id; + // timestamp can't be used: this is the total hours passed, calculated by + // timestamp = 24 * (365 * year + cumulativeDays[month] + day) + // unfortunately cumulativeDays[month] is not clearly defined, + // in the (non-MCP) vanilla version the first month was missing, but MCP added it. + double timestamp; + esm.getT(timestamp); + } + + // FIXME: not all actors have this, add flag + if (esm.isNextSub("CHRD")) // npc only + esm.getHExact(mSkills, 27*2*sizeof(int)); + + if (esm.isNextSub("CRED")) // creature only + esm.getHExact(mCombatStats, 3*2*sizeof(int)); + + mSCRI.load(esm); + + if (esm.isNextSub("ND3D")) + esm.skipHSub(); + if (esm.isNextSub("ANIS")) + esm.skipHSub(); + } + +} diff --git a/apps/essimporter/importacdt.hpp b/apps/essimporter/importacdt.hpp new file mode 100644 index 0000000000..47055092de --- /dev/null +++ b/apps/essimporter/importacdt.hpp @@ -0,0 +1,81 @@ +#ifndef OPENMW_ESSIMPORT_ACDT_H +#define OPENMW_ESSIMPORT_ACDT_H + +#include + +#include + +#include "importscri.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + enum ACDTFlags + { + TalkedToPlayer = 0x4, + Attacked = 0x100, + Unknown = 0x200 + }; + enum ACSCFlags + { + Dead = 0x2 + }; + + /// Actor data, shared by (at least) REFR and CellRef +#pragma pack(push) +#pragma pack(1) + struct ACDT + { + // Note, not stored at *all*: + // - Level changes are lost on reload, except for the player (there it's in the NPC record). + unsigned char mUnknown[12]; + unsigned int mFlags; + float mBreathMeter; // Seconds left before drowning + unsigned char mUnknown2[20]; + float mDynamic[3][2]; + unsigned char mUnknown3[16]; + float mAttributes[8][2]; + float mMagicEffects[27]; // Effect attributes: https://wiki.openmw.org/index.php?title=Research:Magic#Effect_attributes + unsigned char mUnknown4[4]; + unsigned int mGoldPool; + unsigned char mUnknown5[4]; + }; + struct ACSC + { + unsigned char mUnknown1[17]; + unsigned char mFlags; // ACSCFlags + unsigned char mUnknown2[94]; + }; +#pragma pack(pop) + + struct ActorData : public ESM::CellRef + { + bool mHasACDT; + ACDT mACDT; + + bool mHasACSC; + ACSC mACSC; + + int mSkills[27][2]; // skills, base and modified + + // creature combat stats, base and modified + // I think these can be ignored in the conversion, because it is not possible + // to change them ingame + int mCombatStats[3][2]; + + std::string mSelectedSpell; + std::string mSelectedEnchantItem; + + SCRI mSCRI; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importcellref.cpp b/apps/essimporter/importcellref.cpp new file mode 100644 index 0000000000..cca356b2a8 --- /dev/null +++ b/apps/essimporter/importcellref.cpp @@ -0,0 +1,57 @@ +#include "importcellref.hpp" + +#include + +namespace ESSImport +{ + + void CellRef::load(ESM::ESMReader &esm) + { + blank(); + + // (FRMR subrecord name is already read by the loop in ConvertCell) + esm.getHT(mRefNum.mIndex); // FRMR + + // this is required since openmw supports more than 255 content files + int pluginIndex = (mRefNum.mIndex & 0xff000000) >> 24; + mRefNum.mContentFile = pluginIndex-1; + mRefNum.mIndex &= 0x00ffffff; + + mIndexedRefId = esm.getHNString("NAME"); + + ActorData::load(esm); + if (esm.isNextSub("LVCR")) + { + // occurs on levelled creature spawner references + // probably some identifier for the creature that has been spawned? + unsigned char lvcr; + esm.getHT(lvcr); + //std::cout << "LVCR: " << (int)lvcr << std::endl; + } + + mEnabled = true; + esm.getHNOT(mEnabled, "ZNAM"); + + // DATA should occur for all references, except levelled creature spawners + // I've seen DATA *twice* on a creature record, and with the exact same content too! weird + // alarmvoi0000.ess + esm.getHNOT(mPos, "DATA", 24); + esm.getHNOT(mPos, "DATA", 24); + + mDeleted = 0; + if (esm.isNextSub("DELE")) + { + unsigned int deleted; + esm.getHT(deleted); + mDeleted = (deleted >> 24) & 0x2; // the other 3 bytes seem to be uninitialized garbage + } + + if (esm.isNextSub("MVRF")) + { + esm.skipHSub(); + esm.getSubName(); + esm.skipHSub(); + } + } + +} diff --git a/apps/essimporter/importcellref.hpp b/apps/essimporter/importcellref.hpp new file mode 100644 index 0000000000..556ed19bfa --- /dev/null +++ b/apps/essimporter/importcellref.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_ESSIMPORT_CELLREF_H +#define OPENMW_ESSIMPORT_CELLREF_H + +#include + +#include + +#include "importacdt.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + struct CellRef : public ActorData + { + std::string mIndexedRefId; + + std::string mScript; + + bool mEnabled; + + bool mDeleted; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importcntc.cpp b/apps/essimporter/importcntc.cpp new file mode 100644 index 0000000000..a492aef5aa --- /dev/null +++ b/apps/essimporter/importcntc.cpp @@ -0,0 +1,16 @@ +#include "importcntc.hpp" + +#include + +namespace ESSImport +{ + + void CNTC::load(ESM::ESMReader &esm) + { + mIndex = 0; + esm.getHNT(mIndex, "INDX"); + + mInventory.load(esm); + } + +} diff --git a/apps/essimporter/importcntc.hpp b/apps/essimporter/importcntc.hpp new file mode 100644 index 0000000000..1bc7d94bd5 --- /dev/null +++ b/apps/essimporter/importcntc.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTCNTC_H +#define OPENMW_ESSIMPORT_IMPORTCNTC_H + +#include "importinventory.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Changed container contents + struct CNTC + { + int mIndex; + + Inventory mInventory; + + void load(ESM::ESMReader& esm); + }; + +} +#endif diff --git a/apps/essimporter/importcrec.cpp b/apps/essimporter/importcrec.cpp new file mode 100644 index 0000000000..7a8a3eb00d --- /dev/null +++ b/apps/essimporter/importcrec.cpp @@ -0,0 +1,25 @@ +#include "importcrec.hpp" + +#include + +namespace ESSImport +{ + + void CREC::load(ESM::ESMReader &esm) + { + esm.getHNT(mIndex, "INDX"); + + // equivalent of ESM::Creature XSCL? probably don't have to convert this, + // since the value can't be changed + float scale; + esm.getHNOT(scale, "XSCL"); + + // FIXME: use AiPackageList, need to fix getSubName() + while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") + || esm.isNextSub("AI_A")) + esm.skipHSub(); + + mInventory.load(esm); + } + +} diff --git a/apps/essimporter/importcrec.hpp b/apps/essimporter/importcrec.hpp new file mode 100644 index 0000000000..16b7528070 --- /dev/null +++ b/apps/essimporter/importcrec.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_ESSIMPORT_CREC_H +#define OPENMW_ESSIMPORT_CREC_H + +#include "importinventory.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Creature changes + struct CREC + { + int mIndex; + + Inventory mInventory; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importdial.cpp b/apps/essimporter/importdial.cpp new file mode 100644 index 0000000000..5797a708a1 --- /dev/null +++ b/apps/essimporter/importdial.cpp @@ -0,0 +1,23 @@ +#include "importdial.hpp" + +#include + +namespace ESSImport +{ + + void DIAL::load(ESM::ESMReader &esm) + { + // See ESM::Dialogue::Type enum, not sure why we would need this here though + int type = 0; + esm.getHNOT(type, "DATA"); + + // Deleted dialogue in a savefile. No clue what this means... + int deleted = 0; + esm.getHNOT(deleted, "DELE"); + + mIndex = 0; + // *should* always occur except when the dialogue is deleted, but leaving it optional just in case... + esm.getHNOT(mIndex, "XIDX"); + } + +} diff --git a/apps/essimporter/importdial.hpp b/apps/essimporter/importdial.hpp new file mode 100644 index 0000000000..9a1e882332 --- /dev/null +++ b/apps/essimporter/importdial.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTDIAL_H +#define OPENMW_ESSIMPORT_IMPORTDIAL_H +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + struct DIAL + { + int mIndex; // Journal index + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importer.cpp b/apps/essimporter/importer.cpp new file mode 100644 index 0000000000..d5ed43b8a0 --- /dev/null +++ b/apps/essimporter/importer.cpp @@ -0,0 +1,378 @@ +#include "importer.hpp" +#include + +#include + +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "importercontext.hpp" + +#include "converter.hpp" + +namespace +{ + + void writeScreenshot(const ESM::Header& fileHeader, ESM::SavedGame& out) + { + Ogre::Image screenshot; + std::vector screenshotData = fileHeader.mSCRS; // MemoryDataStream doesn't work with const data :( + Ogre::DataStreamPtr screenshotStream (new Ogre::MemoryDataStream(&screenshotData[0], screenshotData.size())); + screenshot.loadRawData(screenshotStream, 128, 128, 1, Ogre::PF_BYTE_BGRA); + Ogre::DataStreamPtr encoded = screenshot.encode("jpg"); + out.mScreenshot.resize(encoded->size()); + encoded->read(&out.mScreenshot[0], encoded->size()); + } + +} + +namespace ESSImport +{ + + Importer::Importer(const std::string &essfile, const std::string &outfile, const std::string &encoding) + : mEssFile(essfile) + , mOutFile(outfile) + , mEncoding(encoding) + { + + } + + struct File + { + struct Subrecord + { + std::string mName; + size_t mFileOffset; + std::vector mData; + }; + + struct Record + { + std::string mName; + size_t mFileOffset; + std::vector mSubrecords; + }; + + std::vector mRecords; + }; + + void read(const std::string& filename, File& file) + { + ESM::ESMReader esm; + esm.open(filename); + + while (esm.hasMoreRecs()) + { + ESM::NAME n = esm.getRecName(); + esm.getRecHeader(); + + File::Record rec; + rec.mName = n.toString(); + rec.mFileOffset = esm.getFileOffset(); + while (esm.hasMoreSubs()) + { + File::Subrecord sub; + esm.getSubName(); + esm.getSubHeader(); + sub.mFileOffset = esm.getFileOffset(); + sub.mName = esm.retSubName().toString(); + sub.mData.resize(esm.getSubSize()); + esm.getExact(&sub.mData[0], sub.mData.size()); + rec.mSubrecords.push_back(sub); + } + file.mRecords.push_back(rec); + } + } + + void Importer::compare() + { + // data that always changes (and/or is already fully decoded) should be blacklisted + std::set > blacklist; + blacklist.insert(std::make_pair("GLOB", "FLTV")); // gamehour + blacklist.insert(std::make_pair("REFR", "DATA")); // player position + blacklist.insert(std::make_pair("CELL", "NAM8")); // fog of war + blacklist.insert(std::make_pair("GAME", "GMDT")); // weather data, current time always changes + blacklist.insert(std::make_pair("CELL", "DELE")); // first 3 bytes are uninitialized + + // this changes way too often, name suggests some renderer internal data? + blacklist.insert(std::make_pair("CELL", "ND3D")); + blacklist.insert(std::make_pair("REFR", "ND3D")); + + File file1; + read(mEssFile, file1); + File file2; + read(mOutFile, file2); // todo rename variable + + // FIXME: use max(size1, size2) + for (unsigned int i=0; i= file2.mRecords.size()) + { + std::cout << "Record in file1 not present in file2: (1) 0x" << std::hex << rec.mFileOffset << std::endl; + return; + } + + File::Record rec2 = file2.mRecords[i]; + + if (rec.mName != rec2.mName) + { + std::cout << "Different record name at (2) 0x" << std::hex << rec2.mFileOffset << std::endl; + return; // TODO: try to recover + } + + // FIXME: use max(size1, size2) + for (unsigned int j=0; j= rec2.mSubrecords.size()) + { + std::cout << "Subrecord in file1 not present in file2: (1) 0x" << std::hex << sub.mFileOffset << std::endl; + return; + } + + File::Subrecord sub2 = rec2.mSubrecords[j]; + + if (sub.mName != sub2.mName) + { + std::cout << "Different subrecord name (" << rec.mName << "." << sub.mName << " vs. " << sub2.mName << ") at (1) 0x" << std::hex << sub.mFileOffset + << " (2) 0x" << sub2.mFileOffset << std::endl; + break; // TODO: try to recover + } + + if (sub.mData != sub2.mData) + { + if (blacklist.find(std::make_pair(rec.mName, sub.mName)) != blacklist.end()) + continue; + + std::cout << "Different subrecord data for " << rec.mName << "." << sub.mName << " at (1) 0x" << std::hex << sub.mFileOffset + << " (2) 0x" << sub2.mFileOffset << std::endl; + + std::cout << "Data 1:" << std::endl; + for (unsigned int k=0; k= sub2.mData.size() || sub2.mData[k] != sub.mData[k]) + different = true; + + if (different) + std::cout << "\033[033m"; + std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub.mData[k] << " "; + if (different) + std::cout << "\033[0m"; + } + std::cout << std::endl; + + std::cout << "Data 2:" << std::endl; + for (unsigned int k=0; k= sub.mData.size() || sub.mData[k] != sub2.mData[k]) + different = true; + + if (different) + std::cout << "\033[033m"; + std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)sub2.mData[k] << " "; + if (different) + std::cout << "\033[0m"; + } + std::cout << std::endl; + } + } + } + } + + void Importer::run() + { + // construct Ogre::Root to gain access to image codecs + Ogre::LogManager logman; + Ogre::Root root; + + ToUTF8::Utf8Encoder encoder(ToUTF8::calculateEncoding(mEncoding)); + ESM::ESMReader esm; + esm.open(mEssFile); + esm.setEncoder(&encoder); + + Context context; + + const ESM::Header& header = esm.getHeader(); + context.mPlayerCellName = header.mGameData.mCurrentCell.toString(); + + const unsigned int recREFR = ESM::FourCC<'R','E','F','R'>::value; + const unsigned int recPCDT = ESM::FourCC<'P','C','D','T'>::value; + const unsigned int recFMAP = ESM::FourCC<'F','M','A','P'>::value; + const unsigned int recKLST = ESM::FourCC<'K','L','S','T'>::value; + const unsigned int recSTLN = ESM::FourCC<'S','T','L','N'>::value; + const unsigned int recGAME = ESM::FourCC<'G','A','M','E'>::value; + const unsigned int recJOUR = ESM::FourCC<'J','O','U','R'>::value; + + std::map > converters; + converters[ESM::REC_GLOB] = boost::shared_ptr(new ConvertGlobal()); + converters[ESM::REC_BOOK] = boost::shared_ptr(new ConvertBook()); + converters[ESM::REC_NPC_] = boost::shared_ptr(new ConvertNPC()); + converters[ESM::REC_CREA] = boost::shared_ptr(new ConvertCREA()); + converters[ESM::REC_NPCC] = boost::shared_ptr(new ConvertNPCC()); + converters[ESM::REC_CREC] = boost::shared_ptr(new ConvertCREC()); + converters[recREFR ] = boost::shared_ptr(new ConvertREFR()); + converters[recPCDT ] = boost::shared_ptr(new ConvertPCDT()); + converters[recFMAP ] = boost::shared_ptr(new ConvertFMAP()); + converters[recKLST ] = boost::shared_ptr(new ConvertKLST()); + converters[recSTLN ] = boost::shared_ptr(new ConvertSTLN()); + converters[recGAME ] = boost::shared_ptr(new ConvertGAME()); + converters[ESM::REC_CELL] = boost::shared_ptr(new ConvertCell()); + converters[ESM::REC_ALCH] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_CLAS] = boost::shared_ptr(new ConvertClass()); + converters[ESM::REC_SPEL] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_ARMO] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_WEAP] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_CLOT] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_ENCH] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_WEAP] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_LEVC] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_LEVI] = boost::shared_ptr(new DefaultConverter()); + converters[ESM::REC_CNTC] = boost::shared_ptr(new ConvertCNTC()); + converters[ESM::REC_FACT] = boost::shared_ptr(new ConvertFACT()); + converters[ESM::REC_INFO] = boost::shared_ptr(new ConvertINFO()); + converters[ESM::REC_DIAL] = boost::shared_ptr(new ConvertDIAL()); + converters[ESM::REC_QUES] = boost::shared_ptr(new ConvertQUES()); + converters[recJOUR ] = boost::shared_ptr(new ConvertJOUR()); + converters[ESM::REC_SCPT] = boost::shared_ptr(new ConvertSCPT()); + + // TODO: + // - REGN (weather in certain regions?) + // - VFXM + // - SPLM (active spell effects) + // - PROJ (magic projectiles in air) + + std::set unknownRecords; + + for (std::map >::const_iterator it = converters.begin(); + it != converters.end(); ++it) + { + it->second->setContext(context); + } + + while (esm.hasMoreRecs()) + { + ESM::NAME n = esm.getRecName(); + esm.getRecHeader(); + + std::map >::iterator it = converters.find(n.val); + if (it != converters.end()) + { + it->second->read(esm); + } + else + { + if (unknownRecords.insert(n.val).second) + std::cerr << "unknown record " << n.toString() << " (0x" << std::hex << esm.getFileOffset() << ")" << std::endl; + + esm.skipRecord(); + } + } + + ESM::ESMWriter writer; + + writer.setFormat (ESM::Header::CurrentFormat); + + std::ofstream stream(mOutFile.c_str(), std::ios::binary); + // all unused + writer.setVersion(0); + writer.setType(0); + writer.setAuthor(""); + writer.setDescription(""); + writer.setRecordCount (0); + + for (std::vector::const_iterator it = header.mMaster.begin(); + it != header.mMaster.end(); ++it) + writer.addMaster (it->name, 0); // not using the size information anyway -> use value of 0 + + writer.save (stream); + + ESM::SavedGame profile; + for (std::vector::const_iterator it = header.mMaster.begin(); + it != header.mMaster.end(); ++it) + { + profile.mContentFiles.push_back(it->name); + } + profile.mDescription = esm.getDesc(); + profile.mInGameTime.mDay = context.mDay; + profile.mInGameTime.mGameHour = context.mHour; + profile.mInGameTime.mMonth = context.mMonth; + profile.mInGameTime.mYear = context.mYear; + profile.mPlayerCell = header.mGameData.mCurrentCell.toString(); + if (context.mPlayerBase.mClass == "NEWCLASSID_CHARGEN") + profile.mPlayerClassName = context.mCustomPlayerClassName; + else + profile.mPlayerClassId = context.mPlayerBase.mClass; + profile.mPlayerLevel = context.mPlayerBase.mNpdt52.mLevel; + profile.mPlayerName = header.mGameData.mPlayerName.toString(); + + writeScreenshot(header, profile); + + writer.startRecord (ESM::REC_SAVE); + profile.save (writer); + writer.endRecord (ESM::REC_SAVE); + + // Writing order should be Dynamic Store -> Cells -> Player, + // so that references to dynamic records can be recognized when loading + for (std::map >::const_iterator it = converters.begin(); + it != converters.end(); ++it) + { + if (it->second->getStage() != 0) + continue; + it->second->write(writer); + } + + writer.startRecord(ESM::REC_NPC_); + writer.writeHNString("NAME", "player"); + context.mPlayerBase.save(writer); + writer.endRecord(ESM::REC_NPC_); + + for (std::map >::const_iterator it = converters.begin(); + it != converters.end(); ++it) + { + if (it->second->getStage() != 1) + continue; + it->second->write(writer); + } + + writer.startRecord(ESM::REC_PLAY); + if (context.mPlayer.mCellId.mPaged) + { + // exterior cell -> determine cell coordinates based on position + const int cellSize = 8192; + int cellX = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[0]/cellSize)); + int cellY = static_cast(std::floor(context.mPlayer.mObject.mPosition.pos[1] / cellSize)); + context.mPlayer.mCellId.mIndex.mX = cellX; + context.mPlayer.mCellId.mIndex.mY = cellY; + } + context.mPlayer.save(writer); + writer.endRecord(ESM::REC_PLAY); + + writer.startRecord (ESM::REC_DIAS); + context.mDialogueState.save(writer); + writer.endRecord(ESM::REC_DIAS); + } + + +} diff --git a/apps/essimporter/importer.hpp b/apps/essimporter/importer.hpp new file mode 100644 index 0000000000..ccacd7972d --- /dev/null +++ b/apps/essimporter/importer.hpp @@ -0,0 +1,26 @@ +#ifndef OPENMW_ESSIMPORTER_IMPORTER_H +#define OPENMW_ESSIMPORTER_IMPORTER_H + +#include + +namespace ESSImport +{ + + class Importer + { + public: + Importer(const std::string& essfile, const std::string& outfile, const std::string& encoding); + + void run(); + + void compare(); + + private: + std::string mEssFile; + std::string mOutFile; + std::string mEncoding; + }; + +} + +#endif diff --git a/apps/essimporter/importercontext.cpp b/apps/essimporter/importercontext.cpp new file mode 100644 index 0000000000..e69de29bb2 diff --git a/apps/essimporter/importercontext.hpp b/apps/essimporter/importercontext.hpp new file mode 100644 index 0000000000..3b010cb8ff --- /dev/null +++ b/apps/essimporter/importercontext.hpp @@ -0,0 +1,73 @@ +#ifndef OPENMW_ESSIMPORT_CONTEXT_H +#define OPENMW_ESSIMPORT_CONTEXT_H + +#include + +#include +#include +#include +#include +#include +#include + +#include "importnpcc.hpp" +#include "importcrec.hpp" +#include "importcntc.hpp" +#include "importplayer.hpp" + + + + +namespace ESSImport +{ + + struct Context + { + // set from the TES3 header + std::string mPlayerCellName; + + ESM::Player mPlayer; + ESM::NPC mPlayerBase; + std::string mCustomPlayerClassName; + + ESM::DialogueState mDialogueState; + + // cells which should show an explored overlay on the global map + std::set > mExploredCells; + + ESM::GlobalMap mGlobalMapState; + + int mDay, mMonth, mYear; + float mHour; + + // key + std::map, CREC> mCreatureChanges; + std::map, NPCC> mNpcChanges; + std::map, CNTC> mContainerChanges; + + std::map mCreatures; + std::map mNpcs; + + Context() + { + mPlayer.mAutoMove = 0; + ESM::CellId playerCellId; + playerCellId.mPaged = true; + playerCellId.mIndex.mX = playerCellId.mIndex.mY = 0; + mPlayer.mCellId = playerCellId; + //mPlayer.mLastKnownExteriorPosition + mPlayer.mHasMark = 0; // TODO + mPlayer.mCurrentCrimeId = 0; // TODO + mPlayer.mObject.blank(); + mPlayer.mObject.mRef.mRefID = "player"; // REFR.mRefID would be PlayerSaveGame + + mGlobalMapState.mBounds.mMinX = 0; + mGlobalMapState.mBounds.mMaxX = 0; + mGlobalMapState.mBounds.mMinY = 0; + mGlobalMapState.mBounds.mMaxY = 0; + } + }; + +} + +#endif diff --git a/apps/essimporter/importgame.cpp b/apps/essimporter/importgame.cpp new file mode 100644 index 0000000000..1012541b49 --- /dev/null +++ b/apps/essimporter/importgame.cpp @@ -0,0 +1,29 @@ +#include "importgame.hpp" + +#include + +namespace ESSImport +{ + +void GAME::load(ESM::ESMReader &esm) +{ + esm.getSubNameIs("GMDT"); + esm.getSubHeader(); + if (esm.getSubSize() == 92) + { + esm.getExact(&mGMDT, 92); + mGMDT.mSecundaPhase = 0; + } + else if (esm.getSubSize() == 96) + { + esm.getT(mGMDT); + } + else + esm.fail("unexpected subrecord size for GAME.GMDT"); + + mGMDT.mWeatherTransition &= (0x000000ff); + mGMDT.mSecundaPhase &= (0x000000ff); + mGMDT.mMasserPhase &= (0x000000ff); +} + +} diff --git a/apps/essimporter/importgame.hpp b/apps/essimporter/importgame.hpp new file mode 100644 index 0000000000..fca7d72a0a --- /dev/null +++ b/apps/essimporter/importgame.hpp @@ -0,0 +1,33 @@ +#ifndef OPENMW_ESSIMPORT_GAME_H +#define OPENMW_ESSIMPORT_GAME_H + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Weather data + struct GAME + { + struct GMDT + { + char mCellName[64]; + int mFogColour; + float mFogDensity; + int mCurrentWeather, mNextWeather; + int mWeatherTransition; // 0-100 transition between weathers, top 3 bytes may be garbage + float mTimeOfNextTransition; // weather changes when gamehour == timeOfNextTransition + int mMasserPhase, mSecundaPhase; // top 3 bytes may be garbage + }; + + GMDT mGMDT; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importinfo.cpp b/apps/essimporter/importinfo.cpp new file mode 100644 index 0000000000..1131553709 --- /dev/null +++ b/apps/essimporter/importinfo.cpp @@ -0,0 +1,14 @@ +#include "importinfo.hpp" + +#include + +namespace ESSImport +{ + + void INFO::load(ESM::ESMReader &esm) + { + mInfo = esm.getHNString("INAM"); + mActorRefId = esm.getHNString("ACDT"); + } + +} diff --git a/apps/essimporter/importinfo.hpp b/apps/essimporter/importinfo.hpp new file mode 100644 index 0000000000..f4d616786f --- /dev/null +++ b/apps/essimporter/importinfo.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTINFO_H +#define OPENMW_ESSIMPORT_IMPORTINFO_H + +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + struct INFO + { + std::string mInfo; + std::string mActorRefId; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importinventory.cpp b/apps/essimporter/importinventory.cpp new file mode 100644 index 0000000000..d27cd5c8ca --- /dev/null +++ b/apps/essimporter/importinventory.cpp @@ -0,0 +1,68 @@ +#include "importinventory.hpp" + +#include + +#include + +#include + +namespace ESSImport +{ + + void Inventory::load(ESM::ESMReader &esm) + { + while (esm.isNextSub("NPCO")) + { + ESM::ContItem contItem; + esm.getHT(contItem); + + InventoryItem item; + item.mId = contItem.mItem.toString(); + item.mCount = contItem.mCount; + item.mRelativeEquipmentSlot = -1; + + // seems that a stack of items can have a set of subrecords for each item? rings0000.ess + // doesn't make any sense to me, if the values were different then the items shouldn't stack in the first place? + // I guess we should double check the stacking logic in OpenMW + for (int i=0;i= int(mItems.size())) + esm.fail("equipment item index out of range"); + + // appears to be a relative index for only the *possible* slots this item can be equipped in, + // i.e. 0 most of the time + int slotIndex; + esm.getT(slotIndex); + + mItems[itemIndex].mRelativeEquipmentSlot = slotIndex; + } + } + +} diff --git a/apps/essimporter/importinventory.hpp b/apps/essimporter/importinventory.hpp new file mode 100644 index 0000000000..0b5405d961 --- /dev/null +++ b/apps/essimporter/importinventory.hpp @@ -0,0 +1,34 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTINVENTORY_H +#define OPENMW_ESSIMPORT_IMPORTINVENTORY_H + +#include +#include + +#include +#include "importscri.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + struct Inventory + { + struct InventoryItem : public ESM::CellRef + { + std::string mId; + int mCount; + int mRelativeEquipmentSlot; + SCRI mSCRI; + }; + std::vector mItems; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importjour.cpp b/apps/essimporter/importjour.cpp new file mode 100644 index 0000000000..e5d24e113c --- /dev/null +++ b/apps/essimporter/importjour.cpp @@ -0,0 +1,13 @@ +#include "importjour.hpp" + +#include + +namespace ESSImport +{ + + void JOUR::load(ESM::ESMReader &esm) + { + mText = esm.getHNString("NAME"); + } + +} diff --git a/apps/essimporter/importjour.hpp b/apps/essimporter/importjour.hpp new file mode 100644 index 0000000000..1b2d094f63 --- /dev/null +++ b/apps/essimporter/importjour.hpp @@ -0,0 +1,25 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTJOUR_H +#define OPENMW_ESSIMPORT_IMPORTJOUR_H + +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Journal + struct JOUR + { + // The entire journal, in HTML + std::string mText; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importklst.cpp b/apps/essimporter/importklst.cpp new file mode 100644 index 0000000000..daa1ab0774 --- /dev/null +++ b/apps/essimporter/importklst.cpp @@ -0,0 +1,22 @@ +#include "importklst.hpp" + +#include + +namespace ESSImport +{ + + void KLST::load(ESM::ESMReader &esm) + { + while (esm.isNextSub("KNAM")) + { + std::string refId = esm.getHString(); + int count; + esm.getHNT(count, "CNAM"); + mKillCounter[refId] = count; + } + + mWerewolfKills = 0; + esm.getHNOT(mWerewolfKills, "INTV"); + } + +} diff --git a/apps/essimporter/importklst.hpp b/apps/essimporter/importklst.hpp new file mode 100644 index 0000000000..d07332600f --- /dev/null +++ b/apps/essimporter/importklst.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESSIMPORT_KLST_H +#define OPENMW_ESSIMPORT_KLST_H + +#include +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Kill Stats + struct KLST + { + void load(ESM::ESMReader& esm); + + /// RefId, kill count + std::map mKillCounter; + + int mWerewolfKills; + }; + +} + +#endif diff --git a/apps/essimporter/importnpcc.cpp b/apps/essimporter/importnpcc.cpp new file mode 100644 index 0000000000..547b014413 --- /dev/null +++ b/apps/essimporter/importnpcc.cpp @@ -0,0 +1,20 @@ +#include "importnpcc.hpp" + +#include + +namespace ESSImport +{ + + void NPCC::load(ESM::ESMReader &esm) + { + esm.getHNT(mNPDT, "NPDT"); + + // FIXME: use AiPackageList, need to fix getSubName() + while (esm.isNextSub("AI_W") || esm.isNextSub("AI_E") || esm.isNextSub("AI_T") || esm.isNextSub("AI_F") + || esm.isNextSub("AI_A")) + esm.skipHSub(); + + mInventory.load(esm); + } + +} diff --git a/apps/essimporter/importnpcc.hpp b/apps/essimporter/importnpcc.hpp new file mode 100644 index 0000000000..c69fa3e035 --- /dev/null +++ b/apps/essimporter/importnpcc.hpp @@ -0,0 +1,36 @@ +#ifndef OPENMW_ESSIMPORT_NPCC_H +#define OPENMW_ESSIMPORT_NPCC_H + +#include + +#include + +#include "importinventory.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + struct NPCC + { + struct NPDT + { + unsigned char mDisposition; + unsigned char unknown; + unsigned char mReputation; + unsigned char unknown2; + int mIndex; + } mNPDT; + + Inventory mInventory; + + void load(ESM::ESMReader &esm); + }; + +} + +#endif diff --git a/apps/essimporter/importplayer.cpp b/apps/essimporter/importplayer.cpp new file mode 100644 index 0000000000..9845ab072a --- /dev/null +++ b/apps/essimporter/importplayer.cpp @@ -0,0 +1,85 @@ +#include "importplayer.hpp" + +#include + +namespace ESSImport +{ + + void REFR::load(ESM::ESMReader &esm) + { + esm.getHNT(mRefNum.mIndex, "FRMR"); + + mRefID = esm.getHNString("NAME"); + + mActorData.load(esm); + + esm.getHNOT(mPos, "DATA", 24); + } + + void PCDT::load(ESM::ESMReader &esm) + { + while (esm.isNextSub("DNAM")) + { + mKnownDialogueTopics.push_back(esm.getHString()); + } + + if (esm.isNextSub("MNAM")) + esm.skipHSub(); // If this field is here it seems to specify the interior cell the player is in, + // but it's not always here, so it's kinda useless + + esm.getHNT(mPNAM, "PNAM"); + + if (esm.isNextSub("SNAM")) + esm.skipHSub(); + if (esm.isNextSub("NAM9")) + esm.skipHSub(); + + mBounty = 0; + esm.getHNOT(mBounty, "CNAM"); + + mBirthsign = esm.getHNOString("BNAM"); + + // Holds the names of the last used Alchemy apparatus. Don't need to import this ATM, + // because our GUI auto-selects the best apparatus. + if (esm.isNextSub("NAM0")) + esm.skipHSub(); + if (esm.isNextSub("NAM1")) + esm.skipHSub(); + if (esm.isNextSub("NAM2")) + esm.skipHSub(); + if (esm.isNextSub("NAM3")) + esm.skipHSub(); + + if (esm.isNextSub("ENAM")) + esm.skipHSub(); + + if (esm.isNextSub("LNAM")) + esm.skipHSub(); + + while (esm.isNextSub("FNAM")) + { + FNAM fnam; + esm.getHT(fnam); + mFactions.push_back(fnam); + } + + if (esm.isNextSub("AADT")) + esm.skipHSub(); // 44 bytes, no clue + + if (esm.isNextSub("KNAM")) + esm.skipHSub(); // assigned Quick Keys, I think + + if (esm.isNextSub("WERE")) + { + // some werewolf data, 152 bytes + // maybe current skills and attributes for werewolf form + esm.getSubHeader(); + esm.skip(152); + } + + // unsure if before or after WERE + if (esm.isNextSub("ANIS")) + esm.skipHSub(); + } + +} diff --git a/apps/essimporter/importplayer.hpp b/apps/essimporter/importplayer.hpp new file mode 100644 index 0000000000..bc6b94be28 --- /dev/null +++ b/apps/essimporter/importplayer.hpp @@ -0,0 +1,83 @@ +#ifndef OPENMW_ESSIMPORT_PLAYER_H +#define OPENMW_ESSIMPORT_PLAYER_H + +#include +#include + +#include +#include +#include + +#include "importacdt.hpp" + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + +/// Player-agnostic player data +struct REFR +{ + ActorData mActorData; + + std::string mRefID; + ESM::Position mPos; + ESM::RefNum mRefNum; + + void load(ESM::ESMReader& esm); +}; + +/// Other player data +struct PCDT +{ + int mBounty; + std::string mBirthsign; + + std::vector mKnownDialogueTopics; + + enum DrawState_ + { + DrawState_Weapon = 0x80, + DrawState_Spell = 0x100 + }; + enum CameraState + { + CameraState_FirstPerson = 0x8, + CameraState_ThirdPerson = 0xa + }; + +#pragma pack(push) +#pragma pack(1) + struct FNAM + { + unsigned char mRank; + unsigned char mUnknown1[3]; + int mReputation; + unsigned char mFlags; // 0x1: unknown, 0x2: expelled + unsigned char mUnknown2[3]; + ESM::NAME32 mFactionName; + }; + + struct PNAM + { + short mDrawState; // DrawState + short mCameraState; // CameraState + unsigned int mLevelProgress; + float mSkillProgress[27]; // skill progress, non-uniform scaled + unsigned char mSkillIncreases[8]; // number of skill increases for each attribute + unsigned char mUnknown3[88]; + }; +#pragma pack(pop) + + std::vector mFactions; + PNAM mPNAM; + + void load(ESM::ESMReader& esm); +}; + +} + +#endif diff --git a/apps/essimporter/importques.cpp b/apps/essimporter/importques.cpp new file mode 100644 index 0000000000..78b779e439 --- /dev/null +++ b/apps/essimporter/importques.cpp @@ -0,0 +1,14 @@ +#include "importques.hpp" + +#include + +namespace ESSImport +{ + + void QUES::load(ESM::ESMReader &esm) + { + while (esm.isNextSub("DATA")) + mInfo.push_back(esm.getHString()); + } + +} diff --git a/apps/essimporter/importques.hpp b/apps/essimporter/importques.hpp new file mode 100644 index 0000000000..51fe22434c --- /dev/null +++ b/apps/essimporter/importques.hpp @@ -0,0 +1,28 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTQUES_H +#define OPENMW_ESSIMPORT_IMPORTQUES_H + +#include +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// State for a quest + /// Presumably this record only exists when Tribunal is installed, + /// since pre-Tribunal there weren't any quest names in the data files. + struct QUES + { + std::string mName; // NAME, should be assigned from outside as usual + std::vector mInfo; // list of journal entries for the quest + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importscpt.cpp b/apps/essimporter/importscpt.cpp new file mode 100644 index 0000000000..652383cdaa --- /dev/null +++ b/apps/essimporter/importscpt.cpp @@ -0,0 +1,26 @@ +#include "importscpt.hpp" + +#include + + + +namespace ESSImport +{ + + void SCPT::load(ESM::ESMReader &esm) + { + esm.getHNT(mSCHD, "SCHD"); + + mSCRI.load(esm); + + mRefNum = -1; + if (esm.isNextSub("RNAM")) + { + mRunning = true; + esm.getHT(mRefNum); + } + else + mRunning = false; + } + +} diff --git a/apps/essimporter/importscpt.hpp b/apps/essimporter/importscpt.hpp new file mode 100644 index 0000000000..ce54c3a734 --- /dev/null +++ b/apps/essimporter/importscpt.hpp @@ -0,0 +1,32 @@ +#ifndef OPENMW_ESSIMPORT_IMPORTSCPT_H +#define OPENMW_ESSIMPORT_IMPORTSCPT_H + +#include "importscri.hpp" + +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + // A running global script + struct SCPT + { + ESM::Script::SCHD mSCHD; + + // values of local variables + SCRI mSCRI; + + bool mRunning; + int mRefNum; // Targeted reference, -1: no reference + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/importscri.cpp b/apps/essimporter/importscri.cpp new file mode 100644 index 0000000000..de0b35c86c --- /dev/null +++ b/apps/essimporter/importscri.cpp @@ -0,0 +1,55 @@ +#include "importscri.hpp" + +#include + +namespace ESSImport +{ + + void SCRI::load(ESM::ESMReader &esm) + { + mScript = esm.getHNOString("SCRI"); + + int numShorts = 0, numLongs = 0, numFloats = 0; + if (esm.isNextSub("SLCS")) + { + esm.getSubHeader(); + esm.getT(numShorts); + esm.getT(numLongs); + esm.getT(numFloats); + } + + if (esm.isNextSub("SLSD")) + { + esm.getSubHeader(); + for (int i=0; i + +#include + +namespace ESM +{ + class ESMReader; +} + +namespace ESSImport +{ + + /// Local variable assigments for a running script + struct SCRI + { + std::string mScript; + + std::vector mShorts; + std::vector mLongs; + std::vector mFloats; + + void load(ESM::ESMReader& esm); + }; + +} + +#endif diff --git a/apps/essimporter/main.cpp b/apps/essimporter/main.cpp new file mode 100644 index 0000000000..a4ad114ec1 --- /dev/null +++ b/apps/essimporter/main.cpp @@ -0,0 +1,77 @@ +#include + +#include +#include +#include +#include + +#include + +#include "importer.hpp" + +namespace bpo = boost::program_options; +namespace bfs = boost::filesystem; + + + +int main(int argc, char** argv) +{ + try + { + bpo::options_description desc("Syntax: openmw-essimporter infile.ess outfile.omwsave\nAllowed options"); + bpo::positional_options_description p_desc; + desc.add_options() + ("help,h", "produce help message") + ("mwsave,m", bpo::value(), "morrowind .ess save file") + ("output,o", bpo::value(), "output file (.omwsave)") + ("compare,c", "compare two .ess files") + ("encoding", boost::program_options::value()->default_value("win1252"), "encoding of the save file") + ; + p_desc.add("mwsave", 1).add("output", 1); + + bpo::variables_map variables; + + bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) + .options(desc) + .positional(p_desc) + .run(); + + bpo::store(parsed, variables); + + if(variables.count("help") || !variables.count("mwsave") || !variables.count("output")) { + std::cout << desc; + return 0; + } + + bpo::notify(variables); + + Files::ConfigurationManager cfgManager(true); + cfgManager.readConfiguration(variables, desc); + + std::string essFile = variables["mwsave"].as(); + std::string outputFile = variables["output"].as(); + std::string encoding = variables["encoding"].as(); + + ESSImport::Importer importer(essFile, outputFile, encoding); + + if (variables.count("compare")) + importer.compare(); + else + { + const std::string& ext = ".omwsave"; + if (boost::filesystem::exists(boost::filesystem::path(outputFile)) + && (outputFile.size() < ext.size() || outputFile.substr(outputFile.size()-ext.size()) != ext)) + { + throw std::runtime_error("Output file already exists and does not end in .omwsave. Did you mean to use --compare?"); + } + importer.run(); + } + } + catch (std::exception& e) + { + std::cerr << "Error: " << e.what() << std::endl; + return 1; + } + + return 0; +} diff --git a/apps/launcher/CMakeLists.txt b/apps/launcher/CMakeLists.txt index ba330f70c6..0de79f8f6b 100644 --- a/apps/launcher/CMakeLists.txt +++ b/apps/launcher/CMakeLists.txt @@ -78,7 +78,7 @@ if(NOT WIN32) endif(NOT WIN32) # Main executable -add_executable(omwlauncher +add_executable(openmw-launcher ${GUI_TYPE} ${LAUNCHER} ${LAUNCHER_HEADER} @@ -87,7 +87,7 @@ add_executable(omwlauncher ${UI_HDRS} ) -target_link_libraries(omwlauncher +target_link_libraries(openmw-launcher ${Boost_LIBRARIES} ${OGRE_LIBRARIES} ${OGRE_STATIC_PLUGINS} @@ -99,6 +99,6 @@ target_link_libraries(omwlauncher if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) - target_link_libraries(omwlauncher gcov) + target_link_libraries(openmw-launcher gcov) endif() diff --git a/apps/launcher/datafilespage.cpp b/apps/launcher/datafilespage.cpp index f45b444707..7861894b08 100644 --- a/apps/launcher/datafilespage.cpp +++ b/apps/launcher/datafilespage.cpp @@ -20,6 +20,9 @@ #include "utils/textinputdialog.hpp" #include "utils/profilescombobox.hpp" + +const char *Launcher::DataFilesPage::mDefaultContentListName = "Default"; + Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config::GameSettings &gameSettings, Config::LauncherSettings &launcherSettings, QWidget *parent) : mCfgMgr(cfg) , mGameSettings(gameSettings) @@ -30,7 +33,7 @@ Launcher::DataFilesPage::DataFilesPage(Files::ConfigurationManager &cfg, Config: setObjectName ("DataFilesPage"); mSelector = new ContentSelectorView::ContentSelector (ui.contentSelectorWidget); - mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); @@ -44,13 +47,13 @@ void Launcher::DataFilesPage::buildView() ui.verticalLayout->insertWidget (0, mSelector->uiWidget()); //tool buttons - ui.newProfileButton->setToolTip ("Create a new profile"); - ui.deleteProfileButton->setToolTip ("Delete an existing profile"); + ui.newProfileButton->setToolTip ("Create a new Content List"); + ui.deleteProfileButton->setToolTip ("Delete an existing Content List"); //combo box - ui.profilesComboBox->addItem ("Default"); - ui.profilesComboBox->setPlaceholderText (QString("Select a profile...")); - ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String("Default"))); + ui.profilesComboBox->addItem(mDefaultContentListName); + ui.profilesComboBox->setPlaceholderText (QString("Select a Content List...")); + ui.profilesComboBox->setCurrentIndex(ui.profilesComboBox->findText(QLatin1String(mDefaultContentListName))); // Add the actions to the toolbuttons ui.newProfileButton->setDefaultAction (ui.newProfileAction); @@ -69,21 +72,8 @@ void Launcher::DataFilesPage::buildView() bool Launcher::DataFilesPage::loadSettings() { - QStringList paths = mGameSettings.getDataDirs(); - - foreach (const QString &path, paths) - mSelector->addFiles(path); - - mDataLocal = mGameSettings.getDataLocal(); - - if (!mDataLocal.isEmpty()) - mSelector->addFiles(mDataLocal); - - paths.insert (0, mDataLocal); - PathIterator pathIterator (paths); - - QStringList profiles = mLauncherSettings.subKeys(QString("Profiles/")); - QString currentProfile = mLauncherSettings.getSettings().value("Profiles/currentprofile"); + QStringList profiles = mLauncherSettings.getContentLists(); + QString currentProfile = mLauncherSettings.getCurrentContentListName(); qDebug() << "current profile is: " << currentProfile; @@ -94,20 +84,41 @@ bool Launcher::DataFilesPage::loadSettings() if (!currentProfile.isEmpty()) addProfile(currentProfile, true); - QStringList files = mLauncherSettings.values(QString("Profiles/") + currentProfile + QString("/content"), Qt::MatchExactly); + return true; +} + +void Launcher::DataFilesPage::populateFileViews(const QString& contentModelName) +{ + QStringList paths = mGameSettings.getDataDirs(); + + foreach(const QString &path, paths) + mSelector->addFiles(path); + + mDataLocal = mGameSettings.getDataLocal(); + + if (!mDataLocal.isEmpty()) + mSelector->addFiles(mDataLocal); + + paths.insert(0, mDataLocal); + PathIterator pathIterator(paths); + + mSelector->setProfileContent(filesInProfile(contentModelName, pathIterator)); +} + +QStringList Launcher::DataFilesPage::filesInProfile(const QString& profileName, PathIterator& pathIterator) +{ + QStringList files = mLauncherSettings.getContentListFiles(profileName); QStringList filepaths; - foreach (const QString &file, files) + foreach(const QString& file, files) { - QString filepath = pathIterator.findFirstPath (file); + QString filepath = pathIterator.findFirstPath(file); if (!filepath.isEmpty()) filepaths << filepath; } - mSelector->setProfileContent (filepaths); - - return true; + return filepaths; } void Launcher::DataFilesPage::saveSettings(const QString &profile) @@ -120,24 +131,20 @@ void Launcher::DataFilesPage::saveSettings(const QString &profile) //retrieve the files selected for the profile ContentSelectorModel::ContentFileList items = mSelector->selectedFiles(); - removeProfile (profileName); - - mGameSettings.remove(QString("content")); - //set the value of the current profile (not necessarily the profile being saved!) - mLauncherSettings.setValue(QString("Profiles/currentprofile"), ui.profilesComboBox->currentText()); + mLauncherSettings.setCurrentContentListName(ui.profilesComboBox->currentText()); + QStringList fileNames; foreach(const ContentSelectorModel::EsmFile *item, items) { - mLauncherSettings.setMultiValue(QString("Profiles/") + profileName + QString("/content"), item->fileName()); - mGameSettings.setMultiValue(QString("content"), item->fileName()); + fileNames.append(item->fileName()); } - + mLauncherSettings.setContentList(profileName, fileNames); + mGameSettings.setContentList(fileNames); } void Launcher::DataFilesPage::removeProfile(const QString &profile) { - mLauncherSettings.remove(QString("Profiles/") + profile); - mLauncherSettings.remove(QString("Profiles/") + profile + QString("/content")); + mLauncherSettings.removeContentList(profile); } QAbstractItemModel *Launcher::DataFilesPage::profilesModel() const @@ -154,9 +161,11 @@ void Launcher::DataFilesPage::setProfile(int index, bool savePrevious) { if (index >= -1 && index < ui.profilesComboBox->count()) { - QString previous = ui.profilesComboBox->itemText(ui.profilesComboBox->currentIndex()); + QString previous = mPreviousProfile; QString current = ui.profilesComboBox->itemText(index); + mPreviousProfile = current; + setProfile (previous, current, savePrevious); } } @@ -167,15 +176,12 @@ void Launcher::DataFilesPage::setProfile (const QString &previous, const QString if (previous == current) return; - if (previous.isEmpty()) - return; - if (!previous.isEmpty() && savePrevious) saveSettings (previous); ui.profilesComboBox->setCurrentProfile (ui.profilesComboBox->findText (current)); - loadSettings(); + populateFileViews(current); checkForDefaultProfile(); } @@ -207,12 +213,16 @@ void Launcher::DataFilesPage::slotProfileRenamed(const QString &previous, const void Launcher::DataFilesPage::slotProfileChanged(int index) { + // in case the event was triggered externally + if (ui.profilesComboBox->currentIndex() != index) + ui.profilesComboBox->setCurrentIndex(index); + setProfile (index, true); } void Launcher::DataFilesPage::on_newProfileAction_triggered() { - if (!mProfileDialog->exec() == QDialog::Accepted) + if (mProfileDialog->exec() != QDialog::Accepted) return; QString profile = mProfileDialog->lineEdit()->text(); @@ -222,15 +232,9 @@ void Launcher::DataFilesPage::on_newProfileAction_triggered() saveSettings(); - mSelector->clearCheckStates(); + mLauncherSettings.setCurrentContentListName(profile); addProfile(profile, true); - - mSelector->setGameFile(); - - saveSettings(); - - emit signalProfileChanged (ui.profilesComboBox->findText(profile)); } void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurrent) @@ -238,10 +242,8 @@ void Launcher::DataFilesPage::addProfile (const QString &profile, bool setAsCurr if (profile.isEmpty()) return; - if (ui.profilesComboBox->findText (profile) != -1) - return; - - ui.profilesComboBox->addItem (profile); + if (ui.profilesComboBox->findText (profile) == -1) + ui.profilesComboBox->addItem (profile); if (setAsCurrent) setProfile (ui.profilesComboBox->findText (profile), false); @@ -257,14 +259,14 @@ void Launcher::DataFilesPage::on_deleteProfileAction_triggered() if (!showDeleteMessageBox (profile)) return; - // Remove the profile from the combobox - ui.profilesComboBox->removeItem (ui.profilesComboBox->findText (profile)); + // this should work since the Default profile can't be deleted and is always index 0 + int next = ui.profilesComboBox->currentIndex()-1; + + // changing the profile forces a reload of plugin file views. + ui.profilesComboBox->setCurrentIndex(next); removeProfile(profile); - - saveSettings(); - - loadSettings(); + ui.profilesComboBox->removeItem(ui.profilesComboBox->findText(profile)); checkForDefaultProfile(); } @@ -285,7 +287,7 @@ void Launcher::DataFilesPage::updateOkButton(const QString &text) void Launcher::DataFilesPage::checkForDefaultProfile() { //don't allow deleting "Default" profile - bool success = (ui.profilesComboBox->currentText() != "Default"); + bool success = (ui.profilesComboBox->currentText() != mDefaultContentListName); ui.deleteProfileAction->setEnabled (success); ui.profilesComboBox->setEditEnabled (success); @@ -294,10 +296,10 @@ void Launcher::DataFilesPage::checkForDefaultProfile() bool Launcher::DataFilesPage::showDeleteMessageBox (const QString &text) { QMessageBox msgBox(this); - msgBox.setWindowTitle(tr("Delete Profile")); + msgBox.setWindowTitle(tr("Delete Content List")); msgBox.setIcon(QMessageBox::Warning); msgBox.setStandardButtons(QMessageBox::Cancel); - msgBox.setText(tr("Are you sure you want to delete %0?").arg(text)); + msgBox.setText(tr("Are you sure you want to delete %1?").arg(text)); QAbstractButton *deleteButton = msgBox.addButton(tr("Delete"), QMessageBox::ActionRole); diff --git a/apps/launcher/datafilespage.hpp b/apps/launcher/datafilespage.hpp index 5beeb0e03a..d25d20fc9e 100644 --- a/apps/launcher/datafilespage.hpp +++ b/apps/launcher/datafilespage.hpp @@ -58,6 +58,10 @@ namespace Launcher void on_newProfileAction_triggered(); void on_deleteProfileAction_triggered(); + public: + /// Content List that is always present + const static char *mDefaultContentListName; + private: TextInputDialog *mProfileDialog; @@ -67,6 +71,8 @@ namespace Launcher Config::GameSettings &mGameSettings; Config::LauncherSettings &mLauncherSettings; + QString mPreviousProfile; + QString mDataLocal; void setPluginsCheckstates(Qt::CheckState state); @@ -80,6 +86,7 @@ namespace Launcher bool showDeleteMessageBox (const QString &text); void addProfile (const QString &profile, bool setAsCurrent); void checkForDefaultProfile(); + void populateFileViews(const QString& contentModelName); class PathIterator { @@ -132,6 +139,8 @@ namespace Launcher } }; + + QStringList filesInProfile(const QString& profileName, PathIterator& pathIterator); }; } #endif diff --git a/apps/launcher/graphicspage.cpp b/apps/launcher/graphicspage.cpp index ec7f5a04d7..cdb51348c8 100644 --- a/apps/launcher/graphicspage.cpp +++ b/apps/launcher/graphicspage.cpp @@ -10,7 +10,10 @@ #define MAC_OS_X_VERSION_MIN_REQUIRED __ENVIRONMENT_MAC_OS_X_VERSION_MIN_REQUIRED__ #endif // MAC_OS_X_VERSION_MIN_REQUIRED -#include +#include + +#include +#include #include @@ -64,7 +67,7 @@ bool Launcher::GraphicsPage::setupOgre() } catch(Ogre::Exception &ex) { - QString ogreError = QString::fromStdString(ex.getFullDescription().c_str()); + QString ogreError = QString::fromUtf8(ex.getFullDescription().c_str()); QMessageBox msgBox; msgBox.setWindowTitle("Error creating Ogre::Root"); msgBox.setIcon(QMessageBox::Critical); @@ -132,7 +135,7 @@ bool Launcher::GraphicsPage::setupSDL() msgBox.setWindowTitle(tr("Error receiving number of screens")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
SDL_GetNumDisplayModes failed:

") + QString::fromStdString(SDL_GetError()) + "
"); + msgBox.setText(tr("
SDL_GetNumDisplayModes failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return false; } @@ -159,6 +162,9 @@ bool Launcher::GraphicsPage::loadSettings() if (mGraphicsSettings.value(QString("Video/fullscreen")) == QLatin1String("true")) fullScreenCheckBox->setCheckState(Qt::Checked); + if (mGraphicsSettings.value(QString("Video/window border")) == QLatin1String("true")) + windowBorderCheckBox->setCheckState(Qt::Checked); + int aaIndex = antiAliasingComboBox->findText(mGraphicsSettings.value(QString("Video/antialiasing"))); if (aaIndex != -1) antiAliasingComboBox->setCurrentIndex(aaIndex); @@ -193,6 +199,9 @@ void Launcher::GraphicsPage::saveSettings() fullScreenCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("true")) : mGraphicsSettings.setValue(QString("Video/fullscreen"), QString("false")); + windowBorderCheckBox->checkState() ? mGraphicsSettings.setValue(QString("Video/window border"), QString("true")) + : mGraphicsSettings.setValue(QString("Video/window border"), QString("false")); + mGraphicsSettings.setValue(QString("Video/antialiasing"), antiAliasingComboBox->currentText()); mGraphicsSettings.setValue(QString("Video/render system"), rendererComboBox->currentText()); @@ -228,7 +237,7 @@ QStringList Launcher::GraphicsPage::getAvailableOptions(const QString &key, Ogre opt_it != i->second.possibleValues.end(); ++opt_it, ++idx) { if (strcmp (key.toStdString().c_str(), i->first.c_str()) == 0) { - result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromStdString((*opt_it).c_str()).simplified(); + result << ((key == "FSAA") ? QString("MSAA ") : QString("")) + QString::fromUtf8((*opt_it).c_str()).simplified(); } } } @@ -257,7 +266,7 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) msgBox.setWindowTitle(tr("Error receiving resolutions")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
SDL_GetNumDisplayModes failed:

") + QString::fromStdString(SDL_GetError()) + "
"); + msgBox.setText(tr("
SDL_GetNumDisplayModes failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return result; } @@ -270,7 +279,7 @@ QStringList Launcher::GraphicsPage::getAvailableResolutions(int screen) msgBox.setWindowTitle(tr("Error receiving resolutions")); msgBox.setIcon(QMessageBox::Critical); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("
SDL_GetDisplayMode failed:

") + QString::fromStdString(SDL_GetError()) + "
"); + msgBox.setText(tr("
SDL_GetDisplayMode failed:

") + QString::fromUtf8(SDL_GetError()) + "
"); msgBox.exec(); return result; } @@ -331,10 +340,12 @@ void Launcher::GraphicsPage::slotFullScreenChanged(int state) customRadioButton->setEnabled(false); customWidthSpinBox->setEnabled(false); customHeightSpinBox->setEnabled(false); + windowBorderCheckBox->setEnabled(false); } else { customRadioButton->setEnabled(true); customWidthSpinBox->setEnabled(true); customHeightSpinBox->setEnabled(true); + windowBorderCheckBox->setEnabled(true); } } diff --git a/apps/launcher/graphicspage.hpp b/apps/launcher/graphicspage.hpp index da4cb9fb33..213b6bccb4 100644 --- a/apps/launcher/graphicspage.hpp +++ b/apps/launcher/graphicspage.hpp @@ -3,14 +3,11 @@ #include -#include -#include - #include - #include "ui_graphicspage.h" +namespace Ogre { class Root; class RenderSystem; } namespace Files { struct ConfigurationManager; } diff --git a/apps/launcher/main.cpp b/apps/launcher/main.cpp index 562f5c7795..11ea568692 100644 --- a/apps/launcher/main.cpp +++ b/apps/launcher/main.cpp @@ -1,3 +1,6 @@ +#include +#include + #include #include #include @@ -15,53 +18,63 @@ int main(int argc, char *argv[]) { - SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); - SDL_SetMainReady(); - if (SDL_Init(SDL_INIT_VIDEO) != 0) + try { - qDebug() << "SDL_Init failed: " << QString::fromStdString(SDL_GetError()); + SDL_SetHint(SDL_HINT_RENDER_DRIVER, "software"); + SDL_SetMainReady(); + if (SDL_Init(SDL_INIT_VIDEO) != 0) + { + qDebug() << "SDL_Init failed: " << QString::fromUtf8(SDL_GetError()); + return 0; + } + signal(SIGINT, SIG_DFL); // We don't want to use the SDL event loop in the launcher, + // so reset SIGINT which SDL wants to redirect to an SDL_Quit event. + + QApplication app(argc, argv); + + // Now we make sure the current dir is set to application path + QDir dir(QCoreApplication::applicationDirPath()); + + #ifdef Q_OS_MAC + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + app.setLibraryPaths(libraryPaths); + #endif + + QDir::setCurrent(dir.absolutePath()); + + // Support non-latin characters + QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); + + Launcher::MainDialog mainWin; + + if (!mainWin.showFirstRunDialog()) + return 0; + + // if (!mainWin.setup()) { + // return 0; + // } + + mainWin.show(); + + int returnValue = app.exec(); + SDL_Quit(); + return returnValue; + } + catch (std::exception& e) + { + std::cerr << "ERROR: " << e.what() << std::endl; return 0; } - - QApplication app(argc, argv); - - // Now we make sure the current dir is set to application path - QDir dir(QCoreApplication::applicationDirPath()); - - #ifdef Q_OS_MAC - if (dir.dirName() == "MacOS") { - dir.cdUp(); - dir.cdUp(); - dir.cdUp(); - } - - // force Qt to load only LOCAL plugins, don't touch system Qt installation - QDir pluginsPath(QCoreApplication::applicationDirPath()); - pluginsPath.cdUp(); - pluginsPath.cd("Plugins"); - - QStringList libraryPaths; - libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - app.setLibraryPaths(libraryPaths); - #endif - - QDir::setCurrent(dir.absolutePath()); - - // Support non-latin characters - QTextCodec::setCodecForCStrings(QTextCodec::codecForName("UTF-8")); - - Launcher::MainDialog mainWin; - - if (!mainWin.showFirstRunDialog()) - return 0; - -// if (!mainWin.setup()) { -// return 0; -// } - - mainWin.show(); - - int returnValue = app.exec(); - SDL_Quit(); - return returnValue; } diff --git a/apps/launcher/maindialog.cpp b/apps/launcher/maindialog.cpp index 975958d7ac..b93d55c176 100644 --- a/apps/launcher/maindialog.cpp +++ b/apps/launcher/maindialog.cpp @@ -27,25 +27,6 @@ using namespace Process; Launcher::MainDialog::MainDialog(QWidget *parent) : mGameSettings(mCfgMgr), QMainWindow (parent) { - // Install the stylesheet font - QFile file; - QFontDatabase fontDatabase; - - const QStringList fonts = fontDatabase.families(); - - // Check if the font is installed - if (!fonts.contains("EB Garamond")) { - - QString font = QString::fromUtf8(mCfgMgr.getGlobalDataPath().string().c_str()) + QString("resources/mygui/EBGaramond-Regular.ttf"); - file.setFileName(font); - - if (!file.exists()) { - font = QString::fromUtf8(mCfgMgr.getLocalPath().string().c_str()) + QString("resources/mygui/EBGaramond-Regular.ttf"); - } - - fontDatabase.addApplicationFont(font); - } - setupUi(this); mGameInvoker = new ProcessInvoker(); @@ -209,6 +190,8 @@ bool Launcher::MainDialog::setup() if (!setupGameSettings()) return false; + mLauncherSettings.setContentList(mGameSettings); + if (!setupGraphicsSettings()) return false; @@ -232,6 +215,8 @@ bool Launcher::MainDialog::reloadSettings() if (!setupGameSettings()) return false; + mLauncherSettings.setContentList(mGameSettings); + if (!setupGraphicsSettings()) return false; @@ -280,8 +265,8 @@ bool Launcher::MainDialog::setupLauncherSettings() QString userPath = QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str()); QStringList paths; - paths.append(QString("launcher.cfg")); - paths.append(userPath + QString("launcher.cfg")); + paths.append(QString(Config::LauncherSettings::sLauncherConfigFileName)); + paths.append(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); foreach (const QString &path, paths) { qDebug() << "Loading config file:" << qPrintable(path); @@ -562,7 +547,7 @@ bool Launcher::MainDialog::writeSettings() file.close(); // Launcher settings - file.setFileName(userPath + QString("launcher.cfg")); + file.setFileName(userPath + QString(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created diff --git a/apps/launcher/settingspage.cpp b/apps/launcher/settingspage.cpp index 5422e79574..c172a31210 100644 --- a/apps/launcher/settingspage.cpp +++ b/apps/launcher/settingspage.cpp @@ -11,6 +11,7 @@ #include #include "utils/textinputdialog.hpp" +#include "datafilespage.hpp" using namespace Process; @@ -51,7 +52,7 @@ Launcher::SettingsPage::SettingsPage(Files::ConfigurationManager &cfg, connect(mImporterInvoker->getProcess(), SIGNAL(finished(int,QProcess::ExitStatus)), this, SLOT(importerFinished(int,QProcess::ExitStatus))); - mProfileDialog = new TextInputDialog(tr("New Profile"), tr("Profile name:"), this); + mProfileDialog = new TextInputDialog(tr("New Content List"), tr("Content List name:"), this); connect(mProfileDialog->lineEdit(), SIGNAL(textChanged(QString)), this, SLOT(updateOkButton(QString))); @@ -140,7 +141,7 @@ void Launcher::SettingsPage::on_importerButton_clicked() qDebug() << "arguments " << arguments; - if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) + if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) return; } @@ -198,31 +199,33 @@ void Launcher::SettingsPage::importerFinished(int exitCode, QProcess::ExitStatus if (exitCode != 0 || exitStatus == QProcess::CrashExit) return; - // Re-read the settings in their current state + // Importer may have changed settings, so refresh mMain->reloadSettings(); // Import selected data files from openmw.cfg if (addonsCheckBox->isChecked()) { + // Because we've reloaded settings, the current content list matches content in OpenMW.cfg + QString oldContentListName = mLauncherSettings.getCurrentContentListName(); if (mProfileDialog->exec() == QDialog::Accepted) { - const QString profile(mProfileDialog->lineEdit()->text()); - const QStringList files(mGameSettings.values(QLatin1String("content"))); - - qDebug() << "Profile " << profile << files; - - // Doesn't quite work right now - mLauncherSettings.setValue(QLatin1String("Profiles/currentprofile"), profile); - - foreach (const QString &file, files) { - mLauncherSettings.setMultiValue(QLatin1String("Profiles/") + profile + QLatin1String("/content"), file); + // remove the current content list to prevent duplication + //... except, not allowed to delete the Default content list + if (oldContentListName.compare(DataFilesPage::mDefaultContentListName) != 0) + { + mLauncherSettings.removeContentList(oldContentListName); } - mGameSettings.remove(QLatin1String("content")); + const QString newContentListName(mProfileDialog->lineEdit()->text()); + const QStringList files(mGameSettings.getContentList()); + mLauncherSettings.setCurrentContentListName(newContentListName); + mLauncherSettings.setContentList(newContentListName, files); + + // Make DataFiles Page load the new content list. + mMain->reloadSettings(); } } - mMain->reloadSettings(); importerButton->setEnabled(true); } @@ -234,7 +237,7 @@ void Launcher::SettingsPage::updateOkButton(const QString &text) return; } - const QStringList profiles(mLauncherSettings.subKeys(QString("Profiles/"))); + const QStringList profiles(mLauncherSettings.getContentLists()); (profiles.contains(text)) ? mProfileDialog->setOkButtonEnabled(false) diff --git a/apps/launcher/utils/profilescombobox.cpp b/apps/launcher/utils/profilescombobox.cpp index c143307249..7df89098e2 100644 --- a/apps/launcher/utils/profilescombobox.cpp +++ b/apps/launcher/utils/profilescombobox.cpp @@ -47,13 +47,13 @@ void ProfilesComboBox::setEditEnabled(bool editable) void ProfilesComboBox::slotTextChanged(const QString &text) { - QPalette *palette = new QPalette(); - palette->setColor(QPalette::Text,Qt::red); + QPalette palette; + palette.setColor(QPalette::Text,Qt::red); int index = findText(text); if (text.isEmpty() || (index != -1 && index != currentIndex())) { - lineEdit()->setPalette(*palette); + lineEdit()->setPalette(palette); } else { lineEdit()->setPalette(QApplication::palette()); } diff --git a/apps/launcher/utils/textinputdialog.cpp b/apps/launcher/utils/textinputdialog.cpp index b47fdb6e62..385d086fd0 100644 --- a/apps/launcher/utils/textinputdialog.cpp +++ b/apps/launcher/utils/textinputdialog.cpp @@ -59,13 +59,13 @@ void Launcher::TextInputDialog::setOkButtonEnabled(bool enabled) QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setEnabled(enabled); - QPalette *palette = new QPalette(); - palette->setColor(QPalette::Text, Qt::red); + QPalette palette; + palette.setColor(QPalette::Text, Qt::red); if (enabled) { mLineEdit->setPalette(QApplication::palette()); } else { // Existing profile name, make the text red - mLineEdit->setPalette(*palette); + mLineEdit->setPalette(palette); } } diff --git a/apps/mwiniimporter/CMakeLists.txt b/apps/mwiniimporter/CMakeLists.txt index deab88ce28..790d47dc47 100644 --- a/apps/mwiniimporter/CMakeLists.txt +++ b/apps/mwiniimporter/CMakeLists.txt @@ -9,16 +9,16 @@ set(MWINIIMPORT_HEADER source_group(launcher FILES ${MWINIIMPORT} ${MWINIIMPORT_HEADER}) -add_executable(mwiniimport +add_executable(openmw-iniimporter ${MWINIIMPORT} ) -target_link_libraries(mwiniimport +target_link_libraries(openmw-iniimporter ${Boost_LIBRARIES} components ) if (BUILD_WITH_CODE_COVERAGE) add_definitions (--coverage) - target_link_libraries(mwiniimport gcov) + target_link_libraries(openmw-iniimporter gcov) endif() diff --git a/apps/mwiniimporter/main.cpp b/apps/mwiniimporter/main.cpp index fdf6db804c..f108678f3f 100644 --- a/apps/mwiniimporter/main.cpp +++ b/apps/mwiniimporter/main.cpp @@ -56,93 +56,87 @@ int wmain(int argc, wchar_t *wargv[]) { char **argv = converter.get(); boost::filesystem::path::imbue(boost::locale::generator().generate("")); #endif - bpo::options_description desc("Syntax: mwiniimporter inifile configfile\nAllowed options"); - bpo::positional_options_description p_desc; - desc.add_options() - ("help,h", "produce help message") - ("verbose,v", "verbose output") - ("ini,i", bpo::value(), "morrowind.ini file") - ("cfg,c", bpo::value(), "openmw.cfg file") - ("output,o", bpo::value()->default_value(""), "openmw.cfg file") - ("game-files,g", "import esm and esp files") - ("no-archives,A", "disable bsa archives import") - ("encoding,e", bpo::value()-> default_value("win1252"), - "Character encoding used in OpenMW game messages:\n" - "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" - "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" - "\n\twin1252 - Western European (Latin) alphabet, used by default") - ; - p_desc.add("ini", 1).add("cfg", 1); - bpo::variables_map vm; - try { + bpo::options_description desc("Syntax: openmw-iniimporter inifile configfile\nAllowed options"); + bpo::positional_options_description p_desc; + desc.add_options() + ("help,h", "produce help message") + ("verbose,v", "verbose output") + ("ini,i", bpo::value(), "morrowind.ini file") + ("cfg,c", bpo::value(), "openmw.cfg file") + ("output,o", bpo::value()->default_value(""), "openmw.cfg file") + ("game-files,g", "import esm and esp files") + ("no-archives,A", "disable bsa archives import") + ("encoding,e", bpo::value()-> default_value("win1252"), + "Character encoding used in OpenMW game messages:\n" + "\n\twin1250 - Central and Eastern European such as Polish, Czech, Slovak, Hungarian, Slovene, Bosnian, Croatian, Serbian (Latin script), Romanian and Albanian languages\n" + "\n\twin1251 - Cyrillic alphabet such as Russian, Bulgarian, Serbian Cyrillic and other languages\n" + "\n\twin1252 - Western European (Latin) alphabet, used by default") + ; + p_desc.add("ini", 1).add("cfg", 1); + + bpo::variables_map vm; + bpo::parsed_options parsed = bpo::command_line_parser(argc, argv) .options(desc) .positional(p_desc) .run(); bpo::store(parsed, vm); + + if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) { + std::cout << desc; + return 0; + } + + bpo::notify(vm); + + std::string iniFile = vm["ini"].as(); + std::string cfgFile = vm["cfg"].as(); + + // if no output is given, write back to cfg file + std::string outputFile(vm["output"].as()); + if(vm["output"].defaulted()) { + outputFile = vm["cfg"].as(); + } + + if(!boost::filesystem::exists(iniFile)) { + std::cerr << "ini file does not exist" << std::endl; + return -3; + } + if(!boost::filesystem::exists(cfgFile)) + std::cerr << "cfg file does not exist" << std::endl; + + MwIniImporter importer; + importer.setVerbose(vm.count("verbose")); + + // Font encoding settings + std::string encoding(vm["encoding"].as()); + importer.setInputEncoding(ToUTF8::calculateEncoding(encoding)); + + MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); + MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); + + importer.merge(cfg, ini); + importer.mergeFallback(cfg, ini); + + if(vm.count("game-files")) { + importer.importGameFiles(cfg, ini); + } + + if(!vm.count("no-archives")) { + importer.importArchives(cfg, ini); + } + + std::cout << "write to: " << outputFile << std::endl; + bfs::ofstream file((bfs::path(outputFile))); + importer.writeToFile(file, cfg); } - catch(boost::program_options::unknown_option & x) + catch (std::exception& e) { - std::cerr << "ERROR: " << x.what() << std::endl; - return false; + std::cerr << "ERROR: " << e.what() << std::endl; } - catch(boost::program_options::invalid_command_line_syntax & x) - { - std::cerr << "ERROR: " << x.what() << std::endl; - return false; - } - - if(vm.count("help") || !vm.count("ini") || !vm.count("cfg")) { - std::cout << desc; - return 0; - } - - bpo::notify(vm); - - std::string iniFile = vm["ini"].as(); - std::string cfgFile = vm["cfg"].as(); - - // if no output is given, write back to cfg file - std::string outputFile(vm["output"].as()); - if(vm["output"].defaulted()) { - outputFile = vm["cfg"].as(); - } - - if(!boost::filesystem::exists(iniFile)) { - std::cerr << "ini file does not exist" << std::endl; - return -3; - } - if(!boost::filesystem::exists(cfgFile)) - std::cerr << "cfg file does not exist" << std::endl; - - MwIniImporter importer; - importer.setVerbose(vm.count("verbose")); - - // Font encoding settings - std::string encoding(vm["encoding"].as()); - importer.setInputEncoding(ToUTF8::calculateEncoding(encoding)); - - MwIniImporter::multistrmap ini = importer.loadIniFile(iniFile); - MwIniImporter::multistrmap cfg = importer.loadCfgFile(cfgFile); - - importer.merge(cfg, ini); - importer.mergeFallback(cfg, ini); - - if(vm.count("game-files")) { - importer.importGameFiles(cfg, ini); - } - - if(!vm.count("no-archives")) { - importer.importArchives(cfg, ini); - } - - std::cout << "write to: " << outputFile << std::endl; - bfs::ofstream file((bfs::path(outputFile))); - importer.writeToFile(file, cfg); - return 0; } diff --git a/apps/opencs/CMakeLists.txt b/apps/opencs/CMakeLists.txt index d22309007c..c46f4e25d1 100644 --- a/apps/opencs/CMakeLists.txt +++ b/apps/opencs/CMakeLists.txt @@ -4,8 +4,6 @@ set (OPENCS_SRC main.cpp opencs_units (. editor) -set (CMAKE_BUILD_TYPE DEBUG) - opencs_units (model/doc document operation saving documentmanager loader runner ) @@ -41,7 +39,7 @@ opencs_units (model/tools opencs_units_noqt (model/tools mandatoryid skillcheck classcheck factioncheck racecheck soundcheck regioncheck - birthsigncheck spellcheck referenceablecheck scriptcheck bodypartcheck + birthsigncheck spellcheck referencecheck referenceablecheck scriptcheck bodypartcheck ) @@ -147,7 +145,7 @@ set (OPENCS_UI ${CMAKE_SOURCE_DIR}/files/ui/filedialog.ui ) -source_group (opencs FILES ${OPENCS_SRC} ${OPENCS_HDR}) +source_group (openmw-cs FILES ${OPENCS_SRC} ${OPENCS_HDR}) if(WIN32) set(QT_USE_QTMAIN TRUE) @@ -170,12 +168,12 @@ qt4_add_resources(OPENCS_RES_SRC ${OPENCS_RES}) include_directories(${CMAKE_CURRENT_BINARY_DIR} ${BULLET_INCLUDE_DIRS}) if(APPLE) - set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/opencs.icns) + set (OPENCS_MAC_ICON ${CMAKE_SOURCE_DIR}/files/mac/openmw-cs.icns) else() set (OPENCS_MAC_ICON "") endif(APPLE) -add_executable(opencs +add_executable(openmw-cs MACOSX_BUNDLE ${OENGINE_BULLET} ${OPENCS_SRC} @@ -186,10 +184,10 @@ add_executable(opencs ) if(APPLE) - set_target_properties(opencs PROPERTIES + set_target_properties(openmw-cs PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${OpenMW_BINARY_DIR}" - OUTPUT_NAME "OpenCS" - MACOSX_BUNDLE_ICON_FILE "opencs.icns" + OUTPUT_NAME "OpenMW-CS" + MACOSX_BUNDLE_ICON_FILE "openmw-cs.icns" MACOSX_BUNDLE_BUNDLE_NAME "OpenCS" MACOSX_BUNDLE_GUI_IDENTIFIER "org.openmw.opencs" MACOSX_BUNDLE_SHORT_VERSION_STRING ${OPENMW_VERSION} @@ -200,7 +198,7 @@ if(APPLE) MACOSX_PACKAGE_LOCATION Resources) endif(APPLE) -target_link_libraries(opencs +target_link_libraries(openmw-cs ${OGRE_LIBRARIES} ${OGRE_Overlay_LIBRARIES} ${OGRE_STATIC_PLUGINS} @@ -212,5 +210,5 @@ target_link_libraries(opencs ) if(APPLE) - INSTALL(TARGETS opencs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) + INSTALL(TARGETS openmw-cs BUNDLE DESTINATION OpenMW COMPONENT BUNDLE) endif() diff --git a/apps/opencs/editor.cpp b/apps/opencs/editor.cpp index 9849608c4c..b761547103 100644 --- a/apps/opencs/editor.cpp +++ b/apps/opencs/editor.cpp @@ -15,7 +15,7 @@ #include #include - +#include #include #include "model/doc/document.hpp" @@ -35,6 +35,8 @@ CS::Editor::Editor (OgreInit::OgreInit& ogreInit) ogreInit.init ((mCfgMgr.getUserConfigPath() / "opencsOgre.log").string()); + NifOgre::Loader::setShowMarkers(true); + mOverlaySystem.reset (new CSVRender::OverlaySystem); Bsa::registerResources (Files::Collections (config.first, !mFsStrict), config.second, true, @@ -75,7 +77,8 @@ CS::Editor::~Editor () mPidFile.close(); if(mServer && boost::filesystem::exists(mPid)) - remove(mPid.string().c_str()); // ignore any error + static_cast ( // silence coverity warning + remove(mPid.string().c_str())); // ignore any error // cleanup global resources used by OEngine delete OEngine::Physic::BulletShapeManager::getSingletonPtr(); @@ -93,7 +96,7 @@ void CS::Editor::setupDataFiles (const Files::PathContainer& dataDirs) std::pair > CS::Editor::readConfig() { boost::program_options::variables_map variables; - boost::program_options::options_description desc("Syntax: opencs \nAllowed options"); + boost::program_options::options_description desc("Syntax: openmw-cs \nAllowed options"); desc.add_options() ("data", boost::program_options::value()->default_value(Files::PathContainer(), "data")->multitoken()->composing()) @@ -236,6 +239,7 @@ void CS::Editor::showSettings() if (mSettings.isHidden()) mSettings.show(); + mSettings.move (QCursor::pos()); mSettings.raise(); mSettings.activateWindow(); } @@ -245,7 +249,7 @@ bool CS::Editor::makeIPCServer() try { mPid = boost::filesystem::temp_directory_path(); - mPid /= "opencs.pid"; + mPid /= "openmw-cs.pid"; bool pidExists = boost::filesystem::exists(mPid); mPidFile.open(mPid); diff --git a/apps/opencs/main.cpp b/apps/opencs/main.cpp index dd20324d12..b11561c135 100644 --- a/apps/opencs/main.cpp +++ b/apps/opencs/main.cpp @@ -46,47 +46,55 @@ class Application : public QApplication int main(int argc, char *argv[]) { - Q_INIT_RESOURCE (resources); - - qRegisterMetaType ("std::string"); - qRegisterMetaType ("CSMWorld::UniversalId"); - - OgreInit::OgreInit ogreInit; - - std::auto_ptr shinyFactory; - - Application application (argc, argv); - -#ifdef Q_OS_MAC - QDir dir(QCoreApplication::applicationDirPath()); - if (dir.dirName() == "MacOS") { - dir.cdUp(); - dir.cdUp(); - dir.cdUp(); - } - QDir::setCurrent(dir.absolutePath()); - - // force Qt to load only LOCAL plugins, don't touch system Qt installation - QDir pluginsPath(QCoreApplication::applicationDirPath()); - pluginsPath.cdUp(); - pluginsPath.cd("Plugins"); - - QStringList libraryPaths; - libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); - application.setLibraryPaths(libraryPaths); -#endif - - application.setWindowIcon (QIcon (":./opencs.png")); - - CS::Editor editor (ogreInit); - - if(!editor.makeIPCServer()) + try { - editor.connectToIPCServer(); + Q_INIT_RESOURCE (resources); + + qRegisterMetaType ("std::string"); + qRegisterMetaType ("CSMWorld::UniversalId"); + + OgreInit::OgreInit ogreInit; + + std::auto_ptr shinyFactory; + + Application application (argc, argv); + + #ifdef Q_OS_MAC + QDir dir(QCoreApplication::applicationDirPath()); + if (dir.dirName() == "MacOS") { + dir.cdUp(); + dir.cdUp(); + dir.cdUp(); + } + QDir::setCurrent(dir.absolutePath()); + + // force Qt to load only LOCAL plugins, don't touch system Qt installation + QDir pluginsPath(QCoreApplication::applicationDirPath()); + pluginsPath.cdUp(); + pluginsPath.cd("Plugins"); + + QStringList libraryPaths; + libraryPaths << pluginsPath.path() << QCoreApplication::applicationDirPath(); + application.setLibraryPaths(libraryPaths); + #endif + + application.setWindowIcon (QIcon (":./openmw-cs.png")); + + CS::Editor editor (ogreInit); + + if(!editor.makeIPCServer()) + { + editor.connectToIPCServer(); + return 0; + } + + shinyFactory = editor.setupGraphics(); + return editor.run(); + } + catch (std::exception& e) + { + std::cerr << "ERROR: " << e.what() << std::endl; return 0; } - shinyFactory = editor.setupGraphics(); - - return editor.run(); } diff --git a/apps/opencs/model/filter/valuenode.cpp b/apps/opencs/model/filter/valuenode.cpp index 66b6282d77..26b9824418 100644 --- a/apps/opencs/model/filter/valuenode.cpp +++ b/apps/opencs/model/filter/valuenode.cpp @@ -27,7 +27,7 @@ bool CSMFilter::ValueNode::test (const CSMWorld::IdTableBase& table, int row, QVariant data = table.data (index); if (data.type()!=QVariant::Double && data.type()!=QVariant::Bool && data.type()!=QVariant::Int && - data.type()!=QVariant::UInt && data.type()!=static_cast (QMetaType::Float)) + data.type()!=QVariant::UInt) return false; double value = data.toDouble(); diff --git a/apps/opencs/model/tools/referencecheck.cpp b/apps/opencs/model/tools/referencecheck.cpp new file mode 100644 index 0000000000..0c1d7d38f3 --- /dev/null +++ b/apps/opencs/model/tools/referencecheck.cpp @@ -0,0 +1,110 @@ +#include "referencecheck.hpp" + +#include + +CSMTools::ReferenceCheckStage::ReferenceCheckStage( + const CSMWorld::RefCollection& references, + const CSMWorld::RefIdCollection& referencables, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& factions) + : + mReferences(references), + mReferencables(referencables), + mDataSet(referencables.getDataSet()), + mCells(cells), + mFactions(factions) +{ +} + +void CSMTools::ReferenceCheckStage::perform(int stage, CSMDoc::Messages &messages) +{ + const CSMWorld::Record& record = mReferences.getRecord(stage); + + if (record.isDeleted()) + return; + + const CSMWorld::CellRef& cellRef = record.get(); + const CSMWorld::UniversalId id(CSMWorld::UniversalId::Type_Reference, cellRef.mId); + + // Check for empty reference id + if (cellRef.mRefID.empty()) { + messages.push_back(std::make_pair(id, " is an empty reference")); + } else { + // Check for non existing referenced object + if (mReferencables.searchId(cellRef.mRefID) == -1) { + messages.push_back(std::make_pair(id, " is referencing non existing object " + cellRef.mRefID)); + } else { + // Check if reference charge is valid for it's proper referenced type + CSMWorld::RefIdData::LocalIndex localIndex = mDataSet.searchId(cellRef.mRefID); + bool isLight = localIndex.second == CSMWorld::UniversalId::Type_Light; + if ((isLight && cellRef.mChargeFloat < -1) || (!isLight && cellRef.mChargeInt < -1)) { + std::string str = " has invalid charge "; + if (localIndex.second == CSMWorld::UniversalId::Type_Light) + str += boost::lexical_cast(cellRef.mChargeFloat); + else + str += boost::lexical_cast(cellRef.mChargeInt); + messages.push_back(std::make_pair(id, id.getId() + str)); + } + } + } + + // Check if referenced object is in valid cell + if (mCells.searchId(cellRef.mCell) == -1) + messages.push_back(std::make_pair(id, " is referencing object from non existing cell " + cellRef.mCell)); + + // If object have owner, check if that owner reference is valid + if (!cellRef.mOwner.empty() && mReferencables.searchId(cellRef.mOwner) == -1) + messages.push_back(std::make_pair(id, " has non existing owner " + cellRef.mOwner)); + + // If object have creature soul trapped, check if that creature reference is valid + if (!cellRef.mSoul.empty()) + if (mReferencables.searchId(cellRef.mSoul) == -1) + messages.push_back(std::make_pair(id, " has non existing trapped soul " + cellRef.mSoul)); + + bool hasFaction = !cellRef.mFaction.empty(); + + // If object have faction, check if that faction reference is valid + if (hasFaction) + if (mFactions.searchId(cellRef.mFaction) == -1) + messages.push_back(std::make_pair(id, " has non existing faction " + cellRef.mFaction)); + + // Check item's faction rank + if (hasFaction && cellRef.mFactionRank < -1) + messages.push_back(std::make_pair(id, " has faction set but has invalid faction rank " + cellRef.mFactionRank)); + else if (!hasFaction && cellRef.mFactionRank != -2) + messages.push_back(std::make_pair(id, " has invalid faction rank " + cellRef.mFactionRank)); + + // If door have destination cell, check if that reference is valid + if (!cellRef.mDestCell.empty()) + if (mCells.searchId(cellRef.mDestCell) == -1) + messages.push_back(std::make_pair(id, " has non existing destination cell " + cellRef.mDestCell)); + + // Check if scale isn't negative + if (cellRef.mScale < 0) + { + std::string str = " has negative scale "; + str += boost::lexical_cast(cellRef.mScale); + messages.push_back(std::make_pair(id, id.getId() + str)); + } + + // Check if enchantement points aren't negative or are at full (-1) + if (cellRef.mEnchantmentCharge < 0 && cellRef.mEnchantmentCharge != -1) + { + std::string str = " has negative enchantment points "; + str += boost::lexical_cast(cellRef.mEnchantmentCharge); + messages.push_back(std::make_pair(id, id.getId() + str)); + } + + // Check if gold value isn't negative + if (cellRef.mGoldValue < 0) + { + std::string str = " has negative gold value "; + str += cellRef.mGoldValue; + messages.push_back(std::make_pair(id, id.getId() + str)); + } +} + +int CSMTools::ReferenceCheckStage::setup() +{ + return mReferences.getSize(); +} diff --git a/apps/opencs/model/tools/referencecheck.hpp b/apps/opencs/model/tools/referencecheck.hpp new file mode 100644 index 0000000000..9cf685b3ad --- /dev/null +++ b/apps/opencs/model/tools/referencecheck.hpp @@ -0,0 +1,29 @@ +#ifndef CSM_TOOLS_REFERENCECHECK_H +#define CSM_TOOLS_REFERENCECHECK_H + +#include "../doc/state.hpp" +#include "../doc/document.hpp" + +namespace CSMTools +{ + class ReferenceCheckStage : public CSMDoc::Stage + { + public: + ReferenceCheckStage (const CSMWorld::RefCollection& references, + const CSMWorld::RefIdCollection& referencables, + const CSMWorld::IdCollection& cells, + const CSMWorld::IdCollection& factions); + + virtual void perform(int stage, CSMDoc::Messages& messages); + virtual int setup(); + + private: + const CSMWorld::RefCollection& mReferences; + const CSMWorld::RefIdCollection& mReferencables; + const CSMWorld::RefIdData& mDataSet; + const CSMWorld::IdCollection& mCells; + const CSMWorld::IdCollection& mFactions; + }; +} + +#endif // CSM_TOOLS_REFERENCECHECK_H \ No newline at end of file diff --git a/apps/opencs/model/tools/tools.cpp b/apps/opencs/model/tools/tools.cpp index 6e157f664f..0c20bd17b4 100644 --- a/apps/opencs/model/tools/tools.cpp +++ b/apps/opencs/model/tools/tools.cpp @@ -23,6 +23,7 @@ #include "referenceablecheck.hpp" #include "scriptcheck.hpp" #include "bodypartcheck.hpp" +#include "referencecheck.hpp" CSMDoc::Operation *CSMTools::Tools::get (int type) { @@ -82,6 +83,8 @@ CSMDoc::Operation *CSMTools::Tools::getVerifier() mVerifier->appendStage (new ReferenceableCheckStage (mData.getReferenceables().getDataSet(), mData.getRaces(), mData.getClasses(), mData.getFactions())); + mVerifier->appendStage (new ReferenceCheckStage(mData.getReferences(), mData.getReferenceables(), mData.getCells(), mData.getFactions())); + mVerifier->appendStage (new ScriptCheckStage (mDocument)); mVerifier->appendStage( diff --git a/apps/opencs/model/world/columnimp.hpp b/apps/opencs/model/world/columnimp.hpp index eba73cf0b0..da14bb4955 100644 --- a/apps/opencs/model/world/columnimp.hpp +++ b/apps/opencs/model/world/columnimp.hpp @@ -1053,13 +1053,13 @@ namespace CSMWorld virtual QVariant get (const Record& record) const { - return record.get().mCharge; + return record.get().mChargeInt; } virtual void set (Record& record, const QVariant& data) { ESXRecordT record2 = record.get(); - record2.mCharge = data.toInt(); + record2.mChargeInt = data.toInt(); record.setModified (record2); } diff --git a/apps/opencs/model/world/pathgrid.cpp b/apps/opencs/model/world/pathgrid.cpp index 1c82585df1..5c66e7d8ea 100644 --- a/apps/opencs/model/world/pathgrid.cpp +++ b/apps/opencs/model/world/pathgrid.cpp @@ -1,4 +1,5 @@ - +#include "cell.hpp" +#include "idcollection.hpp" #include "pathgrid.hpp" #include diff --git a/apps/opencs/model/world/pathgrid.hpp b/apps/opencs/model/world/pathgrid.hpp index 3c9fcff504..d8cc89f24f 100644 --- a/apps/opencs/model/world/pathgrid.hpp +++ b/apps/opencs/model/world/pathgrid.hpp @@ -6,11 +6,12 @@ #include -#include "idcollection.hpp" -#include "cell.hpp" - namespace CSMWorld { + struct Cell; + template + class IdCollection; + /// \brief Wrapper for Pathgrid record /// /// \attention The mData.mX and mData.mY fields of the ESM::Pathgrid struct are not used. diff --git a/apps/opencs/model/world/refidadapterimp.cpp b/apps/opencs/model/world/refidadapterimp.cpp index d9a691abdc..47caa8d71d 100644 --- a/apps/opencs/model/world/refidadapterimp.cpp +++ b/apps/opencs/model/world/refidadapterimp.cpp @@ -223,7 +223,14 @@ void CSMWorld::ContainerRefIdAdapter::setData (const RefIdColumn *column, RefIdD } CSMWorld::CreatureColumns::CreatureColumns (const ActorColumns& actorColumns) -: ActorColumns (actorColumns) +: ActorColumns (actorColumns), + mType(NULL), + mSoul(NULL), + mScale(NULL), + mOriginal(NULL), + mCombat(NULL), + mMagic(NULL), + mStealth(NULL) {} CSMWorld::CreatureRefIdAdapter::CreatureRefIdAdapter (const CreatureColumns& columns) @@ -431,7 +438,14 @@ void CSMWorld::MiscRefIdAdapter::setData (const RefIdColumn *column, RefIdData& InventoryRefIdAdapter::setData (column, data, index, value); } -CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) : ActorColumns (actorColumns) {} +CSMWorld::NpcColumns::NpcColumns (const ActorColumns& actorColumns) +: ActorColumns (actorColumns), + mRace(NULL), + mClass(NULL), + mFaction(NULL), + mHair(NULL), + mHead(NULL) +{} CSMWorld::NpcRefIdAdapter::NpcRefIdAdapter (const NpcColumns& columns) : ActorRefIdAdapter (UniversalId::Type_Npc, columns), mColumns (columns) @@ -587,4 +601,4 @@ void CSMWorld::WeaponRefIdAdapter::setData (const RefIdColumn *column, RefIdData else EnchantableRefIdAdapter::setData (column, data, index, value); } -} \ No newline at end of file +} diff --git a/apps/opencs/model/world/subcellcollection.hpp b/apps/opencs/model/world/subcellcollection.hpp index 28f0de6952..74bb6c9551 100644 --- a/apps/opencs/model/world/subcellcollection.hpp +++ b/apps/opencs/model/world/subcellcollection.hpp @@ -1,10 +1,17 @@ #ifndef CSM_WOLRD_SUBCOLLECTION_H #define CSM_WOLRD_SUBCOLLECTION_H -#include "idcollection.hpp" +namespace ESM +{ + class ESMReader; +} namespace CSMWorld { + struct Cell; + template + class IdCollection; + /// \brief Single type collection of top level records that are associated with cells template > class SubCellCollection : public IdCollection diff --git a/apps/opencs/model/world/universalid.cpp b/apps/opencs/model/world/universalid.cpp index 190ea87d8a..d19959d44d 100644 --- a/apps/opencs/model/world/universalid.cpp +++ b/apps/opencs/model/world/universalid.cpp @@ -131,6 +131,7 @@ namespace } CSMWorld::UniversalId::UniversalId (const std::string& universalId) +: mIndex(0) { std::string::size_type index = universalId.find (':'); diff --git a/apps/opencs/view/doc/filedialog.cpp b/apps/opencs/view/doc/filedialog.cpp index 300656f334..6a72f13ed2 100644 --- a/apps/opencs/view/doc/filedialog.cpp +++ b/apps/opencs/view/doc/filedialog.cpp @@ -18,7 +18,7 @@ #include "adjusterwidget.hpp" CSVDoc::FileDialog::FileDialog(QWidget *parent) : - QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0), mDialogBuilt(false) + QDialog(parent), mSelector (0), mFileWidget (0), mAdjusterWidget (0), mDialogBuilt(false), mAction(ContentAction_Undefined) { ui.setupUi (this); resize(400, 400); @@ -125,13 +125,17 @@ void CSVDoc::FileDialog::buildOpenFileView() if(!mDialogBuilt) { - connect (mSelector, SIGNAL (signalAddonFileSelected (int)), this, SLOT (slotUpdateAcceptButton (int))); - connect (mSelector, SIGNAL (signalAddonFileUnselected (int)), this, SLOT (slotUpdateAcceptButton (int))); + connect (mSelector, SIGNAL (signalAddonDataChanged (const QModelIndex&, const QModelIndex&)), this, SLOT (slotAddonDataChanged(const QModelIndex&, const QModelIndex&))); } connect (ui.projectButtonBox, SIGNAL (accepted()), this, SLOT (slotOpenFile())); } -void CSVDoc::FileDialog::slotUpdateAcceptButton (int) +void CSVDoc::FileDialog::slotAddonDataChanged(const QModelIndex &topleft, const QModelIndex &bottomright) +{ + slotUpdateAcceptButton(0); +} + +void CSVDoc::FileDialog::slotUpdateAcceptButton(int) { QString name = ""; diff --git a/apps/opencs/view/doc/filedialog.hpp b/apps/opencs/view/doc/filedialog.hpp index 111cc0d807..3c23a5cb5a 100644 --- a/apps/opencs/view/doc/filedialog.hpp +++ b/apps/opencs/view/doc/filedialog.hpp @@ -70,6 +70,7 @@ namespace CSVDoc void slotUpdateAcceptButton (int); void slotUpdateAcceptButton (const QString &, bool); void slotRejected(); + void slotAddonDataChanged(const QModelIndex& topleft, const QModelIndex& bottomright); }; } #endif // FILEDIALOG_HPP diff --git a/apps/opencs/view/doc/loader.cpp b/apps/opencs/view/doc/loader.cpp index ca7c93f9df..046eb5229f 100644 --- a/apps/opencs/view/doc/loader.cpp +++ b/apps/opencs/view/doc/loader.cpp @@ -18,7 +18,7 @@ void CSVDoc::LoadingDocument::closeEvent (QCloseEvent *event) } CSVDoc::LoadingDocument::LoadingDocument (CSMDoc::Document *document) -: mDocument (document), mAborted (false), mMessages (0) +: mDocument (document), mAborted (false), mMessages (0), mTotalRecords (0) { setWindowTitle (("Opening " + document->getSavePath().filename().string()).c_str()); @@ -104,7 +104,7 @@ void CSVDoc::LoadingDocument::nextRecord (int records) void CSVDoc::LoadingDocument::abort (const std::string& error) { mAborted = true; - mError->setText (QString::fromUtf8 (("Loading failed: " + error).c_str())); + mError->setText (QString::fromUtf8 (("Loading failed: " + error + "").c_str())); mButtons->setStandardButtons (QDialogButtonBox::Close); } @@ -199,4 +199,4 @@ void CSVDoc::Loader::loadMessage (CSMDoc::Document *document, const std::string& if (iter!=mDocuments.end()) iter->second->addMessage (message); -} \ No newline at end of file +} diff --git a/apps/opencs/view/doc/view.cpp b/apps/opencs/view/doc/view.cpp index 0d2b6060ef..9117a6d034 100644 --- a/apps/opencs/view/doc/view.cpp +++ b/apps/opencs/view/doc/view.cpp @@ -375,17 +375,20 @@ CSVDoc::View::View (ViewManager& viewManager, CSMDoc::Document *document, int to : mViewManager (viewManager), mDocument (document), mViewIndex (totalViews-1), mViewTotal (totalViews) { - QString width = CSMSettings::UserSettings::instance().settingValue - ("window/default-width"); + int width = CSMSettings::UserSettings::instance().settingValue + ("window/default-width").toInt(); - QString height = CSMSettings::UserSettings::instance().settingValue - ("window/default-height"); + int height = CSMSettings::UserSettings::instance().settingValue + ("window/default-height").toInt(); + + width = std::max(width, 300); + height = std::max(height, 300); // trick to get the window decorations and their sizes show(); hide(); - resize (width.toInt() - (frameGeometry().width() - geometry().width()), - height.toInt() - (frameGeometry().height() - geometry().height())); + resize (width - (frameGeometry().width() - geometry().width()), + height - (frameGeometry().height() - geometry().height())); mSubViewWindow.setDockOptions (QMainWindow::AllowNestedDocks); diff --git a/apps/opencs/view/render/cell.cpp b/apps/opencs/view/render/cell.cpp index 821920a473..8d97edf47e 100644 --- a/apps/opencs/view/render/cell.cpp +++ b/apps/opencs/view/render/cell.cpp @@ -120,7 +120,7 @@ bool CSVRender::Cell::addObjects (int start, int end) CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, const std::string& id, boost::shared_ptr physics, const Ogre::Vector3& origin) -: mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics), +: mData (data), mId (Misc::StringUtils::lowerCase (id)), mSceneMgr(sceneManager), mPhysics(physics), mX(0), mY(0), mPathgridId("") { mCellNode = sceneManager->getRootSceneNode()->createChildSceneNode(); @@ -137,15 +137,14 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, int landIndex = land.searchId(mId); if (landIndex != -1) { - mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, - Terrain::Align_XY)); - const ESM::Land* esmLand = land.getRecord(mId).get().mLand.get(); - mTerrain->loadCell(esmLand->mX, - esmLand->mY); - if(esmLand) { + mTerrain.reset(new Terrain::TerrainGrid(sceneManager, new TerrainStorage(mData), Element_Terrain, true, + Terrain::Align_XY)); + mTerrain->loadCell(esmLand->mX, + esmLand->mY); + float verts = ESM::Land::LAND_SIZE; float worldsize = ESM::Land::REAL_SIZE; mX = esmLand->mX; @@ -160,6 +159,7 @@ CSVRender::Cell::Cell (CSMWorld::Data& data, Ogre::SceneManager *sceneManager, CSVRender::Cell::~Cell() { +<<<<<<< .mine // destroy manual objects for(std::map, std::string>::iterator iter = mPgEdges.begin(); iter != mPgEdges.end(); ++iter) @@ -182,6 +182,30 @@ CSVRender::Cell::~Cell() } mPhysics->removeHeightField(mSceneMgr, mX, mY); +======= + if (mTerrain.get()) + mPhysics->removeHeightField(mSceneMgr, mX, mY); + + + + + + + + + + + + + + + + + + + + +>>>>>>> .theirs for (std::map::iterator iter (mObjects.begin()); iter!=mObjects.end(); ++iter) diff --git a/apps/opencs/view/render/cell.hpp b/apps/opencs/view/render/cell.hpp index 0ac8a8c3a3..e504fc7a48 100644 --- a/apps/opencs/view/render/cell.hpp +++ b/apps/opencs/view/render/cell.hpp @@ -9,8 +9,13 @@ #include +#ifndef Q_MOC_RUN #include +<<<<<<< .mine #include // FIXME: temporaty storage until saving to document +======= +#endif +>>>>>>> .theirs #include "object.hpp" diff --git a/apps/opencs/view/render/mousestate.cpp b/apps/opencs/view/render/mousestate.cpp index 5de09cf8a6..38dc41fbc4 100644 --- a/apps/opencs/view/render/mousestate.cpp +++ b/apps/opencs/view/render/mousestate.cpp @@ -422,7 +422,7 @@ namespace CSVRender //plane X, upvector Y, mOffset x : x-z plane, wheel closer/further std::pair MouseState::planeAxis() { - bool screenCoord = true; + const bool screenCoord = true; Ogre::Vector3 dir = getCamera()->getDerivedDirection(); QString wheelDir = "Closer/Further"; diff --git a/apps/opencs/view/render/object.cpp b/apps/opencs/view/render/object.cpp index d92b4aaa2d..3607fb415d 100644 --- a/apps/opencs/view/render/object.cpp +++ b/apps/opencs/view/render/object.cpp @@ -35,7 +35,8 @@ void CSVRender::Object::clear() { mObject.setNull(); - clearSceneNode (mBase); + if (mBase) + clearSceneNode (mBase); } void CSVRender::Object::update() @@ -156,11 +157,13 @@ CSVRender::Object::~Object() { clear(); - if(mPhysics) // preview may not have physics enabled - mPhysics->removeObject(mBase->getName()); - if (mBase) + { + if(mPhysics) // preview may not have physics enabled + mPhysics->removeObject(mBase->getName()); + mBase->getCreator()->destroySceneNode (mBase); + } } bool CSVRender::Object::referenceableDataChanged (const QModelIndex& topLeft, diff --git a/apps/opencs/view/render/object.hpp b/apps/opencs/view/render/object.hpp index 780d19e802..4ae63386f8 100644 --- a/apps/opencs/view/render/object.hpp +++ b/apps/opencs/view/render/object.hpp @@ -3,7 +3,9 @@ #include +#ifndef Q_MOC_RUN #include +#endif class QModelIndex; diff --git a/apps/opencs/view/world/physicssystem.cpp b/apps/opencs/view/world/physicssystem.cpp index c5f1d0a75e..900e5cc1c9 100644 --- a/apps/opencs/view/world/physicssystem.cpp +++ b/apps/opencs/view/world/physicssystem.cpp @@ -16,7 +16,7 @@ namespace CSVWorld PhysicsSystem::PhysicsSystem() { // Create physics. shapeLoader is deleted by the physic engine - NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(); + NifBullet::ManualBulletShapeLoader* shapeLoader = new NifBullet::ManualBulletShapeLoader(true); mEngine = new OEngine::Physic::PhysicEngine(shapeLoader); } diff --git a/apps/opencs/view/world/tablesubview.cpp b/apps/opencs/view/world/tablesubview.cpp index 54518023b4..729b6b8d77 100644 --- a/apps/opencs/view/world/tablesubview.cpp +++ b/apps/opencs/view/world/tablesubview.cpp @@ -138,17 +138,19 @@ bool CSVWorld::TableSubView::eventFilter (QObject* object, QEvent* event) { if (event->type() == QEvent::Drop) { - QDropEvent* drop = dynamic_cast(event); - const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); - if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped - return false; - - bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); - if (handled) + if (QDropEvent* drop = dynamic_cast(event)) { - mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + const CSMWorld::TableMimeData* data = dynamic_cast(drop->mimeData()); + if (!data) // May happen when non-records (e.g. plain text) are dragged and dropped + return false; + + bool handled = data->holdsType(CSMWorld::UniversalId::Type_Filter); + if (handled) + { + mFilterBox->setRecordFilter(data->returnMatching(CSMWorld::UniversalId::Type_Filter).getId()); + } + return handled; } - return handled; } return false; } diff --git a/apps/openmw/CMakeLists.txt b/apps/openmw/CMakeLists.txt index 803c743259..eabbb7577e 100644 --- a/apps/openmw/CMakeLists.txt +++ b/apps/openmw/CMakeLists.txt @@ -40,11 +40,12 @@ add_openmw_dir (mwgui merchantrepair repair soulgemdialog companionwindow bookpage journalviewmodel journalbooks itemmodel containeritemmodel inventoryitemmodel sortfilteritemmodel itemview tradeitemmodel companionitemmodel pickpocketitemmodel controllers savegamedialog - recharge mode videowidget backgroundimage itemwidget screenfader debugwindow + recharge mode videowidget backgroundimage itemwidget screenfader debugwindow spellmodel spellview + draganddrop timeadvancer jailscreen ) add_openmw_dir (mwdialogue - dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper hypertextparser keywordsearch + dialoguemanagerimp journalimp journalentry quest topic filter selectwrapper hypertextparser keywordsearch scripttest ) add_openmw_dir (mwscript @@ -76,7 +77,7 @@ add_openmw_dir (mwmechanics mechanicsmanagerimp stat character creaturestats magiceffects movement actors objects drawstate spells activespells npcstats aipackage aisequence aipursue alchemy aiwander aitravel aifollow aiavoiddoor aiescort aiactivate aicombat repair enchanting pathfinding pathgrid security spellsuccess spellcasting - disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction + disease pickpocket levelledlist combat steering obstacle autocalcspell difficultyscaling aicombataction actor summoning ) add_openmw_dir (mwstate diff --git a/apps/openmw/crashcatcher.cpp b/apps/openmw/crashcatcher.cpp index b9d78540e0..8f25d041cb 100644 --- a/apps/openmw/crashcatcher.cpp +++ b/apps/openmw/crashcatcher.cpp @@ -11,7 +11,6 @@ #include #include - #include #include #include @@ -29,6 +28,7 @@ #include #endif +#define UNUSED(x) (void)(x) static const char crash_switch[] = "--cc-handle-crash"; @@ -160,7 +160,11 @@ static void gdb_info(pid_t pid) printf("Executing: %s\n", cmd_buf); fflush(stdout); - system(cmd_buf); + { /* another special exception for "ignoring return value..." */ + int unused; + unused = system(cmd_buf); + UNUSED(unused); + } /* Clean up */ remove(respfile); } @@ -406,7 +410,13 @@ int cc_install_handlers(int argc, char **argv, int num_signals, int *signals, co snprintf(argv0, sizeof(argv0), "%s", argv[0]); else { - getcwd(argv0, sizeof(argv0)); + { + /* we don't want to disable "ignoring return value" warnings, so we make + * a special exception here. */ + char * unused; + unused = getcwd(argv0, sizeof(argv0)); + UNUSED(unused); + } retval = strlen(argv0); snprintf(argv0+retval, sizeof(argv0)-retval, "/%s", argv[0]); } diff --git a/apps/openmw/engine.cpp b/apps/openmw/engine.cpp index d7b23c0966..669bae60a7 100644 --- a/apps/openmw/engine.cpp +++ b/apps/openmw/engine.cpp @@ -40,6 +40,7 @@ #include "mwdialogue/dialoguemanagerimp.hpp" #include "mwdialogue/journalimp.hpp" +#include "mwdialogue/scripttest.hpp" #include "mwmechanics/mechanicsmanagerimp.hpp" @@ -108,11 +109,14 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) { if (!paused) { - // local scripts - executeLocalScripts(); + if (MWBase::Environment::get().getWorld()->getScriptsEnabled()) + { + // local scripts + executeLocalScripts(); - // global scripts - MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + // global scripts + MWBase::Environment::get().getScriptManager()->getGlobalScripts().run(); + } MWBase::Environment::get().getWorld()->markCellAsUnchanged(); } @@ -169,11 +173,11 @@ bool OMW::Engine::frameRenderingQueued (const Ogre::FrameEvent& evt) OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) : mOgre (0) - , mFpsLevel(0) , mVerboseScripts (false) , mSkipMenu (false) , mUseSound (true) , mCompileAll (false) + , mCompileAllDialogue (false) , mWarningsMode (1) , mScriptContext (0) , mFSStrict (false) @@ -206,6 +210,8 @@ OMW::Engine::Engine(Files::ConfigurationManager& configurationManager) OMW::Engine::~Engine() { + if (mOgre) + mOgre->restoreWindowGammaRamp(); mEnvironment.cleanup(); delete mScriptContext; delete mOgre; @@ -288,16 +294,10 @@ std::string OMW::Engine::loadSettings (Settings::Manager & settings) else throw std::runtime_error ("No default settings file found! Make sure the file \"settings-default.cfg\" was properly installed."); - // load user settings if they exist, otherwise just load the default settings as user settings + // load user settings if they exist const std::string settingspath = mCfgMgr.getUserConfigPath().string() + "/settings.cfg"; if (boost::filesystem::exists(settingspath)) settings.loadUser(settingspath); - else if (boost::filesystem::exists(localdefault)) - settings.loadUser(localdefault); - else if (boost::filesystem::exists(globaldefault)) - settings.loadUser(globaldefault); - - mFpsLevel = settings.getInt("fps", "HUD"); // load nif overrides NifOverrides::Overrides nifOverrides; @@ -345,9 +345,11 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) addResourcesDirectory(mResDir / "mygui"); addResourcesDirectory(mResDir / "water"); addResourcesDirectory(mResDir / "shadows"); + addResourcesDirectory(mResDir / "materials"); OEngine::Render::WindowSettings windowSettings; windowSettings.fullscreen = settings.getBool("fullscreen", "Video"); + windowSettings.window_border = settings.getBool("window border", "Video"); windowSettings.window_x = settings.getInt("resolution x", "Video"); windowSettings.window_y = settings.getInt("resolution y", "Video"); windowSettings.screen = settings.getInt("screen", "Video"); @@ -372,13 +374,15 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) mEnvironment.setInputManager (input); MWGui::WindowManager* window = new MWGui::WindowManager( - mExtensions, mFpsLevel, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), + mExtensions, mOgre, mCfgMgr.getLogPath().string() + std::string("/"), mCfgMgr.getCachePath ().string(), mScriptConsoleMode, mTranslationDataStorage, mEncoding, mExportFonts, mFallbackMap); mEnvironment.setWindowManager (window); // Create sound system mEnvironment.setSoundManager (new MWSound::SoundManager(mUseSound)); + mOgre->setWindowGammaContrast(Settings::Manager::getFloat("gamma", "General"), Settings::Manager::getFloat("contrast", "General")); + if (!mSkipMenu) { std::string logo = mFallbackMap["Movies_Company_Logo"]; @@ -425,7 +429,6 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) if (mCompileAll) { std::pair result = MWBase::Environment::get().getScriptManager()->compileAll(); - if (result.first) std::cout << "compiled " << result.second << " of " << result.first << " scripts (" @@ -433,6 +436,16 @@ void OMW::Engine::prepareEngine (Settings::Manager & settings) << "%)" << std::endl; } + if (mCompileAllDialogue) + { + std::pair result = MWDialogue::ScriptTest::compileAll(&mExtensions, mWarningsMode); + if (result.first) + std::cout + << "compiled " << result.second << " of " << result.first << " dialogue script/actor combinations a(" + << 100*static_cast (result.second)/result.first + << "%)" + << std::endl; + } } // Initialise and enter main loop. @@ -456,9 +469,13 @@ void OMW::Engine::go() // Play some good 'ol tunes MWBase::Environment::get().getSoundManager()->playPlaylist(std::string("Explore")); - // start in main menu - if (!mSkipMenu) + if (!mSaveGameFile.empty()) { + MWBase::Environment::get().getStateManager()->loadGame(mSaveGameFile); + } + else if (!mSkipMenu) + { + // start in main menu MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); try { @@ -497,6 +514,11 @@ void OMW::Engine::activate() if (MWBase::Environment::get().getWindowManager()->isGuiMode()) return; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + if (player.getClass().getCreatureStats(player).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0 + || player.getClass().getCreatureStats(player).getKnockedDown()) + return; + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getFacedObject(); if (ptr.isEmpty()) @@ -505,6 +527,14 @@ void OMW::Engine::activate() if (ptr.getClass().getName(ptr) == "") // objects without name presented to user can never be activated return; + if (ptr.getClass().isActor()) + { + MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + + if (stats.getAiSequence().isInCombat() && !stats.isDead()) + return; + } + MWBase::Environment::get().getWorld()->activate(ptr, MWBase::Environment::get().getWorld()->getPlayerPtr()); } @@ -535,16 +565,16 @@ void OMW::Engine::setCompileAll (bool all) mCompileAll = all; } +void OMW::Engine::setCompileAllDialogue (bool all) +{ + mCompileAllDialogue = all; +} + void OMW::Engine::setSoundUsage(bool soundUsage) { mUseSound = soundUsage; } -void OMW::Engine::showFPS(int level) -{ - mFpsLevel = level; -} - void OMW::Engine::setEncoding(const ToUTF8::FromType& encoding) { mEncoding = encoding; @@ -589,3 +619,8 @@ void OMW::Engine::enableFontExport(bool exportFonts) { mExportFonts = exportFonts; } + +void OMW::Engine::setSaveGameFile(const std::string &savegame) +{ + mSaveGameFile = savegame; +} diff --git a/apps/openmw/engine.hpp b/apps/openmw/engine.hpp index 0ee5a09c5b..3b088595c0 100644 --- a/apps/openmw/engine.hpp +++ b/apps/openmw/engine.hpp @@ -71,17 +71,18 @@ namespace OMW OEngine::Render::OgreRenderer *mOgre; std::string mCellName; std::vector mContentFiles; - int mFpsLevel; bool mVerboseScripts; bool mSkipMenu; bool mUseSound; bool mCompileAll; + bool mCompileAllDialogue; int mWarningsMode; std::string mFocusName; std::map mFallbackMap; bool mScriptConsoleMode; std::string mStartupScript; int mActivationDistanceOverride; + std::string mSaveGameFile; // Grab mouse? bool mGrab; @@ -149,9 +150,6 @@ namespace OMW */ void addContentFile(const std::string& file); - /// Enable fps counter - void showFPS(int level); - /// Enable or disable verbose script output void setScriptsVerbosity(bool scriptsVerbosity); @@ -178,6 +176,9 @@ namespace OMW /// Compile all scripts (excludign dialogue scripts) at startup? void setCompileAll (bool all); + /// Compile all dialogue scripts at startup? + void setCompileAllDialogue (bool all); + /// Font encoding void setEncoding(const ToUTF8::FromType& encoding); @@ -200,6 +201,9 @@ namespace OMW void enableFontExport(bool exportFonts); + /// Set the save game file to load after initialising the engine. + void setSaveGameFile(const std::string& savegame); + private: Files::ConfigurationManager& mCfgMgr; }; diff --git a/apps/openmw/main.cpp b/apps/openmw/main.cpp index 744780b258..82fda060ed 100644 --- a/apps/openmw/main.cpp +++ b/apps/openmw/main.cpp @@ -130,6 +130,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-all", bpo::value()->implicit_value(true) ->default_value(false), "compile all scripts (excluding dialogue scripts) at startup") + ("script-all-dialogue", bpo::value()->implicit_value(true) + ->default_value(false), "compile all dialogue scripts at startup") + ("script-console", bpo::value()->implicit_value(true) ->default_value(false), "enable console-only script functionality") @@ -149,6 +152,9 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat ("script-blacklist-use", bpo::value()->implicit_value(true) ->default_value(true), "enable script blacklisting") + ("load-savegame", bpo::value()->default_value(""), + "load a save game file on game startup (specify an absolute filename or a filename relative to the current working directory)") + ("skip-menu", bpo::value()->implicit_value(true) ->default_value(false), "skip main menu on game startup") @@ -184,29 +190,23 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat bpo::store(valid_opts, variables); bpo::notify(variables); - bool run = true; - if (variables.count ("help")) { std::cout << desc << std::endl; - run = false; + return false; } + std::cout << "OpenMW version " << OPENMW_VERSION; + std::string rev = OPENMW_VERSION_COMMITHASH; + std::string tag = OPENMW_VERSION_TAGHASH; + if (!rev.empty() && !tag.empty()) + { + rev = rev.substr(0, 10); + std::cout << " (revision " << rev << ")"; + } + std::cout << std::endl; + if (variables.count ("version")) - { - std::cout << "OpenMW version " << OPENMW_VERSION << std::endl; - - std::string rev = OPENMW_VERSION_COMMITHASH; - std::string tag = OPENMW_VERSION_TAGHASH; - if (!rev.empty() && !tag.empty()) - { - rev = rev.substr(0, 10); - std::cout << "Revision " << rev << std::endl; - } - run = false; - } - - if (!run) return false; cfgMgr.readConfiguration(variables, desc); @@ -264,12 +264,14 @@ bool parseOptions (int argc, char** argv, OMW::Engine& engine, Files::Configurat // scripts engine.setCompileAll(variables["script-all"].as()); + engine.setCompileAllDialogue(variables["script-all-dialogue"].as()); engine.setScriptsVerbosity(variables["script-verbose"].as()); engine.setScriptConsoleMode (variables["script-console"].as()); engine.setStartupScript (variables["script-run"].as()); engine.setWarningsMode (variables["script-warn"].as()); engine.setScriptBlacklist (variables["script-blacklist"].as()); engine.setScriptBlacklistUse (variables["script-blacklist-use"].as()); + engine.setSaveGameFile (variables["load-savegame"].as()); // other settings engine.setSoundUsage(!variables["no-sound"].as()); @@ -388,12 +390,12 @@ int main(int argc, char**argv) catch (std::exception &e) { #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX || OGRE_PLATFORM == OGRE_PLATFORM_APPLE - if (isatty(fileno(stdin))) - std::cerr << "\nERROR: " << e.what() << std::endl; - else + if (!isatty(fileno(stdin))) #endif SDL_ShowSimpleMessageBox(0, "OpenMW: Fatal error", e.what(), NULL); + std::cerr << "\nERROR: " << e.what() << std::endl; + ret = 1; } diff --git a/apps/openmw/mwbase/dialoguemanager.hpp b/apps/openmw/mwbase/dialoguemanager.hpp index d0e64b23c8..1fe63e6337 100644 --- a/apps/openmw/mwbase/dialoguemanager.hpp +++ b/apps/openmw/mwbase/dialoguemanager.hpp @@ -69,11 +69,13 @@ namespace MWBase virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; - virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff) = 0; + virtual void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute) = 0; + /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const = 0; diff --git a/apps/openmw/mwbase/environment.hpp b/apps/openmw/mwbase/environment.hpp index eb636ea2fb..7f7919f813 100644 --- a/apps/openmw/mwbase/environment.hpp +++ b/apps/openmw/mwbase/environment.hpp @@ -1,5 +1,5 @@ -#ifndef GAME_BASE_INVIRONMENT_H -#define GAME_BASE_INVIRONMENT_H +#ifndef GAME_BASE_ENVIRONMENT_H +#define GAME_BASE_ENVIRONMENT_H namespace MWBase { diff --git a/apps/openmw/mwbase/inputmanager.hpp b/apps/openmw/mwbase/inputmanager.hpp index d44da4974d..a53d8d9dcd 100644 --- a/apps/openmw/mwbase/inputmanager.hpp +++ b/apps/openmw/mwbase/inputmanager.hpp @@ -2,8 +2,8 @@ #define GAME_MWBASE_INPUTMANAGER_H #include - -#include +#include +#include namespace MWBase { @@ -29,7 +29,7 @@ namespace MWBase virtual void changeInputMode(bool guiMode) = 0; - virtual void processChangedSettings(const Settings::CategorySettingVector& changed) = 0; + virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; virtual void setDragDrop(bool dragDrop) = 0; diff --git a/apps/openmw/mwbase/journal.hpp b/apps/openmw/mwbase/journal.hpp index 938cec74b2..738014ba6e 100644 --- a/apps/openmw/mwbase/journal.hpp +++ b/apps/openmw/mwbase/journal.hpp @@ -92,7 +92,7 @@ namespace MWBase virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const = 0; - virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; }; } diff --git a/apps/openmw/mwbase/mechanicsmanager.hpp b/apps/openmw/mwbase/mechanicsmanager.hpp index ce213b316a..f7fc515f57 100644 --- a/apps/openmw/mwbase/mechanicsmanager.hpp +++ b/apps/openmw/mwbase/mechanicsmanager.hpp @@ -113,34 +113,31 @@ namespace MWBase 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_Trespassing, // Picking the lock of an owned door/chest 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. * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. - * @return was the crime reported? + * @param victimAware Is the victim already aware of the crime? + * If this parameter is false, it will be determined by a line-of-sight and awareness check. + * @return was the crime seen? */ 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; + OffenseType type, int arg=0, bool victimAware=false) = 0; /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker) = 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; + /// @param container The container the item is in; may be empty for an item in the world + virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, + 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; - /// @return is \a ptr allowed to take/use \a item or is it a crime? - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim) = 0; - enum PersuasionType { PT_Admire, @@ -183,6 +180,7 @@ namespace MWBase ///return the list of actors which are following the given actor /**ie AiFollow is active and the target is the actor**/ virtual std::list getActorsFollowing(const MWWorld::Ptr& actor) = 0; + virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor) = 0; ///Returns a list of actors who are fighting the given actor within the fAlarmDistance /** ie AiCombat is active and the target is the actor **/ @@ -194,16 +192,25 @@ namespace MWBase virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; - virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; virtual void clear() = 0; - /// @param bias Can be used to add an additional aggression bias towards the target, - /// making it more likely for the function to return true. - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false) = 0; + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target) = 0; /// Resurrects the player if necessary virtual void keepPlayerAlive() = 0; + + virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const = 0; + + virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer) = 0; + + /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). + /// + virtual std::vector > getStolenItemOwners(const std::string& itemid) = 0; + + /// Has the player stolen this item from the given owner? + virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid) = 0; }; } diff --git a/apps/openmw/mwbase/soundmanager.hpp b/apps/openmw/mwbase/soundmanager.hpp index a02a463dde..f3381a8fda 100644 --- a/apps/openmw/mwbase/soundmanager.hpp +++ b/apps/openmw/mwbase/soundmanager.hpp @@ -2,11 +2,9 @@ #define GAME_MWBASE_SOUNDMANAGER_H #include - +#include #include -#include - #include "../mwworld/ptr.hpp" namespace Ogre @@ -42,15 +40,21 @@ namespace MWBase Play_NoTrack = 1<<2, /* (3D only) Play the sound at the given object's position * but do not keep it updated (the sound will not move with * the object and will not stop when the object is deleted. */ - - Play_LoopNoEnv = Play_Loop | Play_NoEnv + Play_RemoveAtDistance = 1<<3, /* (3D only) If the listener gets further than 2000 units away + from the sound source, the sound is removed. + This is weird stuff but apparently how vanilla works for sounds + played by the PlayLoopSound family of script functions. Perhaps we + can make this cut off a more subtle fade later, but have to + be careful to not change the overall volume of areas by too much. */ + Play_LoopNoEnv = Play_Loop | Play_NoEnv, + Play_LoopRemoveAtDistance = Play_Loop | Play_RemoveAtDistance }; enum PlayType { - Play_TypeSfx = 1<<3, /* Normal SFX sound */ - Play_TypeVoice = 1<<4, /* Voice sound */ - Play_TypeFoot = 1<<5, /* Footstep sound */ - Play_TypeMusic = 1<<6, /* Music track */ - Play_TypeMovie = 1<<7, /* Movie audio track */ + Play_TypeSfx = 1<<4, /* Normal SFX sound */ + Play_TypeVoice = 1<<5, /* Voice sound */ + Play_TypeFoot = 1<<6, /* Footstep sound */ + Play_TypeMusic = 1<<7, /* Music track */ + Play_TypeMovie = 1<<8, /* Movie audio track */ Play_TypeMask = Play_TypeSfx|Play_TypeVoice|Play_TypeFoot|Play_TypeMusic|Play_TypeMovie }; @@ -68,7 +72,7 @@ namespace MWBase virtual ~SoundManager() {} - virtual void processChangedSettings(const Settings::CategorySettingVector& settings) = 0; + virtual void processChangedSettings(const std::set< std::pair >& settings) = 0; virtual void stopMusic() = 0; ///< Stops music if it's playing diff --git a/apps/openmw/mwbase/statemanager.hpp b/apps/openmw/mwbase/statemanager.hpp index 006be921b1..79ddbfa2ab 100644 --- a/apps/openmw/mwbase/statemanager.hpp +++ b/apps/openmw/mwbase/statemanager.hpp @@ -62,10 +62,13 @@ namespace MWBase /// /// \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 void loadGame (const std::string& filepath) = 0; + ///< Load a saved game directly from the given file path. This will search the CharacterManager + /// for a Character containing this save file, and set this Character current if one was found. + /// Otherwise, a new Character will be created. + + virtual void loadGame (const MWState::Character *character, const std::string& filepath) = 0; + ///< Load a saved game file belonging to the given character. ///Simple saver, writes over the file if already existing /** Used for quick save and autosave **/ diff --git a/apps/openmw/mwbase/windowmanager.hpp b/apps/openmw/mwbase/windowmanager.hpp index d4f1afa32d..2c4cb360d5 100644 --- a/apps/openmw/mwbase/windowmanager.hpp +++ b/apps/openmw/mwbase/windowmanager.hpp @@ -1,20 +1,24 @@ #ifndef GAME_MWBASE_WINDOWMANAGER_H #define GAME_MWBASE_WINDOWMANAGER_H +#include #include #include #include - -#include - -#include - -#include - -#include "../mwmechanics/stat.hpp" +#include #include "../mwgui/mode.hpp" +namespace Loading +{ + class Listener; +} + +namespace Translation +{ + class Storage; +} + namespace MyGUI { class Gui; @@ -35,6 +39,15 @@ namespace ESM struct Class; class ESMReader; class ESMWriter; + struct CellId; +} + +namespace MWMechanics +{ + class AttributeValue; + template + class DynamicStat; + class SkillValue; } namespace MWWorld @@ -58,6 +71,7 @@ namespace MWGui class ContainerWindow; class DialogueWindow; class WindowModal; + class JailScreen; enum ShowInDialogueMode { ShowInDialogueMode_IfPossible, @@ -109,6 +123,8 @@ namespace MWBase virtual void removeGuiMode (MWGui::GuiMode mode) = 0; ///< can be anywhere in the stack + virtual void goToJail(int days) = 0; + virtual void updatePlayer() = 0; virtual MWGui::GuiMode getMode() const = 0; @@ -240,9 +256,11 @@ namespace MWBase /** No guarentee of actually closing the window **/ virtual void exitCurrentGuiMode() = 0; - virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; + virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible) = 0; virtual void staticMessageBox(const std::string& message) = 0; virtual void removeStaticMessageBox() = 0; + virtual void interactiveMessageBox (const std::string& message, + const std::vector& buttons = std::vector(), bool block=false) = 0; /// returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual int readPressedButton() = 0; @@ -264,7 +282,7 @@ namespace MWBase */ virtual std::string getGameSettingString(const std::string &id, const std::string &default_) = 0; - virtual void processChangedSettings(const Settings::CategorySettingVector& changed) = 0; + virtual void processChangedSettings(const std::set< std::pair >& changed) = 0; virtual void windowResized(int x, int y) = 0; @@ -308,7 +326,7 @@ namespace MWBase virtual void clear() = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) = 0; - virtual void readRecord (ESM::ESMReader& reader, int32_t type) = 0; + virtual void readRecord (ESM::ESMReader& reader, uint32_t type) = 0; virtual int countSavedGameRecords() const = 0; /// Does the current stack of GUI-windows permit saving? @@ -342,6 +360,11 @@ namespace MWBase virtual void setWerewolfOverlay(bool set) = 0; virtual void toggleDebugWindow() = 0; + + /// Cycle to next or previous spell + virtual void cycleSpell(bool next) = 0; + /// Cycle to next or previous weapon + virtual void cycleWeapon(bool next) = 0; }; } diff --git a/apps/openmw/mwbase/world.hpp b/apps/openmw/mwbase/world.hpp index 2dd135f3da..8180d0b1ab 100644 --- a/apps/openmw/mwbase/world.hpp +++ b/apps/openmw/mwbase/world.hpp @@ -3,24 +3,23 @@ #include #include +#include -#include +#include -#include "../mwworld/globals.hpp" #include "../mwworld/ptr.hpp" namespace Ogre { class Vector2; class Vector3; + class Quaternion; + class Image; } -namespace OEngine +namespace Loading { - namespace Physic - { - class PhysicEngine; - } + class Listener; } namespace ESM @@ -33,13 +32,14 @@ namespace ESM struct Potion; struct Spell; struct NPC; - struct CellId; struct Armor; struct Weapon; struct Clothing; struct Enchantment; struct Book; struct EffectList; + struct CreatureLevList; + struct ItemLevList; } namespace MWRender @@ -90,6 +90,7 @@ namespace MWBase { std::string name; float x, y; // world position + ESM::CellId dest; }; World() {} @@ -102,10 +103,11 @@ namespace MWBase virtual void clear() = 0; virtual int countSavedGameRecords() const = 0; + virtual int countSavedGameCells() const = 0; virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const = 0; - virtual void readRecord (ESM::ESMReader& reader, int32_t type, + virtual void readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) = 0; virtual MWWorld::CellStore *getExterior (int x, int y) = 0; @@ -281,7 +283,8 @@ namespace MWBase virtual void deleteObject (const MWWorld::Ptr& ptr) = 0; virtual void undeleteObject (const MWWorld::Ptr& ptr) = 0; - virtual void moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; + virtual MWWorld::Ptr moveObject (const MWWorld::Ptr& ptr, float x, float y, float z) = 0; + ///< @return an updated Ptr in case the Ptr's cell changes virtual void moveObject(const MWWorld::Ptr &ptr, MWWorld::CellStore* newCell, float x, float y, float z) = 0; @@ -358,6 +361,14 @@ namespace MWBase ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record + virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + + virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record) = 0; + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + virtual void update (float duration, bool paused) = 0; virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount) = 0; @@ -376,17 +387,19 @@ namespace MWBase virtual bool canPlaceObject (float cursorX, float cursorY) = 0; ///< @return true if it is possible to place on object at specified cursor location - virtual void processChangedSettings (const Settings::CategorySettingVector& settings) = 0; + virtual void processChangedSettings (const std::set< std::pair >& settings) = 0; virtual bool isFlying(const MWWorld::Ptr &ptr) const = 0; virtual bool isSlowFalling(const MWWorld::Ptr &ptr) const = 0; virtual bool isSwimming(const MWWorld::Ptr &object) const = 0; + virtual bool isWading(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::CellStore* cell, const Ogre::Vector3 &pos) const = 0; virtual bool isOnGround(const MWWorld::Ptr &ptr) const = 0; virtual void togglePOV() = 0; + virtual bool isFirstPerson() const = 0; virtual void togglePreviewMode(bool enable) = 0; virtual bool toggleVanityMode(bool enable) = 0; virtual void allowVanityMode(bool allow) = 0; @@ -439,6 +452,7 @@ namespace MWBase /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr) = 0; + virtual void reattachPlayerCamera() = 0; /// \todo this does not belong here virtual void frameStarted (float dt, bool paused) = 0; @@ -475,6 +489,9 @@ namespace MWBase virtual bool toggleGodMode() = 0; + virtual bool toggleScripts() = 0; + virtual bool getScriptsEnabled() const = 0; + /** * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. * @param actor @@ -534,7 +551,7 @@ namespace MWBase virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos) = 0; virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName) = 0; + const MWWorld::Ptr& caster, int rangeType, const std::string& id, const std::string& sourceName) = 0; virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor) = 0; diff --git a/apps/openmw/mwclass/activator.cpp b/apps/openmw/mwclass/activator.cpp index bf02b4d05e..457b0cec10 100644 --- a/apps/openmw/mwclass/activator.cpp +++ b/apps/openmw/mwclass/activator.cpp @@ -8,7 +8,8 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/world.hpp" -#include "../mwworld//cellstore.hpp" +#include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/action.hpp" @@ -30,20 +31,18 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Activator::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertActivator(ptr); + actors.insertActivator(ptr, model); } } - void Activator::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Activator::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr); + physics.addObject(ptr, model); MWBase::Environment::get().getMechanicsManager()->add(ptr); } diff --git a/apps/openmw/mwclass/activator.hpp b/apps/openmw/mwclass/activator.hpp index 3e4bc3de41..e79318a55e 100644 --- a/apps/openmw/mwclass/activator.hpp +++ b/apps/openmw/mwclass/activator.hpp @@ -16,10 +16,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/apparatus.cpp b/apps/openmw/mwclass/apparatus.cpp index c466cbc33e..316ba3ab6e 100644 --- a/apps/openmw/mwclass/apparatus.cpp +++ b/apps/openmw/mwclass/apparatus.cpp @@ -26,19 +26,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Apparatus::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Apparatus::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Apparatus::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Apparatus::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/apparatus.hpp b/apps/openmw/mwclass/apparatus.hpp index 5cdda8f26a..2ab0a47e3b 100644 --- a/apps/openmw/mwclass/apparatus.hpp +++ b/apps/openmw/mwclass/apparatus.hpp @@ -18,10 +18,10 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/armor.cpp b/apps/openmw/mwclass/armor.cpp index 97f9211d9a..64043157dc 100644 --- a/apps/openmw/mwclass/armor.cpp +++ b/apps/openmw/mwclass/armor.cpp @@ -14,6 +14,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/nullaction.hpp" #include "../mwworld/containerstore.hpp" @@ -30,19 +31,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Armor::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Armor::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Armor::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Armor::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/armor.hpp b/apps/openmw/mwclass/armor.hpp index 8b7804c63f..8c8e74cf41 100644 --- a/apps/openmw/mwclass/armor.hpp +++ b/apps/openmw/mwclass/armor.hpp @@ -17,10 +17,10 @@ namespace MWClass virtual float getWeight (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/book.cpp b/apps/openmw/mwclass/book.cpp index 51d47e7216..a9c96e7c7d 100644 --- a/apps/openmw/mwclass/book.cpp +++ b/apps/openmw/mwclass/book.cpp @@ -11,6 +11,7 @@ #include "../mwworld/actionread.hpp" #include "../mwworld/failedaction.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwrender/objects.hpp" @@ -27,19 +28,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Book::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Book::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Book::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Book::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Book::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/book.hpp b/apps/openmw/mwclass/book.hpp index 49d21e8bf7..05ff88bb2e 100644 --- a/apps/openmw/mwclass/book.hpp +++ b/apps/openmw/mwclass/book.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/clothing.cpp b/apps/openmw/mwclass/clothing.cpp index 0098783505..0fa686dda4 100644 --- a/apps/openmw/mwclass/clothing.cpp +++ b/apps/openmw/mwclass/clothing.cpp @@ -12,6 +12,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/nullaction.hpp" @@ -27,19 +28,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Clothing::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Clothing::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Clothing::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Clothing::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/clothing.hpp b/apps/openmw/mwclass/clothing.hpp index 99ce61ece3..5700543481 100644 --- a/apps/openmw/mwclass/clothing.hpp +++ b/apps/openmw/mwclass/clothing.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/container.cpp b/apps/openmw/mwclass/container.cpp index 59e51e4613..5f49a74b6c 100644 --- a/apps/openmw/mwclass/container.cpp +++ b/apps/openmw/mwclass/container.cpp @@ -15,6 +15,7 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/customdata.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/actionopen.hpp" #include "../mwworld/actiontrap.hpp" #include "../mwworld/physicssystem.hpp" @@ -58,8 +59,10 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); + // setting ownership not needed, since taking items from a container inherits the + // container's owner automatically data->mContainerStore.fill( - ref->mBase->mInventory, ptr.getCellRef().getOwner(), ptr.getCellRef().getFaction(), MWBase::Environment::get().getWorld()->getStore()); + ref->mBase->mInventory, ""); // store ptr.getRefData().setCustomData (data.release()); @@ -81,23 +84,24 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); const ESM::InventoryList& list = ref->mBase->mInventory; MWWorld::ContainerStore& store = getContainerStore(ptr); - store.restock(list, ptr, ptr.getCellRef().getOwner(), ptr.getCellRef().getFaction()); + + // setting ownership not needed, since taking items from a container inherits the + // container's owner automatically + store.restock(list, ptr, ""); } - void Container::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Container::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertActivator(ptr); + actors.insertActivator(ptr, model); } } - void Container::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Container::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr); + physics.addObject(ptr, model); MWBase::Environment::get().getMechanicsManager()->add(ptr); } diff --git a/apps/openmw/mwclass/container.hpp b/apps/openmw/mwclass/container.hpp index e926a71fe3..52873374e4 100644 --- a/apps/openmw/mwclass/container.hpp +++ b/apps/openmw/mwclass/container.hpp @@ -18,10 +18,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/creature.cpp b/apps/openmw/mwclass/creature.cpp index 5910c471b3..e44f4ffc12 100644 --- a/apps/openmw/mwclass/creature.cpp +++ b/apps/openmw/mwclass/creature.cpp @@ -119,7 +119,12 @@ namespace MWClass // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) - data->mCreatureStats.getSpells().add (*iter); + { + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) + data->mCreatureStats.getSpells().add (spell); + else /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility + std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on creature '" << ref->mBase->mId << "'" << std::endl; + } // inventory if (ref->mBase->mFlags & ESM::Creature::Weapon) @@ -134,8 +139,7 @@ namespace MWClass // store ptr.getRefData().setCustomData(data.release()); - getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr), "", - MWBase::Environment::get().getWorld()->getStore()); + getContainerStore(ptr).fill(ref->mBase->mInventory, getId(ptr)); if (ref->mBase->mFlags & ESM::Creature::Weapon) getInventoryStore(ptr).autoEquip(ptr); @@ -155,20 +159,19 @@ namespace MWClass MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Creature::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWWorld::LiveCellRef *ref = ptr.get(); MWRender::Actors& actors = renderingInterface.getActors(); - actors.insertCreature(ptr, ref->mBase->mFlags & ESM::Creature::Weapon); + actors.insertCreature(ptr, model, ref->mBase->mFlags & ESM::Creature::Weapon); } - void Creature::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Creature::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) { - physics.addActor(ptr); + physics.addActor(ptr, model); if (getCreatureStats(ptr).isDead()) MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); } @@ -224,18 +227,7 @@ namespace MWClass 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 fatigue = stats.getFatigue(); - const float normalizedEncumbrance = getNormalizedEncumbrance(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); + MWMechanics::applyFatigueLoss(ptr, weapon); // TODO: where is the distance defined? float dist = 200.f; @@ -283,7 +275,7 @@ namespace MWClass } float damage = min + (max - min) * stats.getAttackStrength(); - + bool healthdmg = true; if (!weapon.isEmpty()) { const unsigned char *attack = NULL; @@ -316,10 +308,14 @@ namespace MWClass } } } + else if (isBipedal(ptr)) + { + MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg); + } MWMechanics::applyElementalShields(ptr, victim); - if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) + if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) damage = 0; if (damage > 0) @@ -327,7 +323,7 @@ namespace MWClass MWMechanics::diseaseContact(victim, ptr); - victim.getClass().onHit(victim, damage, true, weapon, ptr, true); + victim.getClass().onHit(victim, damage, healthdmg, 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 @@ -339,17 +335,18 @@ namespace MWClass // Self defense bool setOnPcHitMe = true; // Note OnPcHitMe is not set for friendly hits. - if ((canWalk(ptr) || canFly(ptr) || canSwim(ptr)) // No retaliation for totally static creatures - // (they have no movement or attacks anyway) - && !attacker.isEmpty()) + + // No retaliation for totally static creatures (they have no movement or attacks anyway) + if (isMobile(ptr) && !attacker.isEmpty()) { setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } + if(!object.isEmpty()) + getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + if(!successful) { - // TODO: Handle HitAttemptOnMe script function - // Missed MWBase::Environment::get().getSoundManager()->playSound3D(ptr, "miss", 1.0f, 1.0f); return; @@ -672,15 +669,15 @@ namespace MWClass if(type >= 0) { std::vector sounds; - sounds.reserve(8); - std::string ptrid = Creature::getId(ptr); + MWWorld::LiveCellRef* ref = ptr.get(); + + const std::string& ourId = (ref->mBase->mOriginal.empty()) ? getId(ptr) : ref->mBase->mOriginal; + MWWorld::Store::iterator sound = store.begin(); while(sound != store.end()) { - if(type == sound->mType && !sound->mCreature.empty() && - Misc::StringUtils::ciEqual(ptrid.substr(0, sound->mCreature.size()), - sound->mCreature)) + if (type == sound->mType && !sound->mCreature.empty() && (Misc::StringUtils::ciEqual(ourId, sound->mCreature))) sounds.push_back(&*sound); ++sound; } @@ -688,6 +685,9 @@ namespace MWClass return sounds[(int)(rand()/(RAND_MAX+1.0)*sounds.size())]->mSound; } + if (type == ESM::SoundGenerator::Land) + return "Body Fall Large"; + return ""; } @@ -804,6 +804,9 @@ namespace MWClass void Creature::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; + const ESM::CreatureState& state2 = dynamic_cast (state); ensureCustomData(ptr); @@ -832,7 +835,6 @@ namespace MWClass customData.mContainerStore->readState (state2.mInventory); customData.mCreatureStats.readState (state2.mCreatureStats); - } void Creature::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) @@ -840,6 +842,12 @@ namespace MWClass { ESM::CreatureState& state2 = dynamic_cast (state); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + ensureCustomData (ptr); CreatureCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); @@ -859,7 +867,7 @@ namespace MWClass { // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. // This also means we cannot respawn dynamically placed references with no content file connection. - if (ptr.getCellRef().getRefNum().mContentFile != -1) + if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); @@ -877,6 +885,18 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); const ESM::InventoryList& list = ref->mBase->mInventory; MWWorld::ContainerStore& store = getContainerStore(ptr); - store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); + store.restock(list, ptr, ptr.getCellRef().getRefId()); + } + + int Creature::getBaseFightRating(const MWWorld::Ptr &ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + return ref->mBase->mAiData.mFight; + } + + void Creature::adjustScale(const MWWorld::Ptr &ptr, float &scale) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + scale *= ref->mBase->mScale; } } diff --git a/apps/openmw/mwclass/creature.hpp b/apps/openmw/mwclass/creature.hpp index 1820d4ea43..e11529b2e8 100644 --- a/apps/openmw/mwclass/creature.hpp +++ b/apps/openmw/mwclass/creature.hpp @@ -44,10 +44,10 @@ namespace MWClass virtual std::string getId (const MWWorld::Ptr& ptr) const; ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; ///< Adjust position to stand on ground. Must be called post model load @@ -154,6 +154,10 @@ namespace MWClass virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr &ptr) const; + + virtual int getBaseFightRating(const MWWorld::Ptr &ptr) const; + + virtual void adjustScale(const MWWorld::Ptr& ptr,float& scale) const; }; } diff --git a/apps/openmw/mwclass/creaturelevlist.cpp b/apps/openmw/mwclass/creaturelevlist.cpp index b1e27c3450..dbc4b6af7b 100644 --- a/apps/openmw/mwclass/creaturelevlist.cpp +++ b/apps/openmw/mwclass/creaturelevlist.cpp @@ -52,7 +52,7 @@ namespace MWClass registerClass (typeid (ESM::CreatureLevList).name(), instance); } - void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, MWRender::RenderingInterface &renderingInterface) const + void CreatureLevList::insertObjectRendering(const MWWorld::Ptr &ptr, const std::string& model, MWRender::RenderingInterface &renderingInterface) const { ensureCustomData(ptr); diff --git a/apps/openmw/mwclass/creaturelevlist.hpp b/apps/openmw/mwclass/creaturelevlist.hpp index 7016524eb9..177aa72359 100644 --- a/apps/openmw/mwclass/creaturelevlist.hpp +++ b/apps/openmw/mwclass/creaturelevlist.hpp @@ -20,7 +20,7 @@ namespace MWClass static void registerSelf(); - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering virtual void readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) diff --git a/apps/openmw/mwclass/door.cpp b/apps/openmw/mwclass/door.cpp index fa9db9e160..10b9b437da 100644 --- a/apps/openmw/mwclass/door.cpp +++ b/apps/openmw/mwclass/door.cpp @@ -8,6 +8,7 @@ #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/nullaction.hpp" @@ -15,6 +16,7 @@ #include "../mwworld/actionteleport.hpp" #include "../mwworld/actiondoor.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/actiontrap.hpp" @@ -22,7 +24,7 @@ #include "../mwgui/tooltips.hpp" -#include "../mwrender/objects.hpp" +#include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" namespace @@ -47,19 +49,18 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Door::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Door::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { - renderingInterface.getObjects().insertModel(ptr, model); + MWRender::Actors& actors = renderingInterface.getActors(); + actors.insertActivator(ptr, model); } } - void Door::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Door::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr); + physics.addObject(ptr, model); // Resume the door's opening/closing animation if it wasn't finished if (ptr.getRefData().getCustomData()) @@ -70,6 +71,8 @@ namespace MWClass MWBase::Environment::get().getWorld()->activateDoor(ptr, customData.mDoorState); } } + + MWBase::Environment::get().getMechanicsManager()->add(ptr); } std::string Door::getModel(const MWWorld::Ptr &ptr) const @@ -90,9 +93,6 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - if (ptr.getCellRef().getTeleport() && !ptr.getCellRef().getDestCell().empty()) // TODO doors that lead to exteriors - return ptr.getCellRef().getDestCell(); - return ref->mBase->mName; } diff --git a/apps/openmw/mwclass/door.hpp b/apps/openmw/mwclass/door.hpp index 23e11d3368..c5f258d3e0 100644 --- a/apps/openmw/mwclass/door.hpp +++ b/apps/openmw/mwclass/door.hpp @@ -19,10 +19,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/ingredient.cpp b/apps/openmw/mwclass/ingredient.cpp index fa03f23ff1..9f662a60ea 100644 --- a/apps/openmw/mwclass/ingredient.cpp +++ b/apps/openmw/mwclass/ingredient.cpp @@ -10,6 +10,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/actioneat.hpp" #include "../mwworld/nullaction.hpp" @@ -31,19 +32,17 @@ namespace MWClass return ref->mBase->mId; } - void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Ingredient::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Ingredient::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Ingredient::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Ingredient::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/ingredient.hpp b/apps/openmw/mwclass/ingredient.hpp index 690dd601a7..a4681f4621 100644 --- a/apps/openmw/mwclass/ingredient.hpp +++ b/apps/openmw/mwclass/ingredient.hpp @@ -15,10 +15,10 @@ namespace MWClass virtual std::string getId (const MWWorld::Ptr& ptr) const; ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/light.cpp b/apps/openmw/mwclass/light.cpp index e6d266de25..35a21b63e4 100644 --- a/apps/openmw/mwclass/light.cpp +++ b/apps/openmw/mwclass/light.cpp @@ -2,12 +2,13 @@ #include "light.hpp" #include -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" @@ -22,29 +23,9 @@ #include "../mwgui/tooltips.hpp" #include "../mwrender/objects.hpp" +#include "../mwrender/actors.hpp" #include "../mwrender/renderinginterface.hpp" -namespace -{ - struct LightCustomData : public MWWorld::CustomData - { - float mTime; - ///< Time remaining - - LightCustomData(MWWorld::Ptr ptr) - { - MWWorld::LiveCellRef *ref = ptr.get(); - mTime = ref->mBase->mData.mTime; - } - ///< Constructs this CustomData from the base values for Ptr. - - virtual MWWorld::CustomData *clone() const - { - return new LightCustomData (*this); - } - }; -} - namespace MWClass { std::string Light::getId (const MWWorld::Ptr& ptr) const @@ -52,32 +33,31 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Light::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Light::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); - MWWorld::LiveCellRef *ref = ptr.get(); // Insert even if model is empty, so that the light is added - renderingInterface.getObjects().insertModel(ptr, model, false, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); + MWRender::Actors& actors = renderingInterface.getActors(); + actors.insertActivator(ptr, model, !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)); } - void Light::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Light::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { MWWorld::LiveCellRef *ref = ptr.get(); assert (ref->mBase != NULL); - const std::string &model = ref->mBase->mModel; - if(!model.empty()) - physics.addObject(ptr,ref->mBase->mData.mFlags & ESM::Light::Carry); + physics.addObject(ptr, model, ref->mBase->mData.mFlags & ESM::Light::Carry); if (!ref->mBase->mSound.empty() && !(ref->mBase->mData.mFlags & ESM::Light::OffDefault)) MWBase::Environment::get().getSoundManager()->playSound3D(ptr, ref->mBase->mSound, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, MWBase::SoundManager::Play_Loop); + + MWBase::Environment::get().getMechanicsManager()->add(ptr); } std::string Light::getModel(const MWWorld::Ptr &ptr) const @@ -218,17 +198,16 @@ namespace MWClass void Light::setRemainingUsageTime (const MWWorld::Ptr& ptr, float duration) const { - ensureCustomData(ptr); - - float &timeRemaining = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; - timeRemaining = duration; + ptr.getCellRef().setChargeFloat(duration); } float Light::getRemainingUsageTime (const MWWorld::Ptr& ptr) const { - ensureCustomData(ptr); - - return dynamic_cast (*ptr.getRefData().getCustomData()).mTime; + MWWorld::LiveCellRef *ref = ptr.get(); + if (ptr.getCellRef().getCharge() == -1) + return ref->mBase->mData.mTime; + else + return ptr.getCellRef().getChargeFloat(); } MWWorld::Ptr @@ -240,12 +219,6 @@ namespace MWClass return MWWorld::Ptr(&cell.get().insert(*ref), &cell); } - void Light::ensureCustomData (const MWWorld::Ptr& ptr) const - { - if (!ptr.getRefData().getCustomData()) - ptr.getRefData().setCustomData(new LightCustomData(ptr)); - } - bool Light::canSell (const MWWorld::Ptr& item, int npcServices) const { return npcServices & ESM::NPC::Lights; @@ -281,26 +254,6 @@ 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 (state); - - ensureCustomData (ptr); - - dynamic_cast (*ptr.getRefData().getCustomData()).mTime = state2.mTime; - } - - void Light::writeAdditionalState (const MWWorld::Ptr& ptr, ESM::ObjectState& state) - const - { - ESM::LightState& state2 = dynamic_cast (state); - - ensureCustomData (ptr); - - state2.mTime = dynamic_cast (*ptr.getRefData().getCustomData()).mTime; - } - std::string Light::getSound(const MWWorld::Ptr& ptr) const { return ptr.get()->mBase->mSound; diff --git a/apps/openmw/mwclass/light.hpp b/apps/openmw/mwclass/light.hpp index a3b8412611..8658375b77 100644 --- a/apps/openmw/mwclass/light.hpp +++ b/apps/openmw/mwclass/light.hpp @@ -10,17 +10,15 @@ namespace MWClass virtual MWWorld::Ptr copyToCellImpl(const MWWorld::Ptr &ptr, MWWorld::CellStore &cell) const; - void ensureCustomData (const MWWorld::Ptr& ptr) const; - public: /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); @@ -75,14 +73,6 @@ namespace MWClass std::pair 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. - virtual std::string getSound(const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwclass/lockpick.cpp b/apps/openmw/mwclass/lockpick.cpp index 9df5870248..e78c43eee3 100644 --- a/apps/openmw/mwclass/lockpick.cpp +++ b/apps/openmw/mwclass/lockpick.cpp @@ -27,19 +27,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Lockpick::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Lockpick::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Lockpick::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Lockpick::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/lockpick.hpp b/apps/openmw/mwclass/lockpick.hpp index d4bdf3fa69..293a40be11 100644 --- a/apps/openmw/mwclass/lockpick.hpp +++ b/apps/openmw/mwclass/lockpick.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/misc.cpp b/apps/openmw/mwclass/misc.cpp index 1b4719c6e7..f9cfd8e0b6 100644 --- a/apps/openmw/mwclass/misc.cpp +++ b/apps/openmw/mwclass/misc.cpp @@ -12,6 +12,7 @@ #include "../mwworld/ptr.hpp" #include "../mwworld/actiontake.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/nullaction.hpp" @@ -43,19 +44,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Miscellaneous::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Miscellaneous::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Miscellaneous::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/misc.hpp b/apps/openmw/mwclass/misc.hpp index 53a8e050bd..23160d41c6 100644 --- a/apps/openmw/mwclass/misc.hpp +++ b/apps/openmw/mwclass/misc.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/npc.cpp b/apps/openmw/mwclass/npc.cpp index 641edcc834..cedff12918 100644 --- a/apps/openmw/mwclass/npc.cpp +++ b/apps/openmw/mwclass/npc.cpp @@ -296,21 +296,6 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); - // NPC stats - if (!ref->mBase->mFaction.empty()) - { - std::string faction = ref->mBase->mFaction; - Misc::StringUtils::toLower(faction); - if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) - { - data->mNpcStats.setFactionRank(faction, (int)ref->mBase->mNpdt52.mRank); - } - else - { - data->mNpcStats.setFactionRank(faction, (int)ref->mBase->mNpdt12.mRank); - } - } - // creature stats int gold=0; if(ref->mBase->mNpdtType != ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) @@ -361,16 +346,19 @@ namespace MWClass for (std::vector::const_iterator iter (race->mPowers.mList.begin()); iter!=race->mPowers.mList.end(); ++iter) { - data->mNpcStats.getSpells().add (*iter); + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) + data->mNpcStats.getSpells().add (spell); + else + std::cerr << "Warning: ignoring nonexistent race power '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; } - if (data->mNpcStats.getFactionRanks().size()) + if (!ref->mBase->mFaction.empty()) { static const int iAutoRepFacMod = MWBase::Environment::get().getWorld()->getStore().get() .find("iAutoRepFacMod")->getInt(); static const int iAutoRepLevMod = MWBase::Environment::get().getWorld()->getStore().get() .find("iAutoRepLevMod")->getInt(); - int rank = data->mNpcStats.getFactionRanks().begin()->second; + int rank = ref->mBase->getFactionRank(); data->mNpcStats.setReputation(iAutoRepFacMod * (rank+1) + iAutoRepLevMod * (data->mNpcStats.getLevel()-1)); } @@ -385,11 +373,19 @@ namespace MWClass // spells for (std::vector::const_iterator iter (ref->mBase->mSpells.mList.begin()); iter!=ref->mBase->mSpells.mList.end(); ++iter) - data->mNpcStats.getSpells().add (*iter); + { + if (const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(*iter)) + data->mNpcStats.getSpells().add (spell); + else + { + /// \todo add option to make this a fatal error message pop-up, but default to warning for vanilla compatibility + std::cerr << "Warning: ignoring nonexistent spell '" << *iter << "' on NPC '" << ref->mBase->mId << "'" << std::endl; + } + } // inventory - data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr), "", - MWBase::Environment::get().getWorld()->getStore()); + // setting ownership is used to make the NPC auto-equip his initial equipment only, and not bartered items + data->mInventoryStore.fill(ref->mBase->mInventory, getId(ptr)); data->mNpcStats.setGoldPool(gold); @@ -413,14 +409,14 @@ namespace MWClass MWBase::Environment::get().getWorld()->adjustPosition(ptr, force); } - void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Npc::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { renderingInterface.getActors().insertNPC(ptr); } - void Npc::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Npc::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - physics.addActor(ptr); + physics.addActor(ptr, model); MWBase::Environment::get().getMechanicsManager()->add(ptr); if (getCreatureStats(ptr).isDead()) MWBase::Environment::get().getWorld()->enableActorCollision(ptr, false); @@ -490,18 +486,7 @@ namespace MWClass if(!weapon.isEmpty() && weapon.getTypeName() != typeid(ESM::Weapon).name()) weapon = MWWorld::Ptr(); - // Reduce fatigue - // somewhat of a guess, but using the weapon weight makes sense - const float fFatigueAttackBase = store.find("fFatigueAttackBase")->getFloat(); - const float fFatigueAttackMult = store.find("fFatigueAttackMult")->getFloat(); - const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->getFloat(); - MWMechanics::DynamicStat fatigue = getCreatureStats(ptr).getFatigue(); - const float normalizedEncumbrance = getNormalizedEncumbrance(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); + MWMechanics::applyFatigueLoss(ptr, weapon); const float fCombatDistance = store.find("fCombatDistance")->getFloat(); float dist = fCombatDistance * (!weapon.isEmpty() ? @@ -562,34 +547,7 @@ namespace MWClass } else { - // Note: MCP contains an option to include Strength in hand-to-hand damage - // calculations. Some mods recommend using it, so we may want to include am - // option for it. - float minstrike = store.find("fMinHandToHandMult")->getFloat(); - float maxstrike = store.find("fMaxHandToHandMult")->getFloat(); - damage = stats.getSkill(weapskill).getModified(); - damage *= minstrike + ((maxstrike-minstrike)*stats.getAttackStrength()); - - healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) - || otherstats.getKnockedDown(); - if(stats.isWerewolf()) - { - healthdmg = true; - // GLOB instead of GMST because it gets updated during a quest - damage *= world->getGlobalFloat("werewolfclawmult"); - } - if(healthdmg) - damage *= store.find("fHandtoHandHealthPer")->getFloat(); - - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - if(stats.isWerewolf()) - { - const ESM::Sound *sound = world->getStore().get().searchRandom("WolfHit"); - if(sound) - sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f); - } - else - sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); + MWMechanics::getHandToHandDamage(ptr, victim, damage, healthdmg); } if(ptr.getRefData().getHandle() == "player") { @@ -626,7 +584,7 @@ namespace MWClass MWMechanics::applyElementalShields(ptr, victim); - if (!weapon.isEmpty() && MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) + if (MWMechanics::blockMeleeAttack(ptr, victim, weapon, damage)) damage = 0; if (healthdmg && damage > 0) @@ -654,10 +612,11 @@ namespace MWClass setOnPcHitMe = MWBase::Environment::get().getMechanicsManager()->actorAttacked(ptr, attacker); } + if(!object.isEmpty()) + getCreatureStats(ptr).setLastHitAttemptObject(object.getClass().getId(object)); + if(!successful) { - // TODO: Handle HitAttemptOnMe script function - // Missed sndMgr->playSound3D(ptr, "miss", 1.0f, 1.0f); return; @@ -1002,37 +961,6 @@ namespace MWClass return x; } - float Npc::getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const - { - MWBase::World *world = MWBase::Environment::get().getWorld(); - const MWWorld::Store &store = world->getStore().get(); - - const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); - - if (fallHeight >= fallDistanceMin) - { - const float acrobaticsSkill = ptr.getClass().getNpcStats (ptr).getSkill(ESM::Skill::Acrobatics).getModified(); - const NpcCustomData *npcdata = static_cast(ptr.getRefData().getCustomData()); - const float jumpSpellBonus = npcdata->mNpcStats.getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); - const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); - const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); - const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); - const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); - - float x = fallHeight - fallDistanceMin; - x -= (1.5 * acrobaticsSkill) + jumpSpellBonus; - x = std::max(0.0f, x); - - float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); - x = fallDistanceBase + fallDistanceMult * x; - x *= a; - - return x; - } - - return 0; - } - MWMechanics::Movement& Npc::getMovementSettings (const MWWorld::Ptr& ptr) const { ensureCustomData (ptr); @@ -1278,6 +1206,7 @@ namespace MWClass // TODO: I have no idea what these are supposed to do for NPCs since they use // voiced dialog for various conditions like health loss and combat taunts. Maybe // only for biped creatures? + if(name == "moan") return ""; if(name == "roar") @@ -1316,6 +1245,9 @@ namespace MWClass void Npc::readAdditionalState (const MWWorld::Ptr& ptr, const ESM::ObjectState& state) const { + if (!state.mHasCustomState) + return; + const ESM::NpcState& state2 = dynamic_cast (state); ensureCustomData(ptr); @@ -1343,6 +1275,12 @@ namespace MWClass { ESM::NpcState& state2 = dynamic_cast (state); + if (!ptr.getRefData().getCustomData()) + { + state.mHasCustomState = false; + return; + } + ensureCustomData (ptr); NpcCustomData& customData = dynamic_cast (*ptr.getRefData().getCustomData()); @@ -1372,7 +1310,7 @@ namespace MWClass { // Note we do not respawn moved references in the cell they were moved to. Instead they are respawned in the original cell. // This also means we cannot respawn dynamically placed references with no content file connection. - if (ptr.getCellRef().getRefNum().mContentFile != -1) + if (ptr.getCellRef().hasContentFile()) { if (ptr.getRefData().getCount() == 0) ptr.getRefData().setCount(1); @@ -1390,6 +1328,29 @@ namespace MWClass MWWorld::LiveCellRef *ref = ptr.get(); const ESM::InventoryList& list = ref->mBase->mInventory; MWWorld::ContainerStore& store = getContainerStore(ptr); - store.restock(list, ptr, ptr.getCellRef().getRefId(), ptr.getCellRef().getFaction()); + store.restock(list, ptr, ptr.getCellRef().getRefId()); + } + + int Npc::getBaseFightRating (const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + return ref->mBase->mAiData.mFight; + } + + bool Npc::isBipedal(const MWWorld::Ptr &ptr) const + { + return true; + } + + std::string Npc::getPrimaryFaction (const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + return ref->mBase->mFaction; + } + + int Npc::getPrimaryFactionRank (const MWWorld::Ptr& ptr) const + { + MWWorld::LiveCellRef *ref = ptr.get(); + return ref->mBase->getFactionRank(); } } diff --git a/apps/openmw/mwclass/npc.hpp b/apps/openmw/mwclass/npc.hpp index fd16e6f083..9aece7368c 100644 --- a/apps/openmw/mwclass/npc.hpp +++ b/apps/openmw/mwclass/npc.hpp @@ -50,10 +50,10 @@ namespace MWClass virtual std::string getId (const MWWorld::Ptr& ptr) const; ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual void adjustPosition(const MWWorld::Ptr& ptr, bool force) const; ///< Adjust position to stand on ground. Must be called post model load @@ -104,9 +104,6 @@ namespace MWClass virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) - virtual float getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const; - ///< Return amount of health points lost when falling - virtual MWMechanics::Movement& getMovementSettings (const MWWorld::Ptr& ptr) const; ///< Return desired movement. @@ -185,9 +182,16 @@ namespace MWClass return true; } + virtual bool isBipedal (const MWWorld::Ptr &ptr) const; + virtual void respawn (const MWWorld::Ptr& ptr) const; virtual void restock (const MWWorld::Ptr& ptr) const; + + virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; + + virtual std::string getPrimaryFaction(const MWWorld::Ptr &ptr) const; + virtual int getPrimaryFactionRank(const MWWorld::Ptr &ptr) const; }; } diff --git a/apps/openmw/mwclass/potion.cpp b/apps/openmw/mwclass/potion.cpp index 2da213c8d6..bd06f89fc8 100644 --- a/apps/openmw/mwclass/potion.cpp +++ b/apps/openmw/mwclass/potion.cpp @@ -11,6 +11,7 @@ #include "../mwworld/actiontake.hpp" #include "../mwworld/actionapply.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/nullaction.hpp" @@ -29,19 +30,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Potion::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Potion::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Potion::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Potion::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/potion.hpp b/apps/openmw/mwclass/potion.hpp index 4c407d161c..32e3901156 100644 --- a/apps/openmw/mwclass/potion.hpp +++ b/apps/openmw/mwclass/potion.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/probe.cpp b/apps/openmw/mwclass/probe.cpp index 82f4879864..a11725f267 100644 --- a/apps/openmw/mwclass/probe.cpp +++ b/apps/openmw/mwclass/probe.cpp @@ -27,19 +27,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Probe::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Probe::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Probe::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Probe::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/probe.hpp b/apps/openmw/mwclass/probe.hpp index 047cb8ed4f..bb90ac1531 100644 --- a/apps/openmw/mwclass/probe.hpp +++ b/apps/openmw/mwclass/probe.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/repair.cpp b/apps/openmw/mwclass/repair.cpp index 4b18aced0d..e9c4ac9b19 100644 --- a/apps/openmw/mwclass/repair.cpp +++ b/apps/openmw/mwclass/repair.cpp @@ -26,19 +26,17 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Repair::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Repair::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Repair::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Repair::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/repair.hpp b/apps/openmw/mwclass/repair.hpp index f89258234d..2589a4af35 100644 --- a/apps/openmw/mwclass/repair.hpp +++ b/apps/openmw/mwclass/repair.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/static.cpp b/apps/openmw/mwclass/static.cpp index c241935ab2..dbbe7e43a7 100644 --- a/apps/openmw/mwclass/static.cpp +++ b/apps/openmw/mwclass/static.cpp @@ -17,22 +17,20 @@ namespace MWClass return ptr.get()->mBase->mId; } - void Static::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Static::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { MWWorld::LiveCellRef *ref = ptr.get(); - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model, !ref->mBase->mPersistent); } } - void Static::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Static::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr); + physics.addObject(ptr, model); } std::string Static::getModel(const MWWorld::Ptr &ptr) const diff --git a/apps/openmw/mwclass/static.hpp b/apps/openmw/mwclass/static.hpp index 2ac2e86825..a94dff394d 100644 --- a/apps/openmw/mwclass/static.hpp +++ b/apps/openmw/mwclass/static.hpp @@ -15,10 +15,10 @@ namespace MWClass /// Return ID of \a ptr virtual std::string getId (const MWWorld::Ptr& ptr) const; - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwclass/weapon.cpp b/apps/openmw/mwclass/weapon.cpp index d2f88efef3..d1a44fd0e3 100644 --- a/apps/openmw/mwclass/weapon.cpp +++ b/apps/openmw/mwclass/weapon.cpp @@ -12,6 +12,7 @@ #include "../mwworld/actionequip.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/physicssystem.hpp" #include "../mwworld/nullaction.hpp" @@ -29,19 +30,17 @@ namespace MWClass return ref->mBase->mId; } - void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Weapon::insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const { - const std::string model = getModel(ptr); if (!model.empty()) { renderingInterface.getObjects().insertModel(ptr, model); } } - void Weapon::insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Weapon::insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const { - const std::string model = getModel(ptr); if(!model.empty()) - physics.addObject(ptr,true); + physics.addObject(ptr, model, true); } std::string Weapon::getModel(const MWWorld::Ptr &ptr) const @@ -190,9 +189,8 @@ namespace MWClass { return std::string("Item Weapon Longblade Up"); } - // Shortblade and thrown weapons - // thrown weapons may not be entirely correct - if (type == 0 || type == 11) + // Shortblade + if (type == 0) { return std::string("Item Weapon Shortblade Up"); } @@ -201,8 +199,8 @@ namespace MWClass { return std::string("Item Weapon Spear Up"); } - // Blunts and Axes - if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8) + // Blunts, Axes and Thrown weapons + if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11) { return std::string("Item Weapon Blunt Up"); } @@ -236,9 +234,8 @@ namespace MWClass { return std::string("Item Weapon Longblade Down"); } - // Shortblade and thrown weapons - // thrown weapons may not be entirely correct - if (type == 0 || type == 11) + // Shortblade + if (type == 0) { return std::string("Item Weapon Shortblade Down"); } @@ -247,8 +244,8 @@ namespace MWClass { return std::string("Item Weapon Spear Down"); } - // Blunts and Axes - if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8) + // Blunts, Axes and Thrown weapons + if (type == 3 || type == 4 || type == 5 || type == 7 || type == 8 || type == 11) { return std::string("Item Weapon Blunt Down"); } @@ -385,7 +382,7 @@ namespace MWClass std::pair Weapon::canBeEquipped(const MWWorld::Ptr &ptr, const MWWorld::Ptr &npc) const { - if (ptr.getCellRef().getCharge() == 0) + if (hasItemHealth(ptr) && ptr.getCellRef().getCharge() == 0) return std::make_pair(0, "#{sInventoryMessage1}"); std::pair, bool> slots_ = ptr.getClass().getEquipmentSlots(ptr); diff --git a/apps/openmw/mwclass/weapon.hpp b/apps/openmw/mwclass/weapon.hpp index 97ee102911..47f1c52514 100644 --- a/apps/openmw/mwclass/weapon.hpp +++ b/apps/openmw/mwclass/weapon.hpp @@ -15,10 +15,10 @@ namespace MWClass virtual std::string getId (const MWWorld::Ptr& ptr) const; ///< Return ID of \a ptr - virtual void insertObjectRendering (const MWWorld::Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObjectRendering (const MWWorld::Ptr& ptr, const std::string& model, MWRender::RenderingInterface& renderingInterface) const; ///< Add reference into a cell for rendering - virtual void insertObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObject(const MWWorld::Ptr& ptr, const std::string& model, MWWorld::PhysicsSystem& physics) const; virtual std::string getName (const MWWorld::Ptr& ptr) const; ///< \return name (the one that is to be presented to the user; not the internal one); diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp index eff54fbc01..1d1f655aaa 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.cpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include @@ -47,7 +48,7 @@ namespace MWDialogue { DialogueManager::DialogueManager (const Compiler::Extensions& extensions, bool scriptVerbose, Translation::Storage& translationDataStorage) : - mCompilerContext (MWScript::CompilerContext::Type_Dialgoue), + mCompilerContext (MWScript::CompilerContext::Type_Dialogue), mErrorStream(std::cout.rdbuf()),mErrorHandler(mErrorStream) , mTemporaryDispositionChange(0.f) , mPermanentDispositionChange(0.f), mScriptVerbose (scriptVerbose) @@ -78,7 +79,7 @@ namespace MWDialogue void DialogueManager::addTopic (const std::string& topic) { - mKnownTopics[Misc::StringUtils::lowerCase(topic)] = true; + mKnownTopics.insert( Misc::StringUtils::lowerCase(topic) ); } void DialogueManager::parseText (const std::string& text) @@ -102,8 +103,8 @@ namespace MWDialogue if (tok->isImplicitKeyword() && mTranslationDataStorage.hasTranslation()) continue; - if (std::find(mActorKnownTopics.begin(), mActorKnownTopics.end(), topicId) != mActorKnownTopics.end()) - mKnownTopics[topicId] = true; + if (mActorKnownTopics.count( topicId )) + mKnownTopics.insert( topicId ); } updateTopics(); @@ -227,7 +228,7 @@ namespace MWDialogue success = false; } - if (!success && mScriptVerbose) + if (!success) { std::cerr << "compiling failed (dialogue script)" << std::endl @@ -341,10 +342,10 @@ namespace MWDialogue if (filter.responseAvailable (*iter)) { std::string lower = Misc::StringUtils::lowerCase(iter->mId); - mActorKnownTopics.push_back (lower); + mActorKnownTopics.insert (lower); //does the player know the topic? - if (mKnownTopics.find (lower) != mKnownTopics.end()) + if (mKnownTopics.count(lower)) { keywordList.push_back (iter->mId); } @@ -635,20 +636,20 @@ namespace MWDialogue { ESM::DialogueState state; - for (std::map::const_iterator iter (mKnownTopics.begin()); + for (std::set::const_iterator iter (mKnownTopics.begin()); iter!=mKnownTopics.end(); ++iter) - if (iter->second) - state.mKnownTopics.push_back (iter->first); + { + state.mKnownTopics.push_back (*iter); + } - state.mModFactionReaction = mModFactionReaction; + state.mChangedFactionReaction = mChangedFactionReaction; writer.startRecord (ESM::REC_DIAS); state.save (writer); writer.endRecord (ESM::REC_DIAS); - progress.increaseProgress(); } - void DialogueManager::readRecord (ESM::ESMReader& reader, int32_t type) + void DialogueManager::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_DIAS) { @@ -660,9 +661,9 @@ namespace MWDialogue for (std::vector::const_iterator iter (state.mKnownTopics.begin()); iter!=state.mKnownTopics.end(); ++iter) if (store.get().search (*iter)) - mKnownTopics.insert (std::make_pair (*iter, true)); + mKnownTopics.insert (*iter); - mModFactionReaction = state.mModFactionReaction; + mChangedFactionReaction = state.mChangedFactionReaction; } } @@ -675,10 +676,23 @@ namespace MWDialogue MWBase::Environment::get().getWorld()->getStore().get().find(fact1); MWBase::Environment::get().getWorld()->getStore().get().find(fact2); - std::map& map = mModFactionReaction[fact1]; - if (map.find(fact2) == map.end()) - map[fact2] = 0; - map[fact2] += diff; + int newValue = getFactionReaction(faction1, faction2) + diff; + + std::map& map = mChangedFactionReaction[fact1]; + map[fact2] = newValue; + } + + void DialogueManager::setFactionReaction(const std::string &faction1, const std::string &faction2, int absolute) + { + std::string fact1 = Misc::StringUtils::lowerCase(faction1); + std::string fact2 = Misc::StringUtils::lowerCase(faction2); + + // Make sure the factions exist + MWBase::Environment::get().getWorld()->getStore().get().find(fact1); + MWBase::Environment::get().getWorld()->getStore().get().find(fact2); + + std::map& map = mChangedFactionReaction[fact1]; + map[fact2] = absolute; } int DialogueManager::getFactionReaction(const std::string &faction1, const std::string &faction2) const @@ -686,10 +700,9 @@ namespace MWDialogue std::string fact1 = Misc::StringUtils::lowerCase(faction1); std::string fact2 = Misc::StringUtils::lowerCase(faction2); - ModFactionReactionMap::const_iterator map = mModFactionReaction.find(fact1); - int diff = 0; - if (map != mModFactionReaction.end() && map->second.find(fact2) != map->second.end()) - diff = map->second.at(fact2); + ModFactionReactionMap::const_iterator map = mChangedFactionReaction.find(fact1); + if (map != mChangedFactionReaction.end() && map->second.find(fact2) != map->second.end()) + return map->second.at(fact2); const ESM::Faction* faction = MWBase::Environment::get().getWorld()->getStore().get().find(fact1); @@ -697,9 +710,9 @@ namespace MWDialogue for (; it != faction->mReactions.end(); ++it) { if (Misc::StringUtils::ciEqual(it->first, fact2)) - return it->second + diff; + return it->second; } - return diff; + return 0; } void DialogueManager::clearInfoActor(const MWWorld::Ptr &actor) const diff --git a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp index 9c3d0d54b7..aec503e87d 100644 --- a/apps/openmw/mwdialogue/dialoguemanagerimp.hpp +++ b/apps/openmw/mwdialogue/dialoguemanagerimp.hpp @@ -4,7 +4,7 @@ #include "../mwbase/dialoguemanager.hpp" #include -#include +#include #include #include @@ -23,13 +23,13 @@ namespace MWDialogue class DialogueManager : public MWBase::DialogueManager { std::map mDialogueMap; - std::map mKnownTopics;// Those are the topics the player knows. + std::set mKnownTopics;// Those are the topics the player knows. // Modified faction reactions. > typedef std::map > ModFactionReactionMap; - ModFactionReactionMap mModFactionReaction; + ModFactionReactionMap mChangedFactionReaction; - std::list mActorKnownTopics; + std::set mActorKnownTopics; Translation::Storage& mTranslationDataStorage; MWScript::CompilerContext mCompilerContext; @@ -92,11 +92,13 @@ namespace MWDialogue virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - virtual void readRecord (ESM::ESMReader& reader, int32_t type); + virtual void readRecord (ESM::ESMReader& reader, uint32_t type); /// Changes faction1's opinion of faction2 by \a diff. virtual void modFactionReaction (const std::string& faction1, const std::string& faction2, int diff); + virtual void setFactionReaction (const std::string& faction1, const std::string& faction2, int absolute); + /// @return faction1's opinion of faction2 virtual int getFactionReaction (const std::string& faction1, const std::string& faction2) const; diff --git a/apps/openmw/mwdialogue/filter.cpp b/apps/openmw/mwdialogue/filter.cpp index 629d99cc2a..adb7d38923 100644 --- a/apps/openmw/mwdialogue/filter.cpp +++ b/apps/openmw/mwdialogue/filter.cpp @@ -67,14 +67,11 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const if (isCreature) return false; - MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats (mActor); - std::map::const_iterator iter = stats.getFactionRanks().find ( Misc::StringUtils::lowerCase (info.mFaction)); - - if (iter==stats.getFactionRanks().end()) + if (!Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), info.mFaction)) return false; // check rank - if (iter->second < info.mData.mRank) + if (mActor.getClass().getPrimaryFactionRank(mActor) < info.mData.mRank) return false; } else if (info.mData.mRank != -1) @@ -83,13 +80,8 @@ bool MWDialogue::Filter::testActor (const ESM::DialInfo& info) const return false; // Rank requirement, but no faction given. Use the actor's faction, if there is one. - MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats (mActor); - - if (!stats.getFactionRanks().size()) - return false; - // check rank - if (stats.getFactionRanks().begin()->second < info.mData.mRank) + if (mActor.getClass().getPrimaryFactionRank(mActor) < info.mData.mRank) return false; } @@ -126,7 +118,7 @@ bool MWDialogue::Filter::testPlayer (const ESM::DialInfo& info) const if (!info.mCell.empty()) { // supports partial matches, just like getPcCell - const std::string& playerCell = player.getCell()->getCell()->mName; + const std::string& playerCell = MWBase::Environment::get().getWorld()->getCellName(player.getCell()); bool match = playerCell.length()>=info.mCell.length() && Misc::StringUtils::ciEqual(playerCell.substr (0, info.mCell.length()), info.mCell); if (!match) @@ -336,12 +328,10 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_RankRequirement: { - if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) + std::string faction = mActor.getClass().getPrimaryFaction(mActor); + if (faction.empty()) return 0; - std::string faction = - mActor.getClass().getNpcStats (mActor).getFactionRanks().begin()->first; - int rank = getFactionRank (player, faction); if (rank>=9) @@ -376,15 +366,14 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con case SelectWrapper::Function_FactionRankDiff: { - if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) + std::string faction = mActor.getClass().getPrimaryFaction(mActor); + + if (faction.empty()) return 0; - const std::pair faction = - *mActor.getClass().getNpcStats (mActor).getFactionRanks().begin(); - - int rank = getFactionRank (player, faction.first); - - return rank-faction.second; + int rank = getFactionRank (player, faction); + int npcRank = mActor.getClass().getPrimaryFactionRank(mActor); + return rank-npcRank; } case SelectWrapper::Function_WerewolfKills: @@ -396,11 +385,10 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con { bool low = select.getFunction()==SelectWrapper::Function_RankLow; - if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) - return 0; + std::string factionId = mActor.getClass().getPrimaryFaction(mActor); - std::string factionId = - mActor.getClass().getNpcStats (mActor).getFactionRanks().begin()->first; + if (factionId.empty()) + return 0; int value = 0; @@ -417,6 +405,21 @@ int MWDialogue::Filter::getSelectStructInteger (const SelectWrapper& select) con return value; } + case SelectWrapper::Function_CreatureTargetted: + + { + MWWorld::Ptr target; + mActor.getClass().getCreatureStats(mActor).getAiSequence().getCombatTarget(target); + if (target) + { + if (target.getClass().isNpc() && target.getClass().getNpcStats(target).isWerewolf()) + return 2; + if (target.getTypeName() == typeid(ESM::Creature).name()) + return 1; + } + } + return 0; + default: throw std::runtime_error ("unknown integer select function"); @@ -439,7 +442,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotFaction: - return !Misc::StringUtils::ciEqual(mActor.get()->mBase->mFaction, select.getName()); + return !Misc::StringUtils::ciEqual(mActor.getClass().getPrimaryFaction(mActor), select.getName()); case SelectWrapper::Function_NotClass: @@ -451,7 +454,8 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_NotCell: - return !Misc::StringUtils::ciEqual(mActor.getCell()->getCell()->mName, select.getName()); + return !Misc::StringUtils::ciEqual(MWBase::Environment::get().getWorld()->getCellName(mActor.getCell()) + , select.getName()); case SelectWrapper::Function_NotLocal: { @@ -478,8 +482,7 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_SameFaction: - return mActor.getClass().getNpcStats (mActor).isSameFaction ( - player.getClass().getNpcStats (player)); + return player.getClass().getNpcStats (player).isInFaction(mActor.getClass().getPrimaryFaction(mActor)); case SelectWrapper::Function_PcCommonDisease: @@ -496,11 +499,10 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co case SelectWrapper::Function_PcExpelled: { - if (mActor.getClass().getNpcStats (mActor).getFactionRanks().empty()) - return false; + std::string faction = mActor.getClass().getPrimaryFaction(mActor); - std::string faction = - mActor.getClass().getNpcStats (mActor).getFactionRanks().begin()->first; + if (faction.empty()) + return false; return player.getClass().getNpcStats(player).getExpelled(faction); } @@ -531,10 +533,6 @@ bool MWDialogue::Filter::getSelectStructBoolean (const SelectWrapper& select) co return MWBase::Environment::get().getMechanicsManager()->isAggressive(mActor, MWBase::Environment::get().getWorld()->getPlayerPtr()); - case SelectWrapper::Function_CreatureTargetted: - - return mActor.getClass().getCreatureStats (mActor).getCreatureTargetted(); - case SelectWrapper::Function_Werewolf: return mActor.getClass().getNpcStats (mActor).isWerewolf(); @@ -549,7 +547,7 @@ int MWDialogue::Filter::getFactionRank (const MWWorld::Ptr& actor, const std::st { MWMechanics::NpcStats& stats = actor.getClass().getNpcStats (actor); - std::map::const_iterator iter = stats.getFactionRanks().find (factionId); + std::map::const_iterator iter = stats.getFactionRanks().find (Misc::StringUtils::lowerCase(factionId)); if (iter==stats.getFactionRanks().end()) return -1; @@ -603,6 +601,17 @@ const ESM::DialInfo* MWDialogue::Filter::search (const ESM::Dialogue& dialogue, return suitableInfos[0]; } +std::vector MWDialogue::Filter::listAll (const ESM::Dialogue& dialogue) const +{ + std::vector infos; + for (ESM::Dialogue::InfoContainer::const_iterator iter = dialogue.mInfo.begin(); iter!=dialogue.mInfo.end(); ++iter) + { + if (testActor (*iter)) + infos.push_back(&*iter); + } + return infos; +} + std::vector MWDialogue::Filter::list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition) const { diff --git a/apps/openmw/mwdialogue/filter.hpp b/apps/openmw/mwdialogue/filter.hpp index 7e7f2b6f54..d41b7aa1ef 100644 --- a/apps/openmw/mwdialogue/filter.hpp +++ b/apps/openmw/mwdialogue/filter.hpp @@ -55,7 +55,11 @@ namespace MWDialogue std::vector list (const ESM::Dialogue& dialogue, bool fallbackToInfoRefusal, bool searchAll, bool invertDisposition=false) const; - ///< \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. + ///< List all infos that could be used on the given actor, using the current runtime state of the actor. + /// \note If fallbackToInfoRefusal is used, the returned DialInfo might not be from the supplied ESM::Dialogue. + + std::vector listAll (const ESM::Dialogue& dialogue) const; + ///< List all infos that could possibly be used on the given actor, ignoring runtime state filters and ignoring player filters. const ESM::DialInfo* search (const ESM::Dialogue& dialogue, const bool fallbackToInfoRefusal) const; ///< Get a matching response for the requested dialogue. diff --git a/apps/openmw/mwdialogue/hypertextparser.cpp b/apps/openmw/mwdialogue/hypertextparser.cpp index 776edac949..aa748e772e 100644 --- a/apps/openmw/mwdialogue/hypertextparser.cpp +++ b/apps/openmw/mwdialogue/hypertextparser.cpp @@ -56,13 +56,17 @@ namespace MWDialogue keywordList.sort(Misc::StringUtils::ciLess); KeywordSearch keywordSearch; - KeywordSearch::Match match; for (std::list::const_iterator it = keywordList.begin(); it != keywordList.end(); ++it) keywordSearch.seed(*it, 0 /*unused*/); - for (std::string::const_iterator it = text.begin(); it != text.end() && keywordSearch.search(it, text.end(), match, text.begin()); it = match.mEnd) - tokens.push_back(Token(std::string(match.mBeg, match.mEnd), Token::ImplicitKeyword)); + std::vector::Match> matches; + keywordSearch.highlightKeywords(text.begin(), text.end(), matches); + + for (std::vector::Match>::const_iterator it = matches.begin(); it != matches.end(); ++it) + { + tokens.push_back(Token(std::string(it->mBeg, it->mEnd), Token::ImplicitKeyword)); + } } size_t removePseudoAsterisks(std::string & phrase) diff --git a/apps/openmw/mwdialogue/journalentry.cpp b/apps/openmw/mwdialogue/journalentry.cpp index b92d7cacea..9f07f7b6fc 100644 --- a/apps/openmw/mwdialogue/journalentry.cpp +++ b/apps/openmw/mwdialogue/journalentry.cpp @@ -89,7 +89,7 @@ namespace MWDialogue for (ESM::Dialogue::InfoContainer::const_iterator iter (dialogue->mInfo.begin()); iter!=dialogue->mInfo.end(); ++iter) - if (iter->mData.mDisposition==index) /// \todo cleanup info structure + if (iter->mData.mJournalIndex==index) { return iter->mId; } diff --git a/apps/openmw/mwdialogue/journalimp.cpp b/apps/openmw/mwdialogue/journalimp.cpp index 9497347e3a..99dab0cf8b 100644 --- a/apps/openmw/mwdialogue/journalimp.cpp +++ b/apps/openmw/mwdialogue/journalimp.cpp @@ -92,9 +92,7 @@ namespace MWDialogue quest.addEntry (entry); // we are doing slicing on purpose here - std::vector empty; - std::string notification = "#{sJournalEntry}"; - MWBase::Environment::get().getWindowManager()->messageBox (notification, empty); + MWBase::Environment::get().getWindowManager()->messageBox ("#{sJournalEntry}"); } void Journal::setJournalIndex (const std::string& id, int index) @@ -189,7 +187,6 @@ namespace MWDialogue writer.startRecord (ESM::REC_QUES); state.save (writer); writer.endRecord (ESM::REC_QUES); - progress.increaseProgress(); for (Topic::TEntryIter iter (quest.begin()); iter!=quest.end(); ++iter) { @@ -200,7 +197,6 @@ namespace MWDialogue writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); - progress.increaseProgress(); } } @@ -212,7 +208,6 @@ namespace MWDialogue writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); - progress.increaseProgress(); } for (TTopicIter iter (mTopics.begin()); iter!=mTopics.end(); ++iter) @@ -228,14 +223,13 @@ namespace MWDialogue writer.startRecord (ESM::REC_JOUR); entry.save (writer); writer.endRecord (ESM::REC_JOUR); - progress.increaseProgress(); } } } - void Journal::readRecord (ESM::ESMReader& reader, int32_t type) + void Journal::readRecord (ESM::ESMReader& reader, uint32_t type) { - if (type==ESM::REC_JOUR) + if (type==ESM::REC_JOUR || type==ESM::REC_JOUR_LEGACY) { ESM::JournalEntry record; record.load (reader); @@ -265,7 +259,12 @@ namespace MWDialogue record.load (reader); if (isThere (record.mTopic)) - mQuests.insert (std::make_pair (record.mTopic, record)); + { + std::pair result = mQuests.insert (std::make_pair (record.mTopic, record)); + // reapply quest index, this is to handle users upgrading from only + // Morrowind.esm (no quest states) to Morrowind.esm + Tribunal.esm + result.first->second.setIndex(record.mState); + } } } } diff --git a/apps/openmw/mwdialogue/journalimp.hpp b/apps/openmw/mwdialogue/journalimp.hpp index d15b909fa0..7f26a5bb90 100644 --- a/apps/openmw/mwdialogue/journalimp.hpp +++ b/apps/openmw/mwdialogue/journalimp.hpp @@ -71,7 +71,7 @@ namespace MWDialogue virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - virtual void readRecord (ESM::ESMReader& reader, int32_t type); + virtual void readRecord (ESM::ESMReader& reader, uint32_t type); }; } diff --git a/apps/openmw/mwdialogue/keywordsearch.hpp b/apps/openmw/mwdialogue/keywordsearch.hpp index 51508890c1..c4e1d75538 100644 --- a/apps/openmw/mwdialogue/keywordsearch.hpp +++ b/apps/openmw/mwdialogue/keywordsearch.hpp @@ -28,6 +28,8 @@ public: void seed (string_t keyword, value_t value) { + if (keyword.empty()) + return; seed_impl (/*std::move*/ (keyword), /*std::move*/ (value), 0, mRoot); } @@ -66,19 +68,26 @@ public: return false; } - bool search (Point beg, Point end, Match & match, Point start) + static bool sortMatches(const Match& left, const Match& right) { + return left.mBeg < right.mBeg; + } + + void highlightKeywords (Point beg, Point end, std::vector& out) + { + std::vector matches; for (Point i = beg; i != end; ++i) { // check if previous character marked start of new word - if (i != start) + if (i != beg) { Point prev = i; - --prev; + --prev; if(isalpha(*prev)) continue; } + // check first character typename Entry::childen_t::iterator candidate = mRoot.mChildren.find (std::tolower (*i, mLocale)); @@ -137,16 +146,57 @@ public: if (t != candidate->second.mKeyword.end ()) continue; - // we did it, report the good news + // found a keyword, but there might still be longer keywords that start somewhere _within_ this keyword + // we will resolve these overlapping keywords later, choosing the longest one in case of conflict + Match match; match.mValue = candidate->second.mValue; match.mBeg = i; match.mEnd = k; - return true; + matches.push_back(match); + break; } } - // no match in range, report the bad news - return false; + // resolve overlapping keywords + while (matches.size()) + { + int longestKeywordSize = 0; + typename std::vector::iterator longestKeyword = matches.begin(); + for (typename std::vector::iterator it = matches.begin(); it != matches.end(); ++it) + { + int size = it->mEnd - it->mBeg; + if (size > longestKeywordSize) + { + longestKeywordSize = size; + longestKeyword = it; + } + + typename std::vector::iterator next = it; + ++next; + + if (next == matches.end()) + break; + + if (it->mEnd <= next->mBeg) + { + break; // no overlap + } + } + + Match keyword = *longestKeyword; + matches.erase(longestKeyword); + out.push_back(keyword); + // erase anything that overlaps with the keyword we just added to the output + for (typename std::vector::iterator it = matches.begin(); it != matches.end();) + { + if (it->mBeg < keyword.mEnd && it->mEnd > keyword.mBeg) + it = matches.erase(it); + else + ++it; + } + } + + std::sort(out.begin(), out.end(), sortMatches); } private: diff --git a/apps/openmw/mwdialogue/quest.cpp b/apps/openmw/mwdialogue/quest.cpp index a699286a15..a9e39b3798 100644 --- a/apps/openmw/mwdialogue/quest.cpp +++ b/apps/openmw/mwdialogue/quest.cpp @@ -75,7 +75,7 @@ namespace MWDialogue iter!=dialogue->mInfo.end(); ++iter) if (iter->mId == entry.mInfoId) { - index = iter->mData.mDisposition; /// \todo cleanup info structure + index = iter->mData.mJournalIndex; break; } diff --git a/apps/openmw/mwdialogue/scripttest.cpp b/apps/openmw/mwdialogue/scripttest.cpp new file mode 100644 index 0000000000..3f46546100 --- /dev/null +++ b/apps/openmw/mwdialogue/scripttest.cpp @@ -0,0 +1,128 @@ +#include "scripttest.hpp" + +#include + +#include "../mwworld/manualref.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/scriptmanager.hpp" + +#include "../mwscript/compilercontext.hpp" + +#include +#include +#include +#include +#include +#include + +#include "filter.hpp" + +namespace +{ + +void test(const MWWorld::Ptr& actor, int &compiled, int &total, const Compiler::Extensions* extensions, int warningsMode) +{ + MWDialogue::Filter filter(actor, 0, false); + + MWScript::CompilerContext compilerContext(MWScript::CompilerContext::Type_Dialogue); + compilerContext.setExtensions(extensions); + std::ostream errorStream(std::cout.rdbuf()); + Compiler::StreamErrorHandler errorHandler(errorStream); + errorHandler.setWarningsMode (warningsMode); + + const MWWorld::Store& dialogues = MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator it = dialogues.begin(); it != dialogues.end(); ++it) + { + std::vector infos = filter.listAll(*it); + + for (std::vector::iterator it = infos.begin(); it != infos.end(); ++it) + { + const ESM::DialInfo* info = *it; + if (!info->mResultScript.empty()) + { + bool success = true; + ++total; + try + { + errorHandler.reset(); + + std::istringstream input (info->mResultScript + "\n"); + + Compiler::Scanner scanner (errorHandler, input, extensions); + + Compiler::Locals locals; + + std::string actorScript = actor.getClass().getScript(actor); + + if (!actorScript.empty()) + { + // grab local variables from actor's script, if available. + locals = MWBase::Environment::get().getScriptManager()->getLocals (actorScript); + } + + Compiler::ScriptParser parser(errorHandler, compilerContext, locals, false); + + scanner.scan (parser); + + if (!errorHandler.isGood()) + success = false; + + ++compiled; + } + catch (const Compiler::SourceException& /* error */) + { + // error has already been reported via error handler + success = false; + } + catch (const std::exception& error) + { + std::cerr << std::string ("Dialogue error: An exception has been thrown: ") + error.what() << std::endl; + success = false; + } + + if (!success) + { + std::cerr + << "compiling failed (dialogue script)" << std::endl + << info->mResultScript + << std::endl << std::endl; + } + } + } + } +} + +} + +namespace MWDialogue +{ + +namespace ScriptTest +{ + + std::pair compileAll(const Compiler::Extensions *extensions, int warningsMode) + { + int compiled = 0, total = 0; + const MWWorld::Store& npcs = MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator it = npcs.begin(); it != npcs.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); + test(ref.getPtr(), compiled, total, extensions, warningsMode); + } + + const MWWorld::Store& creatures = MWBase::Environment::get().getWorld()->getStore().get(); + for (MWWorld::Store::iterator it = creatures.begin(); it != creatures.end(); ++it) + { + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), it->mId); + test(ref.getPtr(), compiled, total, extensions, warningsMode); + } + return std::make_pair(total, compiled); + } + +} + +} diff --git a/apps/openmw/mwdialogue/scripttest.hpp b/apps/openmw/mwdialogue/scripttest.hpp new file mode 100644 index 0000000000..0ac2597256 --- /dev/null +++ b/apps/openmw/mwdialogue/scripttest.hpp @@ -0,0 +1,20 @@ +#ifndef OPENMW_MWDIALOGUE_SCRIPTTEST_H +#define OPENMW_MWDIALOGUE_SCRIPTTEST_H + +#include + +namespace MWDialogue +{ + +namespace ScriptTest +{ + +/// Attempt to compile all dialogue scripts, use for verification purposes +/// @return A pair containing +std::pair compileAll(const Compiler::Extensions* extensions, int warningsMode); + +} + +} + +#endif diff --git a/apps/openmw/mwdialogue/selectwrapper.cpp b/apps/openmw/mwdialogue/selectwrapper.cpp index 3f22998f0e..fa0fbfe136 100644 --- a/apps/openmw/mwdialogue/selectwrapper.cpp +++ b/apps/openmw/mwdialogue/selectwrapper.cpp @@ -205,6 +205,7 @@ MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const Function_Reputation, Function_FactionRankDiff, Function_WerewolfKills, Function_RankLow, Function_RankHigh, + Function_CreatureTargetted, Function_None // end marker }; @@ -225,7 +226,6 @@ MWDialogue::SelectWrapper::Type MWDialogue::SelectWrapper::getType() const Function_PcVampire, Function_TalkedToPc, Function_Alarmed, Function_Detected, Function_Attacked, Function_ShouldAttack, - Function_CreatureTargetted, Function_Werewolf, Function_None // end marker }; diff --git a/apps/openmw/mwgui/alchemywindow.cpp b/apps/openmw/mwgui/alchemywindow.cpp index b9e0044cef..b28e4de09f 100644 --- a/apps/openmw/mwgui/alchemywindow.cpp +++ b/apps/openmw/mwgui/alchemywindow.cpp @@ -1,7 +1,8 @@ #include "alchemywindow.hpp" #include -#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -9,8 +10,12 @@ #include "../mwbase/windowmanager.hpp" #include "../mwmechanics/magiceffects.hpp" +#include "../mwmechanics/alchemy.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" + +#include #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" @@ -24,6 +29,7 @@ namespace MWGui , mApparatus (4) , mIngredients (4) , mSortModel(NULL) + , mAlchemy(new MWMechanics::Alchemy()) { getWidget(mCreateButton, "CreateButton"); getWidget(mCancelButton, "CancelButton"); @@ -63,7 +69,7 @@ namespace MWGui std::string name = mNameEdit->getCaption(); boost::algorithm::trim(name); - MWMechanics::Alchemy::Result result = mAlchemy.create (mNameEdit->getCaption ()); + MWMechanics::Alchemy::Result result = mAlchemy->create (mNameEdit->getCaption ()); if (result == MWMechanics::Alchemy::Result_NoName) { @@ -118,7 +124,7 @@ namespace MWGui void AlchemyWindow::open() { - mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); + mAlchemy->setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); InventoryItemModel* model = new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr()); mSortModel = new SortFilterItemModel(model); @@ -129,10 +135,10 @@ namespace MWGui int index = 0; - mAlchemy.setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); + mAlchemy->setAlchemist (MWBase::Environment::get().getWorld()->getPlayerPtr()); - for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy.beginTools()); - iter!=mAlchemy.endTools() && index (mApparatus.size()); ++iter, ++index) + for (MWMechanics::Alchemy::TToolsIterator iter (mAlchemy->beginTools()); + iter!=mAlchemy->endTools() && index (mApparatus.size()); ++iter, ++index) { mApparatus.at (index)->setItem(*iter); mApparatus.at (index)->clearUserStrings(); @@ -147,7 +153,7 @@ namespace MWGui } void AlchemyWindow::exit() { - mAlchemy.clear(); + mAlchemy->clear(); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Alchemy); MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Inventory); } @@ -161,7 +167,7 @@ namespace MWGui void AlchemyWindow::onSelectedItem(int index) { MWWorld::Ptr item = mSortModel->getItem(index).mBase; - int res = mAlchemy.addIngredient(item); + int res = mAlchemy->addIngredient(item); if (res != -1) { @@ -174,20 +180,20 @@ namespace MWGui void AlchemyWindow::update() { - std::string suggestedName = mAlchemy.suggestPotionName(); + std::string suggestedName = mAlchemy->suggestPotionName(); if (suggestedName != mSuggestedPotionName) mNameEdit->setCaptionWithReplacing(suggestedName); mSuggestedPotionName = suggestedName; mSortModel->clearDragItems(); - MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy.beginIngredients (); + MWMechanics::Alchemy::TIngredientsIterator it = mAlchemy->beginIngredients (); for (int i=0; i<4; ++i) { ItemWidget* ingredient = mIngredients[i]; MWWorld::Ptr item; - if (it != mAlchemy.endIngredients ()) + if (it != mAlchemy->endIngredients ()) { item = *it; ++it; @@ -214,7 +220,7 @@ namespace MWGui mItemView->update(); - std::set effectIds = mAlchemy.listEffects(); + std::set effectIds = mAlchemy->listEffects(); Widgets::SpellEffectList list; for (std::set::iterator it = effectIds.begin(); it != effectIds.end(); ++it) { @@ -249,7 +255,7 @@ namespace MWGui { for (int i=0; i<4; ++i) if (mIngredients[i] == ingredient) - mAlchemy.removeIngredient (i); + mAlchemy->removeIngredient (i); update(); } diff --git a/apps/openmw/mwgui/alchemywindow.hpp b/apps/openmw/mwgui/alchemywindow.hpp index 36f540c1b6..69fff8c674 100644 --- a/apps/openmw/mwgui/alchemywindow.hpp +++ b/apps/openmw/mwgui/alchemywindow.hpp @@ -3,11 +3,14 @@ #include -#include "../mwmechanics/alchemy.hpp" - #include "widgets.hpp" #include "windowbase.hpp" +namespace MWMechanics +{ + class Alchemy; +} + namespace MWGui { class ItemView; @@ -45,7 +48,7 @@ namespace MWGui void update(); - MWMechanics::Alchemy mAlchemy; + std::auto_ptr mAlchemy; std::vector mApparatus; std::vector mIngredients; diff --git a/apps/openmw/mwgui/backgroundimage.cpp b/apps/openmw/mwgui/backgroundimage.cpp index 1e87c0ff19..9c07c5780b 100644 --- a/apps/openmw/mwgui/backgroundimage.cpp +++ b/apps/openmw/mwgui/backgroundimage.cpp @@ -5,14 +5,14 @@ namespace MWGui { -void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool correct) +void BackgroundImage::setBackgroundImage (const std::string& image, bool fixedRatio, bool stretch) { if (mChild) { MyGUI::Gui::getInstance().destroyWidget(mChild); mChild = NULL; } - if (correct) + if (!stretch) { setImageTexture("black.png"); diff --git a/apps/openmw/mwgui/backgroundimage.hpp b/apps/openmw/mwgui/backgroundimage.hpp index 3d1a61eaf7..8c963b7620 100644 --- a/apps/openmw/mwgui/backgroundimage.hpp +++ b/apps/openmw/mwgui/backgroundimage.hpp @@ -18,9 +18,9 @@ namespace MWGui /** * @param fixedRatio Use a fixed ratio of 4:3, regardless of the image dimensions - * @param correct Add black bars? + * @param stretch Stretch to fill the whole screen, or add black bars? */ - void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool correct=true); + void setBackgroundImage (const std::string& image, bool fixedRatio=true, bool stretch=true); virtual void setSize (const MyGUI::IntSize &_value); virtual void setCoord (const MyGUI::IntCoord &_value); diff --git a/apps/openmw/mwgui/birth.cpp b/apps/openmw/mwgui/birth.cpp index a7f90c00ba..668253712d 100644 --- a/apps/openmw/mwgui/birth.cpp +++ b/apps/openmw/mwgui/birth.cpp @@ -1,12 +1,16 @@ #include "birth.hpp" -#include +#include +#include +#include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/esmstore.hpp" #include "widgets.hpp" @@ -80,8 +84,6 @@ namespace MWGui if (Misc::StringUtils::ciEqual(*mBirthList->getItemDataAt(i), birthId)) { mBirthList->setIndexSelected(i); - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); break; } } @@ -116,9 +118,6 @@ namespace MWGui if (_index == MyGUI::ITEM_NONE) return; - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); - const std::string *birthId = mBirthList->getItemDataAt(_index); if (Misc::StringUtils::ciEqual(mCurrentBirthId, *birthId)) return; @@ -233,7 +232,7 @@ namespace MWGui for (std::vector::const_iterator it = categories[category].spells.begin(); it != end; ++it) { const std::string &spellId = *it; - spellWidget = mSpellArea->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + boost::lexical_cast(i)); + spellWidget = mSpellArea->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("Spell") + MyGUI::utility::toString(i)); spellWidget->setSpellId(spellId); mSpellItems.push_back(spellWidget); diff --git a/apps/openmw/mwgui/birth.hpp b/apps/openmw/mwgui/birth.hpp index 257dc6fefe..0a84bb4e98 100644 --- a/apps/openmw/mwgui/birth.hpp +++ b/apps/openmw/mwgui/birth.hpp @@ -3,11 +3,6 @@ #include "windowbase.hpp" -/* - This file contains the dialog for choosing a birth sign. - Layout is defined by resources/mygui/openmw_chargen_race.layout. - */ - namespace MWGui { class BirthDialog : public WindowModal diff --git a/apps/openmw/mwgui/bookpage.cpp b/apps/openmw/mwgui/bookpage.cpp index 57cb3e7a9c..c9cfc8c2c1 100644 --- a/apps/openmw/mwgui/bookpage.cpp +++ b/apps/openmw/mwgui/bookpage.cpp @@ -195,8 +195,20 @@ struct TypesetBookImpl : TypesetBook struct TypesetBookImpl::Typesetter : BookTypesetter { + struct PartialText { + StyleImpl *mStyle; + Utf8Stream::Point mBegin; + Utf8Stream::Point mEnd; + int mWidth; + + PartialText( StyleImpl *style, Utf8Stream::Point begin, Utf8Stream::Point end, int width) : + mStyle(style), mBegin(begin), mEnd(end), mWidth(width) + {} + }; + typedef TypesetBookImpl Book; typedef boost::shared_ptr BookPtr; + typedef std::vector::const_iterator PartialTextConstIterator; int mPageWidth; int mPageHeight; @@ -207,6 +219,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter Run * mRun; std::vector mSectionAlignment; + std::vector mPartialWhitespace; + std::vector mPartialWord; Book::Content const * mCurrentContent; Alignment mCurrentAlignment; @@ -246,7 +260,7 @@ struct TypesetBookImpl::Typesetter : BookTypesetter Style* createHotStyle (Style* baseStyle, Colour normalColour, Colour hoverColour, Colour activeColour, InteractiveId id, bool unique) { - StyleImpl* BaseStyle = dynamic_cast (baseStyle); + StyleImpl* BaseStyle = static_cast (baseStyle); if (!unique) for (Styles::iterator i = mBook->mStyles.begin (); i != mBook->mStyles.end (); ++i) @@ -268,11 +282,13 @@ struct TypesetBookImpl::Typesetter : BookTypesetter { Range range = mBook->addContent (text); - writeImpl (dynamic_cast (style), range.first, range.second); + writeImpl (static_cast (style), range.first, range.second); } intptr_t addContent (Utf8Span text, bool select) { + add_partial_text(); + Contents::iterator i = mBook->mContents.insert (mBook->mContents.end (), Content (text.first, text.second)); if (select) @@ -283,6 +299,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter void selectContent (intptr_t contentHandle) { + add_partial_text(); + mCurrentContent = reinterpret_cast (contentHandle); } @@ -295,19 +313,23 @@ struct TypesetBookImpl::Typesetter : BookTypesetter Utf8Point begin_ = &mCurrentContent->front () + begin; Utf8Point end_ = &mCurrentContent->front () + end ; - writeImpl (dynamic_cast (style), begin_, end_); + writeImpl (static_cast (style), begin_, end_); } void lineBreak (float margin) { assert (margin == 0); //TODO: figure out proper behavior here... + add_partial_text(); + mRun = NULL; mLine = NULL; } void sectionBreak (float margin) { + add_partial_text(); + if (mBook->mSections.size () > 0) { mRun = NULL; @@ -321,6 +343,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter void setSectionAlignment (Alignment sectionAlignment) { + add_partial_text(); + if (mSection != NULL) mSectionAlignment.back () = sectionAlignment; mCurrentAlignment = sectionAlignment; @@ -331,6 +355,8 @@ struct TypesetBookImpl::Typesetter : BookTypesetter int curPageStart = 0; int curPageStop = 0; + add_partial_text(); + std::vector ::iterator sa = mSectionAlignment.begin (); for (Sections::iterator i = mBook->mSections.begin (); i != mBook->mSections.end (); ++i, ++sa) { @@ -415,23 +441,23 @@ struct TypesetBookImpl::Typesetter : BookTypesetter void writeImpl (StyleImpl * style, Utf8Stream::Point _begin, Utf8Stream::Point _end) { - int line_height = style->mFont->getDefaultHeight (); - Utf8Stream stream (_begin, _end); while (!stream.eof ()) { if (ucsLineBreak (stream.peek ())) { + add_partial_text(); stream.consume (); mLine = NULL, mRun = NULL; continue; } + if (ucsBreakingSpace (stream.peek ()) && !mPartialWord.empty()) + add_partial_text(); + int word_width = 0; - int word_height = 0; int space_width = 0; - int character_count = 0; Utf8Stream::Point lead = stream.current (); @@ -450,8 +476,6 @@ struct TypesetBookImpl::Typesetter : BookTypesetter MyGUI::GlyphInfo* gi = style->mFont->getGlyphInfo (stream.peek ()); if (gi) word_width += gi->advance + gi->bearingX; - word_height = line_height; - ++character_count; stream.consume (); } @@ -460,21 +484,57 @@ struct TypesetBookImpl::Typesetter : BookTypesetter if (lead == extent) break; - int left = mLine ? mLine->mRect.right : 0; + if ( lead != origin ) + mPartialWhitespace.push_back (PartialText (style, lead, origin, space_width)); + if ( origin != extent ) + mPartialWord.push_back (PartialText (style, origin, extent, word_width)); + } + } - if (left + space_width + word_width > mPageWidth) - { - mLine = NULL, mRun = NULL; + void add_partial_text () + { + if (mPartialWhitespace.empty() && mPartialWord.empty()) + return; - append_run (style, origin, extent, extent - origin, word_width, mBook->mRect.bottom + word_height); - } - else + int space_width = 0; + int word_width = 0; + + for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) + space_width += i->mWidth; + for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) + word_width += i->mWidth; + + int left = mLine ? mLine->mRect.right : 0; + + if (left + space_width + word_width > mPageWidth) + { + mLine = NULL, mRun = NULL, left = 0; + } + else + { + for (PartialTextConstIterator i = mPartialWhitespace.begin (); i != mPartialWhitespace.end (); ++i) { int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; + int line_height = i->mStyle->mFont->getDefaultHeight (); - append_run (style, lead, extent, extent - origin, left + space_width + word_width, top + word_height); + append_run ( i->mStyle, i->mBegin, i->mEnd, 0, left + i->mWidth, top + line_height); + + left = mLine->mRect.right; } } + + for (PartialTextConstIterator i = mPartialWord.begin (); i != mPartialWord.end (); ++i) + { + int top = mLine ? mLine->mRect.top : mBook->mRect.bottom; + int line_height = i->mStyle->mFont->getDefaultHeight (); + + append_run (i->mStyle, i->mBegin, i->mEnd, i->mEnd - i->mBegin, left + i->mWidth, top + line_height); + + left = mLine->mRect.right; + } + + mPartialWhitespace.clear(); + mPartialWord.clear(); } void append_run (StyleImpl * style, Utf8Stream::Point begin, Utf8Stream::Point end, int pc, int right, int bottom) @@ -639,7 +699,8 @@ namespace MyGUI::Vertex* vertices, RenderXform const & renderXform) : mZ(Z), mOrigin (left, top), mFont (font), mVertices (vertices), - mRenderXform (renderXform) + mRenderXform (renderXform), + mC(0) { mVertexColourType = MyGUI::RenderManager::getInstance().getVertexFormat(); } diff --git a/apps/openmw/mwgui/bookwindow.cpp b/apps/openmw/mwgui/bookwindow.cpp index 92e8767082..da0d1950e9 100644 --- a/apps/openmw/mwgui/bookwindow.cpp +++ b/apps/openmw/mwgui/bookwindow.cpp @@ -1,6 +1,6 @@ #include "bookwindow.hpp" -#include +#include #include @@ -140,8 +140,8 @@ namespace MWGui void BookWindow::updatePages() { - mLeftPageNumber->setCaption( boost::lexical_cast(mCurrentPage*2 + 1) ); - mRightPageNumber->setCaption( boost::lexical_cast(mCurrentPage*2 + 2) ); + mLeftPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 1) ); + mRightPageNumber->setCaption( MyGUI::utility::toString(mCurrentPage*2 + 2) ); //If it is the last page, hide the button "Next Page" if ( (mCurrentPage+1)*2 == mPages.size() diff --git a/apps/openmw/mwgui/charactercreation.cpp b/apps/openmw/mwgui/charactercreation.cpp index 85f57a1a8c..ab412f63b2 100644 --- a/apps/openmw/mwgui/charactercreation.cpp +++ b/apps/openmw/mwgui/charactercreation.cpp @@ -1,18 +1,23 @@ #include "charactercreation.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "../mwworld/class.hpp" +#include "../mwworld/fallback.hpp" +#include "../mwworld/esmstore.hpp" + #include "textinput.hpp" #include "race.hpp" #include "class.hpp" #include "birth.hpp" #include "review.hpp" #include "inventorywindow.hpp" -#include -#include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/mechanicsmanager.hpp" -#include "../mwmechanics/npcstats.hpp" -#include "../mwworld/class.hpp" -#include "../mwworld/fallback.hpp" namespace { @@ -27,11 +32,11 @@ namespace Step sGenerateClassSteps(int number) { number++; const MWWorld::Fallback* fallback=MWBase::Environment::get().getWorld()->getFallback(); - Step step = {fallback->getFallbackString("Question_"+boost::lexical_cast(number)+"_Question"), - {fallback->getFallbackString("Question_"+boost::lexical_cast(number)+"_AnswerOne"), - fallback->getFallbackString("Question_"+boost::lexical_cast(number)+"_AnswerTwo"), - fallback->getFallbackString("Question_"+boost::lexical_cast(number)+"_AnswerThree")}, - "vo\\misc\\chargen qa"+boost::lexical_cast(number)+".wav" + Step step = {fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_Question"), + {fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerOne"), + fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerTwo"), + fallback->getFallbackString("Question_"+MyGUI::utility::toString(number)+"_AnswerThree")}, + "vo\\misc\\chargen qa"+MyGUI::utility::toString(number)+".wav" }; return step; } diff --git a/apps/openmw/mwgui/charactercreation.hpp b/apps/openmw/mwgui/charactercreation.hpp index 03898093d7..c2486c7f0b 100644 --- a/apps/openmw/mwgui/charactercreation.hpp +++ b/apps/openmw/mwgui/charactercreation.hpp @@ -4,8 +4,9 @@ #include #include -#include "../mwbase/world.hpp" -#include "../mwbase/windowmanager.hpp" +#include + +#include "../mwmechanics/stat.hpp" namespace MWGui { diff --git a/apps/openmw/mwgui/class.cpp b/apps/openmw/mwgui/class.cpp index 4e45f1a7c5..3e8734c716 100644 --- a/apps/openmw/mwgui/class.cpp +++ b/apps/openmw/mwgui/class.cpp @@ -1,14 +1,29 @@ #include "class.hpp" +#include +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/esmstore.hpp" #include "tooltips.hpp" #undef min #undef max +namespace +{ + + bool sortClasses(const std::pair& left, const std::pair& right) + { + return left.second.compare(right.second) < 0; + } + +} + namespace MWGui { @@ -129,8 +144,6 @@ namespace MWGui if (Misc::StringUtils::ciEqual(*mClassList->getItemDataAt(i), classId)) { mClassList->setIndexSelected(i); - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); break; } } @@ -165,9 +178,6 @@ namespace MWGui if (_index == MyGUI::ITEM_NONE) return; - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); - const std::string *classId = mClassList->getItemDataAt(_index); if (Misc::StringUtils::ciEqual(mCurrentClassId, *classId)) return; @@ -184,7 +194,7 @@ namespace MWGui const MWWorld::ESMStore &store = MWBase::Environment::get().getWorld()->getStore(); - int index = 0; + std::vector > items; // class id, class name MWWorld::Store::iterator it = store.get().begin(); for (; it != store.get().end(); ++it) { @@ -192,8 +202,15 @@ namespace MWGui if (!playable) // Only display playable classes continue; - const std::string &id = it->mId; - mClassList->addItem(it->mName, id); + items.push_back(std::make_pair(it->mId, it->mName)); + } + std::sort(items.begin(), items.end(), sortClasses); + + int index = 0; + for (std::vector >::const_iterator it = items.begin(); it != items.end(); ++it) + { + const std::string &id = it->first; + mClassList->addItem(it->second, id); if (mCurrentClassId.empty()) { mCurrentClassId = id; @@ -770,6 +787,7 @@ namespace MWGui SelectSkillDialog::SelectSkillDialog() : WindowModal("openmw_chargen_select_skill.layout") + , mSkillId(ESM::Skill::Block) { // Centre dialog center(); diff --git a/apps/openmw/mwgui/class.hpp b/apps/openmw/mwgui/class.hpp index 40056ca5e9..e36a9a98b6 100644 --- a/apps/openmw/mwgui/class.hpp +++ b/apps/openmw/mwgui/class.hpp @@ -1,15 +1,11 @@ #ifndef MWGUI_CLASS_H #define MWGUI_CLASS_H - +#include +#include #include "widgets.hpp" #include "windowbase.hpp" -/* - This file contains the dialogs for choosing a class. - Layout is defined by resources/mygui/openmw_chargen_class.layout. - */ - namespace MWGui { class InfoBoxDialog : public WindowModal diff --git a/apps/openmw/mwgui/companionitemmodel.cpp b/apps/openmw/mwgui/companionitemmodel.cpp index b8be9dcb82..983ef50173 100644 --- a/apps/openmw/mwgui/companionitemmodel.cpp +++ b/apps/openmw/mwgui/companionitemmodel.cpp @@ -3,6 +3,22 @@ #include "../mwmechanics/npcstats.hpp" #include "../mwworld/class.hpp" +namespace +{ + + void modifyProfit(const MWWorld::Ptr& actor, int diff) + { + std::string script = actor.getClass().getScript(actor); + if (!script.empty()) + { + int profit = actor.getRefData().getLocals().getIntVar(script, "minimumprofit"); + profit += diff; + actor.getRefData().getLocals().setVarByInt(script, "minimumprofit", profit); + } + } + +} + namespace MWGui { CompanionItemModel::CompanionItemModel(const MWWorld::Ptr &actor) @@ -12,23 +28,25 @@ namespace MWGui MWWorld::Ptr CompanionItemModel::copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) { - if (mActor.getClass().isNpc()) - { - MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats(mActor); - stats.modifyProfit(item.mBase.getClass().getValue(item.mBase) * count); - } + if (hasProfit(mActor)) + modifyProfit(mActor, item.mBase.getClass().getValue(item.mBase) * count); return InventoryItemModel::copyItem(item, count, setNewOwner); } void CompanionItemModel::removeItem (const ItemStack& item, size_t count) { - if (mActor.getClass().isNpc()) - { - MWMechanics::NpcStats& stats = mActor.getClass().getNpcStats(mActor); - stats.modifyProfit(-item.mBase.getClass().getValue(item.mBase) * count); - } + if (hasProfit(mActor)) + modifyProfit(mActor, -item.mBase.getClass().getValue(item.mBase) * count); InventoryItemModel::removeItem(item, count); } + + bool CompanionItemModel::hasProfit(const MWWorld::Ptr &actor) + { + std::string script = actor.getClass().getScript(actor); + if (script.empty()) + return false; + return actor.getRefData().getLocals().hasVar(script, "minimumprofit"); + } } diff --git a/apps/openmw/mwgui/companionitemmodel.hpp b/apps/openmw/mwgui/companionitemmodel.hpp index 172fa9508a..4c77ee12fa 100644 --- a/apps/openmw/mwgui/companionitemmodel.hpp +++ b/apps/openmw/mwgui/companionitemmodel.hpp @@ -15,6 +15,8 @@ namespace MWGui virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner); virtual void removeItem (const ItemStack& item, size_t count); + + bool hasProfit(const MWWorld::Ptr& actor); }; } diff --git a/apps/openmw/mwgui/companionwindow.cpp b/apps/openmw/mwgui/companionwindow.cpp index 2dd130b0da..8f709ec8da 100644 --- a/apps/openmw/mwgui/companionwindow.cpp +++ b/apps/openmw/mwgui/companionwindow.cpp @@ -1,9 +1,10 @@ #include "companionwindow.hpp" -#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/windowmanager.hpp" #include "../mwmechanics/npcstats.hpp" @@ -13,9 +14,24 @@ #include "itemview.hpp" #include "sortfilteritemmodel.hpp" #include "companionitemmodel.hpp" -#include "container.hpp" +#include "draganddrop.hpp" #include "countdialog.hpp" +namespace +{ + + int getProfit(const MWWorld::Ptr& actor) + { + std::string script = actor.getClass().getScript(actor); + if (!script.empty()) + { + return actor.getRefData().getLocals().getIntVar(script, "minimumprofit"); + } + return 0; + } + +} + namespace MWGui { @@ -115,13 +131,12 @@ void CompanionWindow::updateEncumbranceBar() float encumbrance = mPtr.getClass().getEncumbrance(mPtr); mEncumbranceBar->setValue(encumbrance, capacity); - if (mPtr.getTypeName() != typeid(ESM::NPC).name()) - mProfitLabel->setCaption(""); - else + if (mModel && mModel->hasProfit(mPtr)) { - MWMechanics::NpcStats& stats = mPtr.getClass().getNpcStats(mPtr); - mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + boost::lexical_cast(stats.getProfit())); + mProfitLabel->setCaptionWithReplacing("#{sProfitValue} " + MyGUI::utility::toString(getProfit(mPtr))); } + else + mProfitLabel->setCaption(""); } void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) @@ -131,7 +146,7 @@ void CompanionWindow::onCloseButtonClicked(MyGUI::Widget* _sender) void CompanionWindow::exit() { - if (mPtr.getTypeName() == typeid(ESM::NPC).name() && mPtr.getClass().getNpcStats(mPtr).getProfit() < 0) + if (mModel && mModel->hasProfit(mPtr) && getProfit(mPtr) < 0) { std::vector buttons; buttons.push_back("#{sCompanionWarningButtonOne}"); @@ -147,9 +162,6 @@ void CompanionWindow::onMessageBoxButtonClicked(int button) { if (button == 0) { - mPtr.getRefData().getLocals().setVarByInt(mPtr.getClass().getScript(mPtr), - "minimumprofit", mPtr.getClass().getNpcStats(mPtr).getProfit()); - MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Companion); // Important for Calvus' contract script to work properly MWBase::Environment::get().getWindowManager()->removeGuiMode(GM_Dialogue); diff --git a/apps/openmw/mwgui/confirmationdialog.cpp b/apps/openmw/mwgui/confirmationdialog.cpp index a4e10749a2..83650b1951 100644 --- a/apps/openmw/mwgui/confirmationdialog.cpp +++ b/apps/openmw/mwgui/confirmationdialog.cpp @@ -1,5 +1,8 @@ #include "confirmationdialog.hpp" +#include +#include + namespace MWGui { ConfirmationDialog::ConfirmationDialog() : diff --git a/apps/openmw/mwgui/console.cpp b/apps/openmw/mwgui/console.cpp index 5a7193d650..4eb9a271c1 100644 --- a/apps/openmw/mwgui/console.cpp +++ b/apps/openmw/mwgui/console.cpp @@ -1,5 +1,7 @@ #include "console.hpp" +#include + #include #include @@ -10,6 +12,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/esmstore.hpp" @@ -101,8 +104,20 @@ namespace MWGui it->second->listIdentifier (mNames); } + // exterior cell names aren't technically identifiers, but since the COC function accepts them, + // we should list them too + for (MWWorld::Store::iterator it = store.get().extBegin(); + it != store.get().extEnd(); ++it) + { + if (!it->mName.empty()) + mNames.push_back(it->mName); + } + // sort std::sort (mNames.begin(), mNames.end()); + + // remove duplicates + mNames.erase( std::unique( mNames.begin(), mNames.end() ), mNames.end() ); } } @@ -275,7 +290,8 @@ namespace MWGui // Add the command to the history, and set the current pointer to // the end of the list - mCommandHistory.push_back(cm); + if (mCommandHistory.empty() || mCommandHistory.back() != cm) + mCommandHistory.push_back(cm); mCurrent = mCommandHistory.end(); mEditString.clear(); diff --git a/apps/openmw/mwgui/container.cpp b/apps/openmw/mwgui/container.cpp index 58f23c175b..579730f42f 100644 --- a/apps/openmw/mwgui/container.cpp +++ b/apps/openmw/mwgui/container.cpp @@ -1,6 +1,7 @@ #include "container.hpp" -#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -24,121 +25,11 @@ #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" #include "pickpocketitemmodel.hpp" +#include "draganddrop.hpp" namespace MWGui { - DragAndDrop::DragAndDrop() - : mDraggedWidget(NULL) - , mDraggedCount(0) - , mSourceModel(NULL) - , mSourceView(NULL) - , mSourceSortModel(NULL) - , mIsOnDragAndDrop(false) - { - } - - void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) - { - mItem = sourceModel->getItem(index); - mDraggedCount = count; - mSourceModel = sourceModel; - mSourceView = sourceView; - mSourceSortModel = sortModel; - mIsOnDragAndDrop = true; - - // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend - // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, - // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). - ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); - if (mSourceModel != playerModel) - { - MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); - - playerModel->update(); - - ItemModel::ModelIndex newIndex = -1; - for (unsigned int i=0; igetItemCount(); ++i) - { - if (playerModel->getItem(i).mBase == item) - { - newIndex = i; - break; - } - } - mItem = playerModel->getItem(newIndex); - mSourceModel = playerModel; - - SortFilterItemModel* playerFilterModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); - mSourceSortModel = playerFilterModel; - } - - std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - - if (mSourceSortModel) - { - mSourceSortModel->clearDragItems(); - mSourceSortModel->addDragItem(mItem.mBase, count); - } - - ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget("MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); - - Controllers::ControllerFollowMouse* controller = - MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerFollowMouse::getClassTypeName()) - ->castType(); - MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); - - mDraggedWidget = baseWidget; - baseWidget->setItem(mItem.mBase); - baseWidget->setNeedMouseFocus(false); - baseWidget->setCount(count); - - sourceView->update(); - - MWBase::Environment::get().getWindowManager()->setDragDrop(true); - } - - void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) - { - std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); - MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); - - // We can't drop a conjured item to the ground; the target container should always be the source container - if (mItem.mFlags & ItemStack::Flag_Bound && targetModel != mSourceModel) - { - MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); - return; - } - - // If item is dropped where it was taken from, we don't need to do anything - - // otherwise, do the transfer - if (targetModel != mSourceModel) - { - mSourceModel->moveItem(mItem, mDraggedCount, targetModel); - } - - mSourceModel->update(); - - finish(); - if (targetView) - targetView->update(); - - // We need to update the view since an other item could be auto-equipped. - mSourceView->update(); - } - - void DragAndDrop::finish() - { - mIsOnDragAndDrop = false; - mSourceSortModel->clearDragItems(); - - MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); - mDraggedWidget = 0; - MWBase::Environment::get().getWindowManager()->setDragDrop(false); - } - - ContainerWindow::ContainerWindow(DragAndDrop* dragAndDrop) : WindowBase("openmw_container_window.layout") , mDragAndDrop(dragAndDrop) @@ -263,8 +154,6 @@ namespace MWGui 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(container.getClass().getName(container)); } @@ -299,10 +188,9 @@ namespace MWGui MWMechanics::Pickpocket pickpocket(player, mPtr); if (pickpocket.finish()) { - MWBase::Environment::get().getMechanicsManager()->reportCrime( - player, mPtr, MWBase::MechanicsManager::OT_Pickpocket); + MWBase::Environment::get().getMechanicsManager()->commitCrime( + player, mPtr, MWBase::MechanicsManager::OT_Pickpocket, 0, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief"); mPickpocketDetected = true; return; } @@ -382,10 +270,9 @@ namespace MWGui 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().getMechanicsManager()->commitCrime( + player, MWWorld::Ptr(), MWBase::MechanicsManager::OT_Theft, value, true); MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Container); - MWBase::Environment::get().getDialogueManager()->say(mPtr, "Thief"); mPickpocketDetected = true; return false; } @@ -398,7 +285,7 @@ namespace MWGui if (mPtr.getClass().isActor() && mPtr.getClass().getCreatureStats(mPtr).isDead()) return true; else - MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, count); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, item.mBase, mPtr, count); } return true; } diff --git a/apps/openmw/mwgui/container.hpp b/apps/openmw/mwgui/container.hpp index e32c6efaf0..87ae993a54 100644 --- a/apps/openmw/mwgui/container.hpp +++ b/apps/openmw/mwgui/container.hpp @@ -28,25 +28,6 @@ namespace MWGui namespace MWGui { - class DragAndDrop - { - public: - bool mIsOnDragAndDrop; - MyGUI::Widget* mDraggedWidget; - ItemModel* mSourceModel; - ItemView* mSourceView; - SortFilterItemModel* mSourceSortModel; - ItemStack mItem; - int mDraggedCount; - - DragAndDrop(); - - void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); - void drop (ItemModel* targetModel, ItemView* targetView); - - void finish(); - }; - class ContainerWindow : public WindowBase, public ReferenceInterface { public: diff --git a/apps/openmw/mwgui/controllers.cpp b/apps/openmw/mwgui/controllers.cpp index 5ebd2b1e74..ad804997ad 100644 --- a/apps/openmw/mwgui/controllers.cpp +++ b/apps/openmw/mwgui/controllers.cpp @@ -1,6 +1,7 @@ #include "controllers.hpp" #include +#include namespace MWGui { diff --git a/apps/openmw/mwgui/controllers.hpp b/apps/openmw/mwgui/controllers.hpp index 003027a46a..4208f048c9 100644 --- a/apps/openmw/mwgui/controllers.hpp +++ b/apps/openmw/mwgui/controllers.hpp @@ -1,9 +1,13 @@ #ifndef MWGUI_CONTROLLERS_H #define MWGUI_CONTROLLERS_H -#include +#include #include +namespace MyGUI +{ + class Widget; +} namespace MWGui { diff --git a/apps/openmw/mwgui/countdialog.cpp b/apps/openmw/mwgui/countdialog.cpp index 24d0af0d7d..c6f2180c92 100644 --- a/apps/openmw/mwgui/countdialog.cpp +++ b/apps/openmw/mwgui/countdialog.cpp @@ -1,6 +1,8 @@ #include "countdialog.hpp" -#include +#include +#include +#include #include diff --git a/apps/openmw/mwgui/debugwindow.cpp b/apps/openmw/mwgui/debugwindow.cpp index fab386bda1..a6dab66c10 100644 --- a/apps/openmw/mwgui/debugwindow.cpp +++ b/apps/openmw/mwgui/debugwindow.cpp @@ -1,5 +1,9 @@ #include "debugwindow.hpp" +#include +#include +#include +#include #include diff --git a/apps/openmw/mwgui/dialogue.cpp b/apps/openmw/mwgui/dialogue.cpp index eb548d596d..eee86c6d21 100644 --- a/apps/openmw/mwgui/dialogue.cpp +++ b/apps/openmw/mwgui/dialogue.cpp @@ -1,7 +1,10 @@ #include "dialogue.hpp" #include -#include + +#include +#include +#include #include @@ -15,6 +18,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwdialogue/dialoguemanagerimp.hpp" @@ -95,7 +99,7 @@ namespace MWGui mBribe100Button->setEnabled (playerGold >= 100); mBribe1000Button->setEnabled (playerGold >= 1000); - mGoldLabel->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); + mGoldLabel->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); } void PersuasionDialog::exit() @@ -176,11 +180,13 @@ namespace MWGui } else { - std::string::const_iterator i = text.begin (); - KeywordSearchT::Match match; + std::vector matches; + keywordSearch->highlightKeywords(text.begin(), text.end(), matches); - while (i != text.end () && keywordSearch->search (i, text.end (), match, text.begin ())) + std::string::const_iterator i = text.begin (); + for (std::vector::iterator it = matches.begin(); it != matches.end(); ++it) { + KeywordSearchT::Match match = *it; if (i != match.mBeg) addTopicLink (typesetter, 0, i - text.begin (), match.mBeg - text.begin ()); @@ -188,7 +194,6 @@ namespace MWGui i = match.mEnd; } - if (i != text.end ()) addTopicLink (typesetter, 0, i - text.begin (), text.size ()); } @@ -415,20 +420,11 @@ namespace MWGui MWMechanics::CreatureStats &sellerStats = mPtr.getClass().getCreatureStats(mPtr); float delay = MWBase::Environment::get().getWorld()->getStore().get().find("fBarterGoldResetDelay")->getFloat(); + // Gold is restocked every 24h if (MWBase::Environment::get().getWorld()->getTimeStamp() >= sellerStats.getLastRestockTime() + delay) { sellerStats.setGoldPool(mPtr.getClass().getBaseGold(mPtr)); - mPtr.getClass().restock(mPtr); - - // Also restock any containers owned by this merchant, which are also available to buy in the trade window - std::vector itemSources; - MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); - for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) - { - it->getClass().restock(*it); - } - sellerStats.setLastRestockTime(MWBase::Environment::get().getWorld()->getTimeStamp()); } } @@ -633,7 +629,7 @@ namespace MWGui dispositionVisible = true; mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr)); - mDispositionText->setCaption(boost::lexical_cast(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")); + mDispositionText->setCaption(MyGUI::utility::toString(MWBase::Environment::get().getMechanicsManager()->getDerivedDisposition(mPtr))+std::string("/100")); } bool dispositionWasVisible = mDispositionBar->getVisible(); @@ -675,7 +671,7 @@ namespace MWGui + MWBase::Environment::get().getDialogueManager()->getTemporaryDispositionChange())); mDispositionBar->setProgressRange(100); mDispositionBar->setProgressPosition(disp); - mDispositionText->setCaption(boost::lexical_cast(disp)+std::string("/100")); + mDispositionText->setCaption(MyGUI::utility::toString(disp)+std::string("/100")); } } } diff --git a/apps/openmw/mwgui/dialogue.hpp b/apps/openmw/mwgui/dialogue.hpp index 23709e8fe3..769c32af7c 100644 --- a/apps/openmw/mwgui/dialogue.hpp +++ b/apps/openmw/mwgui/dialogue.hpp @@ -18,11 +18,6 @@ namespace MWGui class WindowManager; } -/* - This file contains the dialouge window - Layout is defined by resources/mygui/openmw_dialogue_window.layout. - */ - namespace MWGui { class DialogueHistoryViewModel; @@ -171,7 +166,7 @@ namespace MWGui BookPage* mHistory; Gui::MWList* mTopicsList; MyGUI::ScrollBar* mScrollBar; - MyGUI::Progress* mDispositionBar; + MyGUI::ProgressBar* mDispositionBar; MyGUI::EditBox* mDispositionText; PersuasionDialog mPersuasionDialog; diff --git a/apps/openmw/mwgui/draganddrop.cpp b/apps/openmw/mwgui/draganddrop.cpp new file mode 100644 index 0000000000..fcb381b954 --- /dev/null +++ b/apps/openmw/mwgui/draganddrop.cpp @@ -0,0 +1,134 @@ +#include "draganddrop.hpp" + +#include +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" + +#include "../mwworld/class.hpp" + +#include "sortfilteritemmodel.hpp" +#include "inventorywindow.hpp" +#include "itemwidget.hpp" +#include "itemview.hpp" +#include "controllers.hpp" + +namespace MWGui +{ + + +DragAndDrop::DragAndDrop() + : mDraggedWidget(NULL) + , mDraggedCount(0) + , mSourceModel(NULL) + , mSourceView(NULL) + , mSourceSortModel(NULL) + , mIsOnDragAndDrop(false) +{ +} + +void DragAndDrop::startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count) +{ + mItem = sourceModel->getItem(index); + mDraggedCount = count; + mSourceModel = sourceModel; + mSourceView = sourceView; + mSourceSortModel = sortModel; + mIsOnDragAndDrop = true; + + // If picking up an item that isn't from the player's inventory, the item gets added to player inventory backend + // immediately, even though it's still floating beneath the mouse cursor. A bit counterintuitive, + // but this is how it works in vanilla, and not doing so would break quests (BM_beasts for instance). + ItemModel* playerModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getModel(); + if (mSourceModel != playerModel) + { + MWWorld::Ptr item = mSourceModel->moveItem(mItem, mDraggedCount, playerModel); + + playerModel->update(); + + ItemModel::ModelIndex newIndex = -1; + for (unsigned int i=0; igetItemCount(); ++i) + { + if (playerModel->getItem(i).mBase == item) + { + newIndex = i; + break; + } + } + mItem = playerModel->getItem(newIndex); + mSourceModel = playerModel; + + SortFilterItemModel* playerFilterModel = MWBase::Environment::get().getWindowManager()->getInventoryWindow()->getSortFilterModel(); + mSourceSortModel = playerFilterModel; + } + + std::string sound = mItem.mBase.getClass().getUpSoundId(mItem.mBase); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + if (mSourceSortModel) + { + mSourceSortModel->clearDragItems(); + mSourceSortModel->addDragItem(mItem.mBase, count); + } + + ItemWidget* baseWidget = MyGUI::Gui::getInstance().createWidget("MW_ItemIcon", 0, 0, 42, 42, MyGUI::Align::Default, "DragAndDrop"); + + Controllers::ControllerFollowMouse* controller = + MyGUI::ControllerManager::getInstance().createItem(Controllers::ControllerFollowMouse::getClassTypeName()) + ->castType(); + MyGUI::ControllerManager::getInstance().addItem(baseWidget, controller); + + mDraggedWidget = baseWidget; + baseWidget->setItem(mItem.mBase); + baseWidget->setNeedMouseFocus(false); + baseWidget->setCount(count); + + sourceView->update(); + + MWBase::Environment::get().getWindowManager()->setDragDrop(true); +} + +void DragAndDrop::drop(ItemModel *targetModel, ItemView *targetView) +{ + std::string sound = mItem.mBase.getClass().getDownSoundId(mItem.mBase); + MWBase::Environment::get().getSoundManager()->playSound (sound, 1.0, 1.0); + + // We can't drop a conjured item to the ground; the target container should always be the source container + if (mItem.mFlags & ItemStack::Flag_Bound && targetModel != mSourceModel) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sBarterDialog12}"); + return; + } + + // If item is dropped where it was taken from, we don't need to do anything - + // otherwise, do the transfer + if (targetModel != mSourceModel) + { + mSourceModel->moveItem(mItem, mDraggedCount, targetModel); + } + + mSourceModel->update(); + + finish(); + if (targetView) + targetView->update(); + + MWBase::Environment::get().getWindowManager()->getInventoryWindow()->updateItemView(); + + // We need to update the view since an other item could be auto-equipped. + mSourceView->update(); +} + +void DragAndDrop::finish() +{ + mIsOnDragAndDrop = false; + mSourceSortModel->clearDragItems(); + + MyGUI::Gui::getInstance().destroyWidget(mDraggedWidget); + mDraggedWidget = 0; + MWBase::Environment::get().getWindowManager()->setDragDrop(false); +} + +} diff --git a/apps/openmw/mwgui/draganddrop.hpp b/apps/openmw/mwgui/draganddrop.hpp new file mode 100644 index 0000000000..a356fe4e29 --- /dev/null +++ b/apps/openmw/mwgui/draganddrop.hpp @@ -0,0 +1,38 @@ +#ifndef OPENMW_MWGUI_DRAGANDDROP_H +#define OPENMW_MWGUI_DRAGANDDROP_H + +#include "itemmodel.hpp" + +namespace MyGUI +{ + class Widget; +} + +namespace MWGui +{ + + class ItemView; + class SortFilterItemModel; + + class DragAndDrop + { + public: + bool mIsOnDragAndDrop; + MyGUI::Widget* mDraggedWidget; + ItemModel* mSourceModel; + ItemView* mSourceView; + SortFilterItemModel* mSourceSortModel; + ItemStack mItem; + int mDraggedCount; + + DragAndDrop(); + + void startDrag (int index, SortFilterItemModel* sortModel, ItemModel* sourceModel, ItemView* sourceView, int count); + void drop (ItemModel* targetModel, ItemView* targetView); + + void finish(); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/enchantingdialog.cpp b/apps/openmw/mwgui/enchantingdialog.cpp index 30d67f5540..43f2493a98 100644 --- a/apps/openmw/mwgui/enchantingdialog.cpp +++ b/apps/openmw/mwgui/enchantingdialog.cpp @@ -4,6 +4,12 @@ #include +#include +#include + +#include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/soundmanager.hpp" @@ -11,9 +17,9 @@ #include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "itemselection.hpp" -#include "container.hpp" #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" @@ -104,33 +110,33 @@ namespace MWGui { std::stringstream enchantCost; enchantCost << std::setprecision(1) << std::fixed << mEnchanting.getEnchantPoints(); - mEnchantmentPoints->setCaption(enchantCost.str() + " / " + boost::lexical_cast(mEnchanting.getMaxEnchantValue())); + mEnchantmentPoints->setCaption(enchantCost.str() + " / " + MyGUI::utility::toString(mEnchanting.getMaxEnchantValue())); - mCharge->setCaption(boost::lexical_cast(mEnchanting.getGemCharge())); + mCharge->setCaption(MyGUI::utility::toString(mEnchanting.getGemCharge())); std::stringstream castCost; - castCost << std::setprecision(1) << std::fixed << mEnchanting.getCastCost(); - mCastCost->setCaption(boost::lexical_cast(castCost.str())); + castCost << mEnchanting.getEffectiveCastCost(); + mCastCost->setCaption(castCost.str()); - mPrice->setCaption(boost::lexical_cast(mEnchanting.getEnchantPrice())); + mPrice->setCaption(MyGUI::utility::toString(mEnchanting.getEnchantPrice())); switch(mEnchanting.getCastStyle()) { case ESM::Enchantment::CastOnce: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastOnce","Cast Once")); - mAddEffectDialog.constantEffect=false; + setConstantEffect(false); break; case ESM::Enchantment::WhenStrikes: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenStrikes", "When Strikes")); - mAddEffectDialog.constantEffect=false; + setConstantEffect(false); break; case ESM::Enchantment::WhenUsed: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastWhenUsed", "When Used")); - mAddEffectDialog.constantEffect=false; + setConstantEffect(false); break; case ESM::Enchantment::ConstantEffect: mTypeButton->setCaption(MWBase::Environment::get().getWindowManager()->getGameSettingString("sItemCastConstant", "Cast Constant")); - mAddEffectDialog.constantEffect=true; + setConstantEffect(true); break; } } @@ -281,6 +287,7 @@ namespace MWGui { mEnchanting.nextCastStyle(); updateLabels(); + updateEffectsView(); } void EnchantingDialog::onBuyButtonClicked(MyGUI::Widget* sender) @@ -332,15 +339,15 @@ namespace MWGui for (int i=0; i<2; ++i) { MWWorld::Ptr item = (i == 0) ? mEnchanting.getOldItem() : mEnchanting.getGem(); - if (Misc::StringUtils::ciEqual(item.getCellRef().getOwner(), mPtr.getCellRef().getRefId())) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(item.getCellRef().getRefId(), + mPtr.getCellRef().getRefId())) { std::string msg = MWBase::Environment::get().getWorld()->getStore().get().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().getMechanicsManager()->commitCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, + item.getClass().getValue(item), true); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Enchanting); MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; diff --git a/apps/openmw/mwgui/formatting.cpp b/apps/openmw/mwgui/formatting.cpp index c55650c453..761a89ca62 100644 --- a/apps/openmw/mwgui/formatting.cpp +++ b/apps/openmw/mwgui/formatting.cpp @@ -1,20 +1,23 @@ #include "formatting.hpp" +#include +#include + +#include +#include +#include +#include +#include + +#include +#include + #include #include #include #include "../mwscript/interpretercontext.hpp" -#include -#include -#include - -#include -#include - -#include - namespace MWGui { namespace Formatting @@ -193,6 +196,9 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(parent->getChildAt(0)); } + mTextStyle = TextStyle(); + mBlockStyle = BlockStyle(); + MyGUI::Widget * paper = parent->createWidget("Widget", MyGUI::IntCoord(0, 0, pag.getPageWidth(), pag.getPageHeight()), MyGUI::Align::Left | MyGUI::Align::Top); paper->setNeedMouseFocus(false); @@ -207,8 +213,25 @@ namespace MWGui continue; std::string plainText = parser.getReadyText(); + + // for cases when linebreaks are used to cause a shift to the next page + // if the split text block ends in an empty line, proceeding text block(s) should have leading empty lines removed + if (pag.getIgnoreLeadingEmptyLines()) + { + while (!plainText.empty()) + { + if (plainText[0] == '\n') + plainText.erase(plainText.begin()); + else + { + pag.setIgnoreLeadingEmptyLines(false); + break; + } + } + } + if (plainText.empty()) - brBeforeLastTag = false; + brBeforeLastTag = true; else { // Each block of text (between two tags / boundary and tag) will be displayed in a separate editbox widget, @@ -252,14 +275,16 @@ namespace MWGui { case BookTextParser::Event_ImgTag: { + pag.setIgnoreLeadingEmptyLines(false); + const BookTextParser::Attributes & attr = parser.getAttributes(); if (attr.find("src") == attr.end() || attr.find("width") == attr.end() || attr.find("height") == attr.end()) continue; std::string src = attr.at("src"); - int width = boost::lexical_cast(attr.at("width")); - int height = boost::lexical_cast(attr.at("height")); + int width = MyGUI::utility::parseInt(attr.at("width")); + int height = MyGUI::utility::parseInt(attr.at("height")); ImageElement elem(paper, pag, mBlockStyle, src, width, height); @@ -318,7 +343,7 @@ namespace MWGui { if (attr.find("color") != attr.end()) { - int color; + unsigned int color; std::stringstream ss; ss << attr.at("color"); ss >> std::hex >> color; @@ -331,9 +356,7 @@ namespace MWGui if (attr.find("face") != attr.end()) { std::string face = attr.at("face"); - - if (face != "Magic Cards") - mTextStyle.mFont = face; + mTextStyle.mFont = face; } if (attr.find("size") != attr.end()) { @@ -373,7 +396,7 @@ namespace MWGui { MyGUI::EditBox* box = parent->createWidget("NormalText", MyGUI::IntCoord(0, pag.getCurrentTop(), pag.getPageWidth(), 0), MyGUI::Align::Left | MyGUI::Align::Top, - parent->getName() + boost::lexical_cast(parent->getChildCount())); + parent->getName() + MyGUI::utility::toString(parent->getChildCount())); box->setProperty("Static", "true"); box->setProperty("MultiLine", "true"); box->setProperty("WordWrap", "true"); @@ -408,13 +431,18 @@ namespace MWGui // first empty lines that would go to the next page should be ignored // unfortunately, getLineInfo method won't be available until 3.2.2 #if (MYGUI_VERSION >= MYGUI_DEFINE_VERSION(3, 2, 2)) + mPaginator.setIgnoreLeadingEmptyLines(true); + const MyGUI::VectorLineInfo & lines = mEditBox->getSubWidgetText()->castType()->getLineInfo(); for (unsigned int i = lastLine; i < lines.size(); ++i) { if (lines[i].width == 0) ret += lineHeight; else + { + mPaginator.setIgnoreLeadingEmptyLines(false); break; + } } #endif return ret; @@ -436,7 +464,7 @@ namespace MWGui mImageBox = parent->createWidget ("ImageBox", MyGUI::IntCoord(left, pag.getCurrentTop(), width, mImageHeight), MyGUI::Align::Left | MyGUI::Align::Top, - parent->getName() + boost::lexical_cast(parent->getChildCount())); + parent->getName() + MyGUI::utility::toString(parent->getChildCount())); std::string image = Misc::ResourceHelpers::correctBookartPath(src, width, mImageHeight); mImageBox->setImageTexture(image); diff --git a/apps/openmw/mwgui/formatting.hpp b/apps/openmw/mwgui/formatting.hpp index 5b79250577..d525baacec 100644 --- a/apps/openmw/mwgui/formatting.hpp +++ b/apps/openmw/mwgui/formatting.hpp @@ -1,7 +1,7 @@ #ifndef MWGUI_FORMATTING_H #define MWGUI_FORMATTING_H -#include +#include #include namespace MWGui @@ -81,7 +81,8 @@ namespace MWGui Paginator(int pageWidth, int pageHeight) : mStartTop(0), mCurrentTop(0), - mPageWidth(pageWidth), mPageHeight(pageHeight) + mPageWidth(pageWidth), mPageHeight(pageHeight), + mIgnoreLeadingEmptyLines(false) { } @@ -89,10 +90,12 @@ namespace MWGui int getCurrentTop() const { return mCurrentTop; } int getPageWidth() const { return mPageWidth; } int getPageHeight() const { return mPageHeight; } + bool getIgnoreLeadingEmptyLines() const { return mIgnoreLeadingEmptyLines; } Pages getPages() const { return mPages; } void setStartTop(int top) { mStartTop = top; } void setCurrentTop(int top) { mCurrentTop = top; } + void setIgnoreLeadingEmptyLines(bool ignore) { mIgnoreLeadingEmptyLines = ignore; } Paginator & operator<<(const Page & page) { @@ -103,6 +106,7 @@ namespace MWGui private: int mStartTop, mCurrentTop; int mPageWidth, mPageHeight; + bool mIgnoreLeadingEmptyLines; Pages mPages; }; diff --git a/apps/openmw/mwgui/hud.cpp b/apps/openmw/mwgui/hud.cpp index 2593e6e77c..1f7ead6f56 100644 --- a/apps/openmw/mwgui/hud.cpp +++ b/apps/openmw/mwgui/hud.cpp @@ -1,14 +1,24 @@ #include "hud.hpp" -#include +#include + +#include +#include +#include +#include +#include +#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -17,7 +27,7 @@ #include "console.hpp" #include "spellicons.hpp" #include "itemmodel.hpp" -#include "container.hpp" +#include "draganddrop.hpp" #include "itemmodel.hpp" #include "itemwidget.hpp" @@ -162,7 +172,7 @@ namespace MWGui getWidget(mTriangleCounter, "TriangleCounter"); getWidget(mBatchCounter, "BatchCounter"); - LocalMapBase::init(mMinimap, mCompass); + LocalMapBase::init(mMinimap, mCompass, Settings::Manager::getInt("local map hud widget size", "Map")); mMainWidget->eventMouseButtonClick += MyGUI::newDelegate(this, &HUD::onWorldClicked); mMainWidget->eventMouseMove += MyGUI::newDelegate(this, &HUD::onWorldMouseOver); @@ -173,6 +183,10 @@ namespace MWGui HUD::~HUD() { + mMainWidget->eventMouseLostFocus.clear(); + mMainWidget->eventMouseMove.clear(); + mMainWidget->eventMouseButtonClick.clear(); + delete mSpellIcons; } @@ -203,17 +217,17 @@ namespace MWGui void HUD::setFPS(float fps) { if (mFpsCounter) - mFpsCounter->setCaption(boost::lexical_cast((int)fps)); + mFpsCounter->setCaption(MyGUI::utility::toString((int)fps)); } void HUD::setTriangleCount(unsigned int count) { - mTriangleCounter->setCaption(boost::lexical_cast(count)); + mTriangleCounter->setCaption(MyGUI::utility::toString(count)); } void HUD::setBatchCount(unsigned int count) { - mBatchCounter->setCaption(boost::lexical_cast(count)); + mBatchCounter->setCaption(MyGUI::utility::toString(count)); } void HUD::setValue(const std::string& id, const MWMechanics::DynamicStat& value) @@ -222,7 +236,7 @@ namespace MWGui int modified = static_cast(value.getModified()); MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + std::string valStr = MyGUI::utility::toString(current) + "/" + MyGUI::utility::toString(modified); if (id == "HBar") { mHealth->setProgressRange(modified); @@ -618,6 +632,11 @@ namespace MWGui // Health is usually cast to int before displaying. Actors die whenever they are < 1 health. // Therefore any value < 1 should show as an empty health bar. We do the same in statswindow :) mEnemyHealth->setProgressPosition(int(stats.getHealth().getCurrent()) / stats.getHealth().getModified() * 100); + + static const float fNPCHealthBarFade = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarFade")->getFloat(); + if (fNPCHealthBarFade > 0.f) + mEnemyHealth->setAlpha(std::max(0.f, std::min(1.f, mEnemyHealthTimer/fNPCHealthBarFade))); + } void HUD::update() @@ -639,7 +658,7 @@ namespace MWGui void HUD::setEnemy(const MWWorld::Ptr &enemy) { mEnemyActorId = enemy.getClass().getCreatureStats(enemy).getActorId(); - mEnemyHealthTimer = 5; + mEnemyHealthTimer = MWBase::Environment::get().getWorld()->getStore().get().find("fNPCHealthBarTime")->getFloat(); if (!mEnemyHealth->getVisible()) mWeaponSpellBox->setPosition(mWeaponSpellBox->getPosition() - MyGUI::IntPoint(0,20)); mEnemyHealth->setVisible(true); diff --git a/apps/openmw/mwgui/hud.hpp b/apps/openmw/mwgui/hud.hpp index ca68d907a9..41a535a089 100644 --- a/apps/openmw/mwgui/hud.hpp +++ b/apps/openmw/mwgui/hud.hpp @@ -4,7 +4,11 @@ #include "mapwindow.hpp" #include "../mwmechanics/stat.hpp" -#include "../mwworld/ptr.hpp" + +namespace MWWorld +{ + class Ptr; +} namespace MWGui { diff --git a/apps/openmw/mwgui/inventoryitemmodel.cpp b/apps/openmw/mwgui/inventoryitemmodel.cpp index f45881770a..e8354b7406 100644 --- a/apps/openmw/mwgui/inventoryitemmodel.cpp +++ b/apps/openmw/mwgui/inventoryitemmodel.cpp @@ -65,16 +65,7 @@ MWWorld::Ptr InventoryItemModel::moveItem(const ItemStack &item, size_t count, I if (item.mFlags & ItemStack::Flag_Bound) return MWWorld::Ptr(); - bool setNewOwner = false; - - // Are you dead? Then you wont need that anymore - if (mActor.getClass().isActor() && mActor.getClass().getCreatureStats(mActor).isDead() - // Make sure that the item is actually owned by the dead actor - // Prevents a potential exploit for resetting the owner of any item, by placing the item in a corpse - && Misc::StringUtils::ciEqual(item.mBase.getCellRef().getOwner(), mActor.getCellRef().getRefId())) - setNewOwner = true; - - MWWorld::Ptr ret = otherModel->copyItem(item, count, setNewOwner); + MWWorld::Ptr ret = otherModel->copyItem(item, count, false); removeItem(item, count); return ret; } @@ -99,14 +90,8 @@ void InventoryItemModel::update() if (mActor.getClass().hasInventoryStore(mActor)) { MWWorld::InventoryStore& store = mActor.getClass().getInventoryStore(mActor); - for (int slot=0; slot -#include +#include +#include +#include +#include +#include + +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -15,6 +21,7 @@ #include "../mwworld/action.hpp" #include "../mwscript/interpretercontext.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwrender/characterpreview.hpp" #include "bookwindow.hpp" #include "scrollwindow.hpp" @@ -25,7 +32,21 @@ #include "tradeitemmodel.hpp" #include "countdialog.hpp" #include "tradewindow.hpp" -#include "container.hpp" +#include "draganddrop.hpp" +#include "widgets.hpp" + +namespace +{ + + bool isRightHandWeapon(const MWWorld::Ptr& item) + { + if (item.getClass().getTypeName() != typeid(ESM::Weapon).name()) + return false; + std::vector equipmentSlots = item.getClass().getEquipmentSlots(item).first; + return (!equipmentSlots.empty() && equipmentSlots.front() == MWWorld::InventoryStore::Slot_CarriedRight); + } + +} namespace MWGui { @@ -296,6 +317,9 @@ namespace MWGui void InventoryWindow::updateItemView() { + if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) + MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); + mItemView->update(); mPreviewDirty = true; } @@ -396,7 +420,7 @@ namespace MWGui // 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()) + if (!script.empty() && MWBase::Environment::get().getWorld()->getScriptsEnabled()) { MWScript::InterpreterContext interpreterContext (&ptr.getRefData().getLocals(), ptr); MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); @@ -514,6 +538,14 @@ namespace MWGui void InventoryWindow::doRenderUpdate () { mPreview->onFrame(); + + if (mPreviewResize || mPreviewDirty) + { + mArmorRating->setCaptionWithReplacing ("#{sArmor}: " + + MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) + mArmorRating->setCaptionWithReplacing (MyGUI::utility::toString(static_cast(mPtr.getClass().getArmorRating(mPtr)))); + } if (mPreviewResize) { mPreviewResize = false; @@ -530,11 +562,6 @@ namespace MWGui mPreview->update (); mAvatarImage->setImageTexture("CharacterPreview"); - - mArmorRating->setCaptionWithReplacing ("#{sArmor}: " - + boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); - if (mArmorRating->getTextSize().width > mArmorRating->getSize().width) - mArmorRating->setCaptionWithReplacing (boost::lexical_cast(static_cast(mPtr.getClass().getArmorRating(mPtr)))); } } @@ -597,6 +624,60 @@ namespace MWGui throw std::runtime_error("Added item not found"); mDragAndDrop->startDrag(i, mSortModel, mTradeModel, mItemView, count); - MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, count); + MWBase::Environment::get().getMechanicsManager()->itemTaken(player, newObject, MWWorld::Ptr(), count); + + if (MWBase::Environment::get().getWindowManager()->getSpellWindow()) + MWBase::Environment::get().getWindowManager()->getSpellWindow()->updateSpells(); + } + + void InventoryWindow::cycle(bool next) + { + ItemModel::ModelIndex selected = -1; + // not using mSortFilterModel as we only need sorting, not filtering + SortFilterItemModel model(new InventoryItemModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + model.setSortByType(false); + model.update(); + if (model.getItemCount() == 0) + return; + + for (ItemModel::ModelIndex i=0; irebuild(); } } diff --git a/apps/openmw/mwgui/inventorywindow.hpp b/apps/openmw/mwgui/inventorywindow.hpp index 6fd6ece4a9..ee71d9b04c 100644 --- a/apps/openmw/mwgui/inventorywindow.hpp +++ b/apps/openmw/mwgui/inventorywindow.hpp @@ -1,14 +1,23 @@ #ifndef MGUI_Inventory_H #define MGUI_Inventory_H -#include "../mwrender/characterpreview.hpp" - #include "windowpinnablebase.hpp" -#include "widgets.hpp" #include "mode.hpp" +#include "../mwworld/ptr.hpp" + +namespace MWRender +{ + class InventoryPreview; +} + namespace MWGui { + namespace Widgets + { + class MWDynamicStat; + } + class ItemView; class SortFilterItemModel; class TradeItemModel; @@ -33,9 +42,7 @@ namespace MWGui MWWorld::Ptr getAvatarSelectedItem(int x, int y); - void rebuildAvatar() { - mPreview->rebuild(); - } + void rebuildAvatar(); SortFilterItemModel* getSortFilterModel(); TradeItemModel* getTradeModel(); @@ -49,6 +56,9 @@ namespace MWGui void setGuiMode(GuiMode mode); + /// Cycle to previous/next weapon + void cycle(bool next); + private: DragAndDrop* mDragAndDrop; diff --git a/apps/openmw/mwgui/itemmodel.cpp b/apps/openmw/mwgui/itemmodel.cpp index f5135ce80e..8224fd55b0 100644 --- a/apps/openmw/mwgui/itemmodel.cpp +++ b/apps/openmw/mwgui/itemmodel.cpp @@ -1,5 +1,7 @@ #include "itemmodel.hpp" +#include + #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/store.hpp" diff --git a/apps/openmw/mwgui/itemmodel.hpp b/apps/openmw/mwgui/itemmodel.hpp index c1b243a761..53c7018e22 100644 --- a/apps/openmw/mwgui/itemmodel.hpp +++ b/apps/openmw/mwgui/itemmodel.hpp @@ -46,20 +46,27 @@ namespace MWGui ItemModel(); virtual ~ItemModel() {} - typedef int ModelIndex; + typedef int ModelIndex; // -1 means invalid index + /// Throws for invalid index or out of range index virtual ItemStack getItem (ModelIndex index) = 0; - ///< throws for invalid index + + /// The number of items in the model, this specifies the range of indices you can pass to + /// the getItem function (but this range is only valid until the next call to update()) virtual size_t getItemCount() = 0; + /// Returns an invalid index if the item was not found virtual ModelIndex getIndex (ItemStack item) = 0; + /// Rebuild the item model, this will invalidate existing model indices virtual void update() = 0; /// Move items from this model to \a otherModel. + /// @note Derived implementations may return an empty Ptr if the move was unsuccessful. virtual MWWorld::Ptr moveItem (const ItemStack& item, size_t count, ItemModel* otherModel); - /// @param setNewOwner Set the copied item's owner to the actor we are copying to, or keep the original owner? + /// @param setNewOwner If true, set the copied item's owner to the actor we are copying to, + /// otherwise reset owner to "" virtual MWWorld::Ptr copyItem (const ItemStack& item, size_t count, bool setNewOwner=false) = 0; virtual void removeItem (const ItemStack& item, size_t count) = 0; diff --git a/apps/openmw/mwgui/itemselection.cpp b/apps/openmw/mwgui/itemselection.cpp index 1b197f6d80..916f133607 100644 --- a/apps/openmw/mwgui/itemselection.cpp +++ b/apps/openmw/mwgui/itemselection.cpp @@ -1,5 +1,8 @@ #include "itemselection.hpp" +#include +#include + #include "itemview.hpp" #include "inventoryitemmodel.hpp" #include "sortfilteritemmodel.hpp" diff --git a/apps/openmw/mwgui/itemselection.hpp b/apps/openmw/mwgui/itemselection.hpp index 28c45c6056..50e15a3fe1 100644 --- a/apps/openmw/mwgui/itemselection.hpp +++ b/apps/openmw/mwgui/itemselection.hpp @@ -1,7 +1,14 @@ #ifndef OPENMW_GAME_MWGUI_ITEMSELECTION_H #define OPENMW_GAME_MWGUI_ITEMSELECTION_H -#include "container.hpp" +#include + +#include "windowbase.hpp" + +namespace MWWorld +{ + class Ptr; +} namespace MWGui { diff --git a/apps/openmw/mwgui/itemview.cpp b/apps/openmw/mwgui/itemview.cpp index a51ada2754..e30f6e7a81 100644 --- a/apps/openmw/mwgui/itemview.cpp +++ b/apps/openmw/mwgui/itemview.cpp @@ -1,6 +1,6 @@ #include "itemview.hpp" -#include +#include #include #include @@ -53,9 +53,14 @@ void ItemView::layoutWidgets() int x = 0; int y = 0; - int maxHeight = mScrollView->getSize().height - 58; - MyGUI::Widget* dragArea = mScrollView->getChildAt(0); + int maxHeight = mScrollView->getHeight(); + + int rows = maxHeight/42; + rows = std::max(rows, 1); + bool showScrollbar = int(std::ceil(dragArea->getChildCount()/float(rows))) > mScrollView->getWidth()/42; + if (showScrollbar) + maxHeight -= 18; for (unsigned int i=0; igetChildCount(); ++i) { @@ -64,7 +69,8 @@ void ItemView::layoutWidgets() w->setPosition(x, y); y += 42; - if (y > maxHeight) + + if (y > maxHeight-42 && i < dragArea->getChildCount()-1) { x += 42; y = 0; @@ -149,11 +155,6 @@ void ItemView::setSize(const MyGUI::IntSize &_value) layoutWidgets(); } -void ItemView::setSize(int _width, int _height) -{ - setSize(MyGUI::IntSize(_width, _height)); -} - void ItemView::setCoord(const MyGUI::IntCoord &_value) { bool changed = (_value.width != getWidth() || _value.height != getHeight()); @@ -162,11 +163,6 @@ void ItemView::setCoord(const MyGUI::IntCoord &_value) layoutWidgets(); } -void ItemView::setCoord(int _left, int _top, int _width, int _height) -{ - setCoord(MyGUI::IntCoord(_left, _top, _width, _height)); -} - void ItemView::registerComponents() { MyGUI::FactoryManager::getInstance().registerFactory("Widget"); diff --git a/apps/openmw/mwgui/itemview.hpp b/apps/openmw/mwgui/itemview.hpp index 1a5bd79a3d..9aeba67520 100644 --- a/apps/openmw/mwgui/itemview.hpp +++ b/apps/openmw/mwgui/itemview.hpp @@ -37,8 +37,6 @@ namespace MWGui virtual void setSize(const MyGUI::IntSize& _value); virtual void setCoord(const MyGUI::IntCoord& _value); - void setSize(int _width, int _height); - void setCoord(int _left, int _top, int _width, int _height); void onSelectedItem (MyGUI::Widget* sender); void onSelectedBackground (MyGUI::Widget* sender); diff --git a/apps/openmw/mwgui/itemwidget.cpp b/apps/openmw/mwgui/itemwidget.cpp index b6a2880788..36940113e1 100644 --- a/apps/openmw/mwgui/itemwidget.cpp +++ b/apps/openmw/mwgui/itemwidget.cpp @@ -4,8 +4,6 @@ #include #include -#include - #include #include "../mwworld/class.hpp" @@ -17,9 +15,9 @@ namespace if (count == 1) return ""; if (count > 9999) - return boost::lexical_cast(int(count/1000.f)) + "k"; + return MyGUI::utility::toString(int(count/1000.f)) + "k"; else - return boost::lexical_cast(count); + return MyGUI::utility::toString(count); } } diff --git a/apps/openmw/mwgui/jailscreen.cpp b/apps/openmw/mwgui/jailscreen.cpp new file mode 100644 index 0000000000..548b1b604c --- /dev/null +++ b/apps/openmw/mwgui/jailscreen.cpp @@ -0,0 +1,129 @@ +#include + +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/environment.hpp" + +#include "../mwmechanics/npcstats.hpp" + +#include "../mwworld/esmstore.hpp" +#include "../mwworld/store.hpp" +#include "../mwworld/class.hpp" + +#include "jailscreen.hpp" + +namespace MWGui +{ + JailScreen::JailScreen() + : WindowBase("openmw_jail_screen.layout"), + mTimeAdvancer(0.0125), + mDays(1), + mFadeTimeRemaining(0) + { + getWidget(mProgressBar, "ProgressBar"); + + setVisible(false); + + mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &JailScreen::onJailProgressChanged); + mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &JailScreen::onJailFinished); + + center(); + } + + void JailScreen::goToJail(int days) + { + mDays = days; + + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.5); + mFadeTimeRemaining = 0.5; + + setVisible(false); + mProgressBar->setScrollRange(days*24+1); + mProgressBar->setScrollPosition(0); + mProgressBar->setTrackSize(0); + } + + void JailScreen::onFrame(float dt) + { + mTimeAdvancer.onFrame(dt); + + if (mFadeTimeRemaining <= 0) + return; + + mFadeTimeRemaining -= dt; + + if (mFadeTimeRemaining <= 0) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWBase::Environment::get().getWorld()->teleportToClosestMarker(player, "prisonmarker"); + + setVisible(true); + mTimeAdvancer.run(mDays*24); + } + } + + void JailScreen::onJailProgressChanged(int cur, int /*total*/) + { + mProgressBar->setScrollPosition(0); + mProgressBar->setTrackSize(cur / (float)(mProgressBar->getScrollRange()) * mProgressBar->getLineSize()); + } + + void JailScreen::onJailFinished() + { + MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Jail); + MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + MWBase::Environment::get().getWorld()->advanceTime(mDays * 24); + for (int i=0; irest(true); + + std::set skills; + for (int day=0; day (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 MWWorld::Store& gmst = MWBase::Environment::get().getWorld()->getStore().get(); + + std::string message; + if (mDays == 1) + message = gmst.find("sNotifyMessage42")->getString(); + else + message = gmst.find("sNotifyMessage43")->getString(); + + std::stringstream dayStr; + dayStr << mDays; + if (message.find("%d") != std::string::npos) + message.replace(message.find("%d"), 2, dayStr.str()); + + for (std::set::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 buttons; + buttons.push_back("#{sOk}"); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); + } +} diff --git a/apps/openmw/mwgui/jailscreen.hpp b/apps/openmw/mwgui/jailscreen.hpp new file mode 100644 index 0000000000..7a544126db --- /dev/null +++ b/apps/openmw/mwgui/jailscreen.hpp @@ -0,0 +1,31 @@ +#ifndef MWGUI_JAILSCREEN_H +#define MWGUI_JAILSCREEN_H + +#include "windowbase.hpp" +#include "timeadvancer.hpp" + +namespace MWGui +{ + class JailScreen : public WindowBase + { + public: + JailScreen(); + void goToJail(int days); + + void onFrame(float dt); + + private: + int mDays; + + float mFadeTimeRemaining; + + MyGUI::ScrollBar* mProgressBar; + + void onJailProgressChanged(int cur, int total); + void onJailFinished(); + + TimeAdvancer mTimeAdvancer; + }; +} + +#endif diff --git a/apps/openmw/mwgui/journalviewmodel.cpp b/apps/openmw/mwgui/journalviewmodel.cpp index 059af463fc..3a86613f61 100644 --- a/apps/openmw/mwgui/journalviewmodel.cpp +++ b/apps/openmw/mwgui/journalviewmodel.cpp @@ -8,6 +8,8 @@ #include +#include + #include "../mwbase/world.hpp" #include "../mwbase/journal.hpp" #include "../mwbase/environment.hpp" @@ -174,12 +176,14 @@ struct JournalViewModelImpl : JournalViewModel } else { + std::vector matches; + mModel->mKeywordSearch.highlightKeywords(utf8text.begin(), utf8text.end(), matches); + std::string::const_iterator i = utf8text.begin (); - - KeywordSearchT::Match match; - - while (i != utf8text.end () && mModel->mKeywordSearch.search (i, utf8text.end (), match, utf8text.begin())) + for (std::vector::const_iterator it = matches.begin(); it != matches.end(); ++it) { + const KeywordSearchT::Match& match = *it; + if (i != match.mBeg) visitor (0, i - utf8text.begin (), match.mBeg - utf8text.begin ()); diff --git a/apps/openmw/mwgui/journalwindow.cpp b/apps/openmw/mwgui/journalwindow.cpp index 0bcd5062d2..d7e27a2776 100644 --- a/apps/openmw/mwgui/journalwindow.cpp +++ b/apps/openmw/mwgui/journalwindow.cpp @@ -1,22 +1,25 @@ #include "journalwindow.hpp" -#include "../mwbase/environment.hpp" -#include "../mwbase/soundmanager.hpp" -#include "../mwbase/windowmanager.hpp" -#include "../mwbase/journal.hpp" - #include #include #include #include #include + +#include + #include #include -#include "boost/lexical_cast.hpp" +#include #include #include +#include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" +#include "../mwbase/windowmanager.hpp" +#include "../mwbase/journal.hpp" + #include "bookpage.hpp" #include "windowbase.hpp" #include "journalviewmodel.hpp" @@ -71,7 +74,7 @@ namespace void setText (char const * name, value_type const & value) { getWidget (name) -> - setCaption (boost::lexical_cast (value)); + setCaption (MyGUI::utility::toString (value)); } void setVisible (char const * name, bool visible) diff --git a/apps/openmw/mwgui/journalwindow.hpp b/apps/openmw/mwgui/journalwindow.hpp index 63770ec1aa..5d2a5318ac 100644 --- a/apps/openmw/mwgui/journalwindow.hpp +++ b/apps/openmw/mwgui/journalwindow.hpp @@ -1,7 +1,6 @@ #ifndef MWGUI_JOURNAL_H #define MWGUI_JOURNAL_H -#include #include namespace MWBase { class WindowManager; } diff --git a/apps/openmw/mwgui/levelupdialog.cpp b/apps/openmw/mwgui/levelupdialog.cpp index 56a2319470..c392372ff3 100644 --- a/apps/openmw/mwgui/levelupdialog.cpp +++ b/apps/openmw/mwgui/levelupdialog.cpp @@ -1,6 +1,8 @@ #include "levelupdialog.hpp" -#include +#include +#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -34,17 +36,17 @@ namespace MWGui for (int i=1; i<9; ++i) { MyGUI::TextBox* t; - getWidget(t, "AttribVal" + boost::lexical_cast(i)); + getWidget(t, "AttribVal" + MyGUI::utility::toString(i)); MyGUI::Button* b; - getWidget(b, "Attrib" + boost::lexical_cast(i)); + getWidget(b, "Attrib" + MyGUI::utility::toString(i)); b->setUserData (i-1); b->eventMouseButtonClick += MyGUI::newDelegate(this, &LevelupDialog::onAttributeClicked); mAttributes.push_back(b); mAttributeValues.push_back(t); - getWidget(t, "AttribMultiplier" + boost::lexical_cast(i)); + getWidget(t, "AttribMultiplier" + MyGUI::utility::toString(i)); mAttributeMultipliers.push_back(t); } @@ -76,7 +78,7 @@ namespace MWGui if (val >= 100) val = 100; - mAttributeValues[i]->setCaption(boost::lexical_cast(val)); + mAttributeValues[i]->setCaption(MyGUI::utility::toString(val)); } } @@ -137,7 +139,6 @@ namespace MWGui if(world->getStore().get().isDynamic(cls->mId)) { - // Vanilla uses thief.dds for custom classes. // Choosing Stealth specialization and Speed/Agility as attributes, if possible. Otherwise fall back to first class found. MWWorld::SharedIterator it = world->getStore().get().begin(); for(; it != world->getStore().get().end(); ++it) @@ -154,13 +155,13 @@ namespace MWGui mClassImage->setImageTexture ("textures\\levelup\\" + cls->mId + ".dds"); int level = creatureStats.getLevel ()+1; - mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + boost::lexical_cast(level)); + mLevelText->setCaptionWithReplacing("#{sLevelUpMenu1} " + MyGUI::utility::toString(level)); std::string levelupdescription; if(level > 20) levelupdescription=world->getFallback()->getFallbackString("Level_Up_Default"); else - levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+boost::lexical_cast(level)); + levelupdescription=world->getFallback()->getFallbackString("Level_Up_Level"+MyGUI::utility::toString(level)); mLevelDescription->setCaption (levelupdescription); @@ -171,14 +172,17 @@ namespace MWGui if (pcStats.getAttribute(i).getBase() < 100) { mAttributes[i]->setEnabled(true); + mAttributeValues[i]->setEnabled(true); availableAttributes++; int mult = pcStats.getLevelupAttributeMultiplier (i); - text->setCaption(mult <= 1 ? "" : "x" + boost::lexical_cast(mult)); + mult = std::min(mult, 100-pcStats.getAttribute(i).getBase()); + text->setCaption(mult <= 1 ? "" : "x" + MyGUI::utility::toString(mult)); } else { mAttributes[i]->setEnabled(false); + mAttributeValues[i]->setEnabled(false); text->setCaption(""); } diff --git a/apps/openmw/mwgui/loadingscreen.cpp b/apps/openmw/mwgui/loadingscreen.cpp index a446266d9b..9e3343c782 100644 --- a/apps/openmw/mwgui/loadingscreen.cpp +++ b/apps/openmw/mwgui/loadingscreen.cpp @@ -8,6 +8,14 @@ #include #include #include +#include + +#include +#include +#include +#include + +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -34,6 +42,7 @@ namespace MWGui getWidget(mLoadingText, "LoadingText"); getWidget(mProgressBar, "ProgressBar"); + getWidget(mLoadingBox, "LoadingBox"); mProgressBar->setScrollViewPage(1); @@ -46,6 +55,11 @@ namespace MWGui void LoadingScreen::setLabel(const std::string &label) { mLoadingText->setCaptionWithReplacing(label); + int padding = mLoadingBox->getWidth() - mLoadingText->getWidth(); + MyGUI::IntSize size(mLoadingText->getTextSize().width+padding, mLoadingBox->getHeight()); + size.width = std::max(300, size.width); + mLoadingBox->setSize(size); + mLoadingBox->setPosition(mMainWidget->getWidth()/2 - mLoadingBox->getWidth()/2, mLoadingBox->getTop()); } LoadingScreen::~LoadingScreen() @@ -137,7 +151,9 @@ namespace MWGui Ogre::TextureManager::getSingleton ().load (randomSplash, Ogre::ResourceGroupManager::AUTODETECT_RESOURCE_GROUP_NAME); // TODO: add option (filename pattern?) to use image aspect ratio instead of 4:3 - mBackgroundImage->setBackgroundImage(randomSplash, true, true); + // we can't do this by default, because the Morrowind splash screens are 1024x1024, but should be displayed as 4:3 + bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); + mBackgroundImage->setBackgroundImage(randomSplash, true, stretch); } else std::cerr << "No loading screens found!" << std::endl; diff --git a/apps/openmw/mwgui/loadingscreen.hpp b/apps/openmw/mwgui/loadingscreen.hpp index 310a6df3c2..0d3ffbbec7 100644 --- a/apps/openmw/mwgui/loadingscreen.hpp +++ b/apps/openmw/mwgui/loadingscreen.hpp @@ -1,13 +1,18 @@ #ifndef MWGUI_LOADINGSCREEN_H #define MWGUI_LOADINGSCREEN_H -#include #include +#include #include "windowbase.hpp" #include +namespace Ogre +{ + class SceneManager; +} + namespace MWGui { class BackgroundImage; @@ -47,6 +52,8 @@ namespace MWGui size_t mProgress; + MyGUI::Widget* mLoadingBox; + MyGUI::TextBox* mLoadingText; MyGUI::ScrollBar* mProgressBar; BackgroundImage* mBackgroundImage; diff --git a/apps/openmw/mwgui/mainmenu.cpp b/apps/openmw/mwgui/mainmenu.cpp index bb003c4818..6ad4da3bfb 100644 --- a/apps/openmw/mwgui/mainmenu.cpp +++ b/apps/openmw/mwgui/mainmenu.cpp @@ -2,9 +2,14 @@ #include +#include +#include +#include + #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" @@ -156,6 +161,8 @@ namespace MWGui if (!show) return; + bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); + if (mHasAnimatedMenu) { if (!mVideo) @@ -176,13 +183,7 @@ namespace MWGui int screenHeight = viewSize.height; mVideoBackground->setSize(screenWidth, screenHeight); - double imageaspect = static_cast(mVideo->getVideoWidth())/mVideo->getVideoHeight(); - - int leftPadding = std::max(0.0, (screenWidth - screenHeight * imageaspect) / 2); - int topPadding = std::max(0.0, (screenHeight - screenWidth / imageaspect) / 2); - - mVideo->setCoord(leftPadding, topPadding, - screenWidth - leftPadding*2, screenHeight - topPadding*2); + mVideo->autoResize(stretch); mVideo->setVisible(true); } @@ -192,7 +193,7 @@ namespace MWGui { mBackground = MyGUI::Gui::getInstance().createWidgetReal("ImageBox", 0,0,1,1, MyGUI::Align::Stretch, "Menu"); - mBackground->setBackgroundImage("textures\\menu_morrowind.dds"); + mBackground->setBackgroundImage("textures\\menu_morrowind.dds", true, stretch); } mBackground->setVisible(true); } diff --git a/apps/openmw/mwgui/mapwindow.cpp b/apps/openmw/mwgui/mapwindow.cpp index 0262c4d0e4..13e2e39049 100644 --- a/apps/openmw/mwgui/mapwindow.cpp +++ b/apps/openmw/mwgui/mapwindow.cpp @@ -1,29 +1,37 @@ #include "mapwindow.hpp" -#include - #include #include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwrender/globalmap.hpp" -#include "../components/esm/globalmap.hpp" - #include "widgets.hpp" #include "confirmationdialog.hpp" +#include "tooltips.hpp" namespace { - const int widgetSize = 512; - const int cellSize = 8192; enum LocalMapWidgetDepth @@ -80,35 +88,16 @@ namespace namespace MWGui { - void CustomMarker::save(ESM::ESMWriter &esm) const - { - esm.writeHNT("POSX", mWorldX); - esm.writeHNT("POSY", mWorldY); - mCell.save(esm); - if (!mNote.empty()) - esm.writeHNString("NOTE", mNote); - } - - void CustomMarker::load(ESM::ESMReader &esm) - { - esm.getHNT(mWorldX, "POSX"); - esm.getHNT(mWorldY, "POSY"); - mCell.load(esm); - mNote = esm.getHNOString("NOTE"); - } - - // ------------------------------------------------------ - - void CustomMarkerCollection::addMarker(const CustomMarker &marker, bool triggerEvent) + void CustomMarkerCollection::addMarker(const ESM::CustomMarker &marker, bool triggerEvent) { mMarkers.push_back(marker); if (triggerEvent) eventMarkersChanged(); } - void CustomMarkerCollection::deleteMarker(const CustomMarker &marker) + void CustomMarkerCollection::deleteMarker(const ESM::CustomMarker &marker) { - std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); + std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); if (it != mMarkers.end()) mMarkers.erase(it); else @@ -117,9 +106,9 @@ namespace MWGui eventMarkersChanged(); } - void CustomMarkerCollection::updateMarker(const CustomMarker &marker, const std::string &newNote) + void CustomMarkerCollection::updateMarker(const ESM::CustomMarker &marker, const std::string &newNote) { - std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); + std::vector::iterator it = std::find(mMarkers.begin(), mMarkers.end(), marker); if (it != mMarkers.end()) it->mNote = newNote; else @@ -134,12 +123,12 @@ namespace MWGui eventMarkersChanged(); } - std::vector::const_iterator CustomMarkerCollection::begin() const + std::vector::const_iterator CustomMarkerCollection::begin() const { return mMarkers.begin(); } - std::vector::const_iterator CustomMarkerCollection::end() const + std::vector::const_iterator CustomMarkerCollection::end() const { return mMarkers.end(); } @@ -164,6 +153,7 @@ namespace MWGui , mCompass(NULL) , mMarkerUpdateTimer(0.0f) , mCustomMarkers(markers) + , mMapWidgetSize(0) { mCustomMarkers.eventMarkersChanged += MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } @@ -173,26 +163,28 @@ namespace MWGui mCustomMarkers.eventMarkersChanged -= MyGUI::newDelegate(this, &LocalMapBase::updateCustomMarkers); } - void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass) + void LocalMapBase::init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize) { mLocalMap = widget; mCompass = compass; + mMapWidgetSize = mapWidgetSize; + + mLocalMap->setCanvasSize(mMapWidgetSize*3, mMapWidgetSize*3); mCompass->setDepth(Local_CompassLayer); mCompass->setNeedMouseFocus(false); - // create 3x3 map widgets, 512x512 each, holding a 1024x1024 texture each for (int mx=0; mx<3; ++mx) { for (int my=0; my<3; ++my) { MyGUI::ImageBox* map = mLocalMap->createWidget("ImageBox", - MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), + MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); map->setDepth(Local_MapLayer); MyGUI::ImageBox* fog = mLocalMap->createWidget("ImageBox", - MyGUI::IntCoord(mx*widgetSize, my*widgetSize, widgetSize, widgetSize), + MyGUI::IntCoord(mx*mMapWidgetSize, my*mMapWidgetSize, mMapWidgetSize, mMapWidgetSize), MyGUI::Align::Top | MyGUI::Align::Left); fog->setDepth(Local_FogLayer); @@ -224,8 +216,8 @@ namespace MWGui { for (int my=0; my<3; ++my) { - std::string image = mPrefix+"_"+ boost::lexical_cast(mCurX + (mx-1)) + "_" - + boost::lexical_cast(mCurY + (-1*(my-1))); + std::string image = mPrefix+"_"+ MyGUI::utility::toString(mCurX + (mx-1)) + "_" + + MyGUI::utility::toString(mCurY + (-1*(my-1))); MyGUI::ImageBox* fog = mFogWidgets[my + 3*mx]; fog->setImageTexture(mFogOfWar ? ((MyGUI::RenderManager::getInstance().getTexture(image+"_fog") != 0) ? image+"_fog" @@ -236,7 +228,7 @@ namespace MWGui redraw(); } - MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerPosition& markerPos) + MyGUI::IntPoint LocalMapBase::getMarkerPosition(float worldX, float worldY, MarkerUserData& markerPos) { MyGUI::IntPoint widgetPos; // normalized cell coordinates @@ -258,8 +250,8 @@ namespace MWGui markerPos.cellX = cellX; markerPos.cellY = cellY; - widgetPos = MyGUI::IntPoint(nX * widgetSize + (1+cellDx) * widgetSize, - nY * widgetSize - (cellDy-1) * widgetSize); + widgetPos = MyGUI::IntPoint(nX * mMapWidgetSize + (1+cellDx) * mMapWidgetSize, + nY * mMapWidgetSize - (cellDy-1) * mMapWidgetSize); } else { @@ -271,8 +263,8 @@ namespace MWGui markerPos.cellY = cellY; // Image space is -Y up, cells are Y up - widgetPos = MyGUI::IntPoint(nX * widgetSize + (1+(cellX-mCurX)) * widgetSize, - nY * widgetSize + (1-(cellY-mCurY)) * widgetSize); + widgetPos = MyGUI::IntPoint(nX * mMapWidgetSize + (1+(cellX-mCurX)) * mMapWidgetSize, + nY * mMapWidgetSize + (1-(cellY-mCurY)) * mMapWidgetSize); } markerPos.nX = nX; @@ -286,9 +278,9 @@ namespace MWGui MyGUI::Gui::getInstance().destroyWidget(*it); mCustomMarkerWidgets.clear(); - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) { - const CustomMarker& marker = *it; + const ESM::CustomMarker& marker = *it; if (marker.mCell.mPaged != !mInterior) continue; @@ -305,7 +297,7 @@ namespace MWGui continue; } - MarkerPosition markerPos; + MarkerUserData markerPos; MyGUI::IntPoint widgetPos = getMarkerPosition(marker.mWorldX, marker.mWorldY, markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, @@ -351,8 +343,8 @@ namespace MWGui for (int my=0; my<3; ++my) { // map - std::string image = mPrefix+"_"+ boost::lexical_cast(x + (mx-1)) + "_" - + boost::lexical_cast(y + (-1*(my-1))); + std::string image = mPrefix+"_"+ MyGUI::utility::toString(x + (mx-1)) + "_" + + MyGUI::utility::toString(y + (-1*(my-1))); MyGUI::ImageBox* box = mMapWidgets[my + 3*mx]; @@ -390,8 +382,17 @@ namespace MWGui { MWBase::World::DoorMarker marker = *it; - MarkerPosition markerPos; - MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, markerPos); + std::vector destNotes; + for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + { + if (it->mCell == marker.dest) + destNotes.push_back(it->mNote); + } + + MarkerUserData data; + data.notes = destNotes; + data.caption = marker.name; + MyGUI::IntPoint widgetPos = getMarkerPosition(marker.x, marker.y, data); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, 8, 8); @@ -402,12 +403,10 @@ namespace MWGui markerWidget->setHoverColour(MyGUI::Colour::parse(MyGUI::LanguageManager::getInstance().replaceTags("#{fontcolour=normal_over}"))); markerWidget->setDepth(Local_MarkerLayer); markerWidget->setNeedMouseFocus(true); - markerWidget->setUserString("ToolTipType", "Layout"); - markerWidget->setUserString("ToolTipLayout", "TextToolTipOneLine"); - markerWidget->setUserString("Caption_TextOneLine", marker.name); // Used by tooltips to not show the tooltip if marker is hidden by fog of war - markerWidget->setUserString("IsMarker", "true"); - markerWidget->setUserData(markerPos); + markerWidget->setUserString("ToolTipType", "MapMarker"); + + markerWidget->setUserData(data); doorMarkerCreated(markerWidget); mDoorMarkerWidgets.push_back(markerWidget); @@ -425,9 +424,9 @@ namespace MWGui void LocalMapBase::setPlayerPos(int cellX, int cellY, const float nx, const float ny) { - MyGUI::IntPoint pos(widgetSize+nx*widgetSize-16, widgetSize+ny*widgetSize-16); - pos.left += (cellX - mCurX) * widgetSize; - pos.top -= (cellY - mCurY) * widgetSize; + MyGUI::IntPoint pos(mMapWidgetSize+nx*mMapWidgetSize-16, mMapWidgetSize+ny*mMapWidgetSize-16); + pos.left += (cellX - mCurX) * mMapWidgetSize; + pos.top -= (cellY - mCurY) * mMapWidgetSize; if (pos != mCompass->getPosition()) { @@ -490,7 +489,7 @@ namespace MWGui for (std::vector::iterator it = markers.begin(); it != markers.end(); ++it) { const ESM::Position& worldPos = it->getRefData().getPosition(); - MarkerPosition markerPos; + MarkerUserData markerPos; MyGUI::IntPoint widgetPos = getMarkerPosition(worldPos.pos[0], worldPos.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, @@ -500,9 +499,8 @@ namespace MWGui widgetCoord, MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture(markerTexture); - markerWidget->setUserString("IsMarker", "true"); - markerWidget->setUserData(markerPos); markerWidget->setColour(markerColour); + markerWidget->setNeedMouseFocus(false); mMagicMarkerWidgets.push_back(markerWidget); } } @@ -536,7 +534,7 @@ namespace MWGui if (markedCell && markedCell->isExterior() == !mInterior && (!mInterior || Misc::StringUtils::ciEqual(markedCell->getCell()->mName, mPrefix))) { - MarkerPosition markerPos; + MarkerUserData markerPos; MyGUI::IntPoint widgetPos = getMarkerPosition(markedPosition.pos[0], markedPosition.pos[1], markerPos); MyGUI::IntCoord widgetCoord(widgetPos.left - 4, widgetPos.top - 4, @@ -545,8 +543,7 @@ namespace MWGui widgetCoord, MyGUI::Align::Default); markerWidget->setDepth(Local_MarkerAboveFogLayer); markerWidget->setImageTexture("textures\\menu_map_smark.dds"); - markerWidget->setUserString("IsMarker", "true"); - markerWidget->setUserData(markerPos); + markerWidget->setNeedMouseFocus(false); mMagicMarkerWidgets.push_back(markerWidget); } @@ -612,8 +609,7 @@ namespace MWGui mEventBoxLocal->eventMouseButtonPressed += MyGUI::newDelegate(this, &MapWindow::onDragStart); mEventBoxLocal->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &MapWindow::onMapDoubleClicked); - LocalMapBase::init(mLocalMap, mPlayerArrowLocal); - } + LocalMapBase::init(mLocalMap, mPlayerArrowLocal, Settings::Manager::getInt("local map widget size", "Map")); } void MapWindow::onNoteEditOk() { @@ -646,7 +642,7 @@ namespace MWGui void MapWindow::onCustomMarkerDoubleClicked(MyGUI::Widget *sender) { - mEditingMarker = *sender->getUserData(); + mEditingMarker = *sender->getUserData(); mEditNoteDialog.setText(mEditingMarker.mNote); mEditNoteDialog.showDeleteButton(true); mEditNoteDialog.setVisible(true); @@ -657,10 +653,10 @@ namespace MWGui MyGUI::IntPoint clickedPos = MyGUI::InputManager::getInstance().getMousePosition(); MyGUI::IntPoint widgetPos = clickedPos - mEventBoxLocal->getAbsolutePosition(); - int x = int(widgetPos.left/float(widgetSize))-1; - int y = (int(widgetPos.top/float(widgetSize))-1)*-1; - float nX = widgetPos.left/float(widgetSize) - int(widgetPos.left/float(widgetSize)); - float nY = widgetPos.top/float(widgetSize) - int(widgetPos.top/float(widgetSize)); + int x = int(widgetPos.left/float(mMapWidgetSize))-1; + int y = (int(widgetPos.top/float(mMapWidgetSize))-1)*-1; + float nX = widgetPos.left/float(mMapWidgetSize) - int(widgetPos.left/float(mMapWidgetSize)); + float nY = widgetPos.top/float(mMapWidgetSize) - int(widgetPos.top/float(mMapWidgetSize)); x += mCurX; y += mCurY; @@ -895,10 +891,9 @@ namespace MWGui writer.startRecord(ESM::REC_GMAP); map.save(writer); writer.endRecord(ESM::REC_GMAP); - progress.increaseProgress(); } - void MapWindow::readRecord(ESM::ESMReader &reader, int32_t type) + void MapWindow::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_GMAP) { diff --git a/apps/openmw/mwgui/mapwindow.hpp b/apps/openmw/mwgui/mapwindow.hpp index a8aaa8e45f..a80b3e4c59 100644 --- a/apps/openmw/mwgui/mapwindow.hpp +++ b/apps/openmw/mwgui/mapwindow.hpp @@ -7,6 +7,8 @@ #include +#include + namespace MWRender { class GlobalMap; @@ -26,43 +28,25 @@ namespace Loading namespace MWGui { - struct CustomMarker - { - float mWorldX; - float mWorldY; - - ESM::CellId mCell; - - std::string mNote; - - bool operator == (const CustomMarker& other) - { - return mNote == other.mNote && mCell == other.mCell && mWorldX == other.mWorldX && mWorldY == other.mWorldY; - } - - void load (ESM::ESMReader& reader); - void save (ESM::ESMWriter& writer) const; - }; - class CustomMarkerCollection { public: - void addMarker(const CustomMarker& marker, bool triggerEvent=true); - void deleteMarker (const CustomMarker& marker); - void updateMarker(const CustomMarker& marker, const std::string& newNote); + void addMarker(const ESM::CustomMarker& marker, bool triggerEvent=true); + void deleteMarker (const ESM::CustomMarker& marker); + void updateMarker(const ESM::CustomMarker& marker, const std::string& newNote); void clear(); size_t size() const; - std::vector::const_iterator begin() const; - std::vector::const_iterator end() const; + std::vector::const_iterator begin() const; + std::vector::const_iterator end() const; typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; EventHandle_Void eventMarkersChanged; private: - std::vector mMarkers; + std::vector mMarkers; }; class LocalMapBase @@ -70,7 +54,7 @@ namespace MWGui public: LocalMapBase(CustomMarkerCollection& markers); virtual ~LocalMapBase(); - void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass); + void init(MyGUI::ScrollView* widget, MyGUI::ImageBox* compass, int mapWidgetSize); void setCellPrefix(const std::string& prefix); void setActiveCell(const int x, const int y, bool interior=false); @@ -81,13 +65,15 @@ namespace MWGui bool toggleFogOfWar(); - struct MarkerPosition + struct MarkerUserData { bool interior; int cellX; int cellY; float nX; float nY; + std::vector notes; + std::string caption; }; protected: @@ -99,6 +85,8 @@ namespace MWGui bool mChanged; bool mFogOfWar; + int mMapWidgetSize; + // Stores markers that were placed by a player. May be shared between multiple map views. CustomMarkerCollection& mCustomMarkers; @@ -114,7 +102,7 @@ namespace MWGui void applyFogOfWar(); - MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerPosition& markerPos); + MyGUI::IntPoint getMarkerPosition (float worldX, float worldY, MarkerUserData& markerPos); virtual void notifyPlayerUpdate() {} virtual void notifyMapChanged() {} @@ -191,7 +179,7 @@ namespace MWGui void clear(); void write (ESM::ESMWriter& writer, Loading::Listener& progress); - void readRecord (ESM::ESMReader& reader, int32_t type); + void readRecord (ESM::ESMReader& reader, uint32_t type); private: void onDragStart(MyGUI::Widget* _sender, int _left, int _top, MyGUI::MouseButton _id); @@ -231,7 +219,7 @@ namespace MWGui MWRender::GlobalMap* mGlobalMapRender; EditNoteDialog mEditNoteDialog; - CustomMarker mEditingMarker; + ESM::CustomMarker mEditingMarker; virtual void onPinToggled(); virtual void onTitleDoubleClicked(); diff --git a/apps/openmw/mwgui/merchantrepair.cpp b/apps/openmw/mwgui/merchantrepair.cpp index e85681c048..907c664b1a 100644 --- a/apps/openmw/mwgui/merchantrepair.cpp +++ b/apps/openmw/mwgui/merchantrepair.cpp @@ -2,7 +2,9 @@ #include -#include +#include +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -67,7 +69,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) std::string name = iter->getClass().getName(*iter) - + " - " + boost::lexical_cast(price) + + " - " + MyGUI::utility::toString(price) + MWBase::Environment::get().getWorld()->getStore().get() .find("sgp")->getString(); @@ -83,7 +85,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) currentY += 18; - button->setUserString("Price", boost::lexical_cast(price)); + button->setUserString("Price", MyGUI::utility::toString(price)); button->setUserData(*iter); button->setCaptionWithReplacing(name); button->setSize(button->getTextSize().width,18); @@ -98,7 +100,7 @@ void MerchantRepair::startRepair(const MWWorld::Ptr &actor) mList->setVisibleVScroll(true); mGoldLabel->setCaptionWithReplacing("#{sGold}: " - + boost::lexical_cast(playerGold)); + + MyGUI::utility::toString(playerGold)); } void MerchantRepair::onMouseWheel(MyGUI::Widget* _sender, int _rel) @@ -123,7 +125,7 @@ void MerchantRepair::onRepairButtonClick(MyGUI::Widget *sender) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - int price = boost::lexical_cast(sender->getUserString("Price")); + int price = MyGUI::utility::parseInt(sender->getUserString("Price")); if (price > player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId)) return; diff --git a/apps/openmw/mwgui/merchantrepair.hpp b/apps/openmw/mwgui/merchantrepair.hpp index 2f13873654..231d11089b 100644 --- a/apps/openmw/mwgui/merchantrepair.hpp +++ b/apps/openmw/mwgui/merchantrepair.hpp @@ -4,8 +4,6 @@ #include "windowbase.hpp" #include "../mwworld/ptr.hpp" - - namespace MWGui { diff --git a/apps/openmw/mwgui/messagebox.cpp b/apps/openmw/mwgui/messagebox.cpp index d4520aad7e..cdbcf784df 100644 --- a/apps/openmw/mwgui/messagebox.cpp +++ b/apps/openmw/mwgui/messagebox.cpp @@ -1,9 +1,18 @@ +#include "messagebox.hpp" + +#include +#include +#include +#include + #include -#include "messagebox.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/soundmanager.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/windowmanager.hpp" + +#undef MessageBox namespace MWGui { @@ -139,10 +148,11 @@ namespace MWGui return false; } - int MessageBoxManager::readPressedButton () + int MessageBoxManager::readPressedButton (bool reset) { int pressed = mLastButtonPressed; - mLastButtonPressed = -1; + if (reset) + mLastButtonPressed = -1; return pressed; } diff --git a/apps/openmw/mwgui/messagebox.hpp b/apps/openmw/mwgui/messagebox.hpp index 5f180df204..48a92c844b 100644 --- a/apps/openmw/mwgui/messagebox.hpp +++ b/apps/openmw/mwgui/messagebox.hpp @@ -3,8 +3,6 @@ #include "windowbase.hpp" -#include "../mwbase/windowmanager.hpp" - #undef MessageBox namespace MyGUI @@ -35,7 +33,8 @@ namespace MWGui bool removeMessageBox (MessageBox *msgbox); - int readPressedButton (); + /// @param reset Reset the pressed button to -1 after reading it. + int readPressedButton (bool reset=true); typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Int; diff --git a/apps/openmw/mwgui/mode.hpp b/apps/openmw/mwgui/mode.hpp index a1688d2e53..db851e0674 100644 --- a/apps/openmw/mwgui/mode.hpp +++ b/apps/openmw/mwgui/mode.hpp @@ -46,6 +46,7 @@ namespace MWGui GM_Loading, GM_LoadingWallpaper, + GM_Jail, GM_QuickKeysMenu }; diff --git a/apps/openmw/mwgui/quickkeysmenu.cpp b/apps/openmw/mwgui/quickkeysmenu.cpp index 440165e749..a102de1e56 100644 --- a/apps/openmw/mwgui/quickkeysmenu.cpp +++ b/apps/openmw/mwgui/quickkeysmenu.cpp @@ -1,6 +1,8 @@ #include "quickkeysmenu.hpp" -#include +#include +#include +#include #include #include @@ -8,12 +10,12 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/esmstore.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" @@ -23,7 +25,8 @@ #include "windowmanagerimp.hpp" #include "itemselection.hpp" -#include "spellwindow.hpp" +#include "spellview.hpp" + #include "itemwidget.hpp" #include "sortfilteritemmodel.hpp" @@ -52,7 +55,7 @@ namespace MWGui for (int i = 0; i < 10; ++i) { ItemWidget* button; - getWidget(button, "QuickKey" + boost::lexical_cast(i+1)); + getWidget(button, "QuickKey" + MyGUI::utility::toString(i+1)); button->eventMouseButtonClick += MyGUI::newDelegate(this, &QuickKeysMenu::onQuickKeyButtonClicked); @@ -95,7 +98,7 @@ namespace MWGui MyGUI::TextBox* textBox = key->createWidgetReal("SandText", MyGUI::FloatCoord(0,0,1,1), MyGUI::Align::Default); textBox->setTextAlign (MyGUI::Align::Center); - textBox->setCaption (boost::lexical_cast(index+1)); + textBox->setCaption (MyGUI::utility::toString(index+1)); textBox->setNeedMouseFocus (false); } @@ -326,6 +329,10 @@ namespace MWGui if (!item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + + // make sure that item was successfully equipped + if (!store.isEquipped(item)) + return; } store.setSelectedEnchantItem(it); @@ -424,7 +431,7 @@ namespace MWGui writer.endRecord(ESM::REC_KEYS); } - void QuickKeysMenu::readRecord(ESM::ESMReader &reader, int32_t type) + void QuickKeysMenu::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type != ESM::REC_KEYS) return; @@ -495,13 +502,15 @@ namespace MWGui MagicSelectionDialog::MagicSelectionDialog(QuickKeysMenu* parent) : WindowModal("openmw_magicselection_dialog.layout") , mParent(parent) - , mWidth(0) - , mHeight(0) { getWidget(mCancelButton, "CancelButton"); getWidget(mMagicList, "MagicList"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onCancelButtonClicked); + mMagicList->setShowCostColumn(false); + mMagicList->setHighlightSelected(false); + mMagicList->eventSpellClicked += MyGUI::newDelegate(this, &MagicSelectionDialog::onModelIndexSelected); + center(); } @@ -519,211 +528,17 @@ namespace MWGui { WindowModal::open(); - while (mMagicList->getChildCount ()) - MyGUI::Gui::getInstance ().destroyWidget (mMagicList->getChildAt (0)); - - mHeight = 0; - - const int spellHeight = 18; - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - - /// \todo lots of copy&pasted code from SpellWindow - - // retrieve powers & spells, sort by name - std::vector spellList; - - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - { - spellList.push_back (it->first); - } - - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - std::vector powers; - std::vector::iterator it = spellList.begin(); - while (it != spellList.end()) - { - const ESM::Spell* spell = esmStore.get().find(*it); - if (spell->mData.mType == ESM::Spell::ST_Power) - { - powers.push_back(*it); - it = spellList.erase(it); - } - else if (spell->mData.mType == ESM::Spell::ST_Ability - || spell->mData.mType == ESM::Spell::ST_Blight - || spell->mData.mType == ESM::Spell::ST_Curse - || spell->mData.mType == ESM::Spell::ST_Disease) - { - it = spellList.erase(it); - } - else - ++it; - } - std::sort(powers.begin(), powers.end(), sortSpells); - std::sort(spellList.begin(), spellList.end(), sortSpells); - - // retrieve usable magic items & sort - std::vector items; - for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) - { - std::string enchantId = it->getClass().getEnchantment(*it); - if (enchantId != "") - { - // only add items with "Cast once" or "Cast on use" - const ESM::Enchantment* enchant = - esmStore.get().find(enchantId); - - int type = enchant->mData.mType; - if (type != ESM::Enchantment::CastOnce - && type != ESM::Enchantment::WhenUsed) - continue; - - items.push_back(*it); - } - } - std::sort(items.begin(), items.end(), sortItems); - - - int height = estimateHeight(items.size() + powers.size() + spellList.size()); - bool scrollVisible = height > mMagicList->getHeight(); - mWidth = mMagicList->getWidth() - scrollVisible * 18; - - - // powers - addGroup("#{sPowers}", ""); - - for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mMagicList->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected); - - mHeight += spellHeight; - } - - // other spells - addGroup("#{sSpells}", ""); - for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mMagicList->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onSpellSelected); - - mHeight += spellHeight; - } - - - // enchanted items - addGroup("#{sMagicItem}", ""); - - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) - { - MWWorld::Ptr item = *it; - - // check if the item is currently equipped (will display in a different color) - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (store.getSlot(i) != store.end() && *store.getSlot(i) == item) - { - equipped = true; - break; - } - } - - MyGUI::Button* t = mMagicList->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(item.getClass().getName(item)); - t->setTextAlign(MyGUI::Align::Left); - t->setUserData(item); - t->setUserString("ToolTipType", "ItemPtr"); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &MagicSelectionDialog::onEnchantedItemSelected); - t->eventMouseWheel += MyGUI::newDelegate(this, &MagicSelectionDialog::onMouseWheel); - - mHeight += spellHeight; - } - - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden - mMagicList->setVisibleVScroll(false); - mMagicList->setCanvasSize (mWidth, std::max(mMagicList->getHeight(), mHeight)); - mMagicList->setVisibleVScroll(true); + mMagicList->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mMagicList->update(); } - void MagicSelectionDialog::addGroup(const std::string &label, const std::string& label2) + void MagicSelectionDialog::onModelIndexSelected(SpellModel::ModelIndex index) { - if (mMagicList->getChildCount() > 0) - { - MyGUI::ImageBox* separator = mMagicList->createWidget("MW_HLine", - MyGUI::IntCoord(4, mHeight, mWidth-8, 18), - MyGUI::Align::Left | MyGUI::Align::Top); - separator->setNeedMouseFocus(false); - mHeight += 18; - } - - MyGUI::TextBox* groupWidget = mMagicList->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth, 24), - MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - groupWidget->setCaptionWithReplacing(label); - groupWidget->setTextAlign(MyGUI::Align::Left); - groupWidget->setNeedMouseFocus(false); - - if (label2 != "") - { - MyGUI::TextBox* groupWidget2 = mMagicList->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth-4, 24), - MyGUI::Align::Left | MyGUI::Align::Top); - groupWidget2->setCaptionWithReplacing(label2); - groupWidget2->setTextAlign(MyGUI::Align::Right); - groupWidget2->setNeedMouseFocus(false); - } - - mHeight += 24; - } - - - void MagicSelectionDialog::onMouseWheel(MyGUI::Widget* _sender, int _rel) - { - if (mMagicList->getViewOffset().top + _rel*0.3 > 0) - mMagicList->setViewOffset(MyGUI::IntPoint(0, 0)); + const Spell& spell = mMagicList->getModel()->getItem(index); + if (spell.mType == Spell::Type_EnchantedItem) + mParent->onAssignMagicItem(spell.mItem); else - mMagicList->setViewOffset(MyGUI::IntPoint(0, mMagicList->getViewOffset().top + _rel*0.3)); - } - - void MagicSelectionDialog::onEnchantedItemSelected(MyGUI::Widget* _sender) - { - MWWorld::Ptr item = *_sender->getUserData(); - - mParent->onAssignMagicItem (item); - } - - void MagicSelectionDialog::onSpellSelected(MyGUI::Widget* _sender) - { - mParent->onAssignMagic (_sender->getUserString("Spell")); - } - - int MagicSelectionDialog::estimateHeight(int numSpells) const - { - int height = 0; - height += 24 * 3 + 18 * 2; // group headings - height += numSpells * 18; - return height; + mParent->onAssignMagic(spell.mId); } } diff --git a/apps/openmw/mwgui/quickkeysmenu.hpp b/apps/openmw/mwgui/quickkeysmenu.hpp index dc088d3c9a..00afa45610 100644 --- a/apps/openmw/mwgui/quickkeysmenu.hpp +++ b/apps/openmw/mwgui/quickkeysmenu.hpp @@ -5,6 +5,8 @@ #include "windowbase.hpp" +#include "spellmodel.hpp" + namespace MWGui { @@ -12,6 +14,7 @@ namespace MWGui class ItemSelectionDialog; class MagicSelectionDialog; class ItemWidget; + class SpellView; class QuickKeysMenu : public WindowBase { @@ -44,7 +47,7 @@ namespace MWGui void write (ESM::ESMWriter& writer); - void readRecord (ESM::ESMReader& reader, int32_t type); + void readRecord (ESM::ESMReader& reader, uint32_t type); void clear(); @@ -94,21 +97,12 @@ namespace MWGui private: MyGUI::Button* mCancelButton; - MyGUI::ScrollView* mMagicList; - - int mWidth; - int mHeight; + SpellView* mMagicList; QuickKeysMenu* mParent; void onCancelButtonClicked (MyGUI::Widget* sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); - void onEnchantedItemSelected(MyGUI::Widget* _sender); - void onSpellSelected(MyGUI::Widget* _sender); - int estimateHeight(int numSpells) const; - - - void addGroup(const std::string& label, const std::string& label2); + void onModelIndexSelected(SpellModel::ModelIndex index); }; } diff --git a/apps/openmw/mwgui/race.cpp b/apps/openmw/mwgui/race.cpp index bcb766f8f3..b03bf758af 100644 --- a/apps/openmw/mwgui/race.cpp +++ b/apps/openmw/mwgui/race.cpp @@ -1,11 +1,17 @@ #include "race.hpp" -#include +#include +#include +#include +#include + #include +#include "../mwworld/esmstore.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwrender/characterpreview.hpp" #include "tooltips.hpp" @@ -20,6 +26,12 @@ namespace else return index; } + + bool sortRaces(const std::pair& left, const std::pair& right) + { + return left.second.compare(right.second) < 0; + } + } namespace MWGui @@ -122,7 +134,7 @@ namespace MWGui mPreview.reset(new MWRender::RaceSelectionPreview()); mPreview->setup(); - mPreview->update (0); + mPreview->update (mCurrentAngle); const ESM::NPC proto = mPreview->getPrototype(); setRaceId(proto.mRace); @@ -143,8 +155,11 @@ namespace MWGui mPreviewImage->setImageTexture (textureName); mPreviewDirty = true; - } + size_t initialPos = mHeadRotate->getScrollRange()/2+mHeadRotate->getScrollRange()/10; + mHeadRotate->setScrollPosition(initialPos); + onHeadRotate(mHeadRotate, initialPos); + } void RaceDialog::setRaceId(const std::string &raceId) { @@ -156,8 +171,6 @@ namespace MWGui if (Misc::StringUtils::ciEqual(*mRaceList->getItemDataAt(i), raceId)) { mRaceList->setIndexSelected(i); - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); break; } } @@ -191,10 +204,9 @@ namespace MWGui void RaceDialog::onHeadRotate(MyGUI::ScrollBar* scroll, size_t _position) { float angle = (float(_position) / (scroll->getScrollRange()-1) - 0.5) * 3.14 * 2; - float diff = angle - mCurrentAngle; - mPreview->update (diff); + mPreview->update (angle); mPreviewDirty = true; - mCurrentAngle += diff; + mCurrentAngle = angle; } void RaceDialog::onSelectPreviousGender(MyGUI::Widget*) @@ -242,8 +254,6 @@ namespace MWGui if (_index == MyGUI::ITEM_NONE) return; - MyGUI::Button* okButton; - getWidget(okButton, "OKButton"); const std::string *raceId = mRaceList->getItemDataAt(_index); if (Misc::StringUtils::ciEqual(mCurrentRaceId, *raceId)) return; @@ -345,8 +355,7 @@ namespace MWGui const MWWorld::Store &races = MWBase::Environment::get().getWorld()->getStore().get(); - - int index = 0; + std::vector > items; // ID, name MWWorld::Store::iterator it = races.begin(); for (; it != races.end(); ++it) { @@ -354,8 +363,15 @@ namespace MWGui if (!playable) // Only display playable races continue; - mRaceList->addItem(it->mName, it->mId); - if (Misc::StringUtils::ciEqual(it->mId, mCurrentRaceId)) + items.push_back(std::make_pair(it->mId, it->mName)); + } + std::sort(items.begin(), items.end(), sortRaces); + + int index = 0; + for (std::vector >::const_iterator it = items.begin(); it != items.end(); ++it) + { + mRaceList->addItem(it->second, it->first); + if (Misc::StringUtils::ciEqual(it->first, mCurrentRaceId)) mRaceList->setIndexSelected(index); ++index; } @@ -386,7 +402,7 @@ namespace MWGui continue; skillWidget = mSkillList->createWidget("MW_StatNameValue", coord1, MyGUI::Align::Default, - std::string("Skill") + boost::lexical_cast(i)); + std::string("Skill") + MyGUI::utility::toString(i)); skillWidget->setSkillNumber(skillId); skillWidget->setSkillValue(Widgets::MWSkill::SkillValue(race->mData.mBonus[i].mBonus)); ToolTips::createSkillToolTip(skillWidget, skillId); @@ -420,7 +436,7 @@ namespace MWGui for (int i = 0; it != end; ++it) { const std::string &spellpower = *it; - Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + boost::lexical_cast(i)); + Widgets::MWSpellPtr spellPowerWidget = mSpellPowerList->createWidget("MW_StatName", coord, MyGUI::Align::Default, std::string("SpellPower") + MyGUI::utility::toString(i)); spellPowerWidget->setSpellId(spellpower); spellPowerWidget->setUserString("ToolTipType", "Spell"); spellPowerWidget->setUserString("Spell", spellpower); @@ -431,4 +447,9 @@ namespace MWGui ++i; } } + + const ESM::NPC& RaceDialog::getResult() const + { + return mPreview->getPrototype(); + } } diff --git a/apps/openmw/mwgui/race.hpp b/apps/openmw/mwgui/race.hpp index 454c1d0b61..be16af5d1e 100644 --- a/apps/openmw/mwgui/race.hpp +++ b/apps/openmw/mwgui/race.hpp @@ -1,8 +1,6 @@ #ifndef MWGUI_RACE_H #define MWGUI_RACE_H -#include "../mwrender/characterpreview.hpp" - #include "windowbase.hpp" @@ -11,10 +9,15 @@ namespace MWGui class WindowManager; } -/* - This file contains the dialog for choosing a race. - Layout is defined by resources/mygui/openmw_chargen_race.layout. - */ +namespace MWRender +{ + class RaceSelectionPreview; +} + +namespace ESM +{ + struct NPC; +} namespace MWGui { @@ -29,7 +32,7 @@ namespace MWGui GM_Female }; - const ESM::NPC &getResult() const { return mPreview->getPrototype(); } + const ESM::NPC &getResult() const; const std::string &getRaceId() const { return mCurrentRaceId; } Gender getGender() const { return mGenderIndex == 0 ? GM_Male : GM_Female; } // getFace() diff --git a/apps/openmw/mwgui/recharge.cpp b/apps/openmw/mwgui/recharge.cpp index c45c2566ea..2c854a8f54 100644 --- a/apps/openmw/mwgui/recharge.cpp +++ b/apps/openmw/mwgui/recharge.cpp @@ -1,14 +1,19 @@ #include "recharge.hpp" -#include #include +#include +#include + +#include + #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" @@ -60,7 +65,7 @@ void Recharge::updateView() std::string soul = gem.getCellRef().getSoul(); const ESM::Creature *creature = MWBase::Environment::get().getWorld()->getStore().get().find(soul); - mChargeLabel->setCaptionWithReplacing("#{sCharges} " + boost::lexical_cast(creature->mData.mSoul)); + mChargeLabel->setCaptionWithReplacing("#{sCharges} " + MyGUI::utility::toString(creature->mData.mSoul)); bool toolBoxVisible = (gem.getRefData().getCount() != 0); mGemBox->setVisible(toolBoxVisible); diff --git a/apps/openmw/mwgui/recharge.hpp b/apps/openmw/mwgui/recharge.hpp index 17d700649b..3e8e1269e7 100644 --- a/apps/openmw/mwgui/recharge.hpp +++ b/apps/openmw/mwgui/recharge.hpp @@ -3,7 +3,10 @@ #include "windowbase.hpp" -#include "../mwworld/ptr.hpp" +namespace MWWorld +{ + class Ptr; +} namespace MWGui { diff --git a/apps/openmw/mwgui/repair.cpp b/apps/openmw/mwgui/repair.cpp index 019f341b57..c3c9714005 100644 --- a/apps/openmw/mwgui/repair.cpp +++ b/apps/openmw/mwgui/repair.cpp @@ -2,7 +2,8 @@ #include -#include +#include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" @@ -65,7 +66,7 @@ void Repair::updateRepairView() std::stringstream qualityStr; qualityStr << std::setprecision(3) << quality; - mUsesLabel->setCaptionWithReplacing("#{sUses} " + boost::lexical_cast(uses)); + mUsesLabel->setCaptionWithReplacing("#{sUses} " + MyGUI::utility::toString(uses)); mQualityLabel->setCaptionWithReplacing("#{sQuality} " + qualityStr.str()); bool toolBoxVisible = (mRepair.getTool().getRefData().getCount() != 0); diff --git a/apps/openmw/mwgui/review.cpp b/apps/openmw/mwgui/review.cpp index d1f8a76a62..1bcd2d31aa 100644 --- a/apps/openmw/mwgui/review.cpp +++ b/apps/openmw/mwgui/review.cpp @@ -1,16 +1,29 @@ #include "review.hpp" -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/esmstore.hpp" #include "tooltips.hpp" #undef min #undef max +namespace +{ + void adjustButtonSize(MyGUI::Button *button) + { + // adjust size of button to fit its text + MyGUI::IntSize size = button->getTextSize(); + button->setSize(size.width + 24, button->getSize().height); + } +} + namespace MWGui { @@ -62,7 +75,7 @@ namespace MWGui Widgets::MWAttributePtr attribute; for (int idx = 0; idx < ESM::Attribute::Length; ++idx) { - getWidget(attribute, std::string("Attribute") + boost::lexical_cast(idx)); + getWidget(attribute, std::string("Attribute") + MyGUI::utility::toString(idx)); mAttributeWidgets.insert(std::make_pair(static_cast(ESM::Attribute::sAttributeIds[idx]), attribute)); attribute->setAttributeId(ESM::Attribute::sAttributeIds[idx]); attribute->setAttributeValue(Widgets::MWAttribute::AttributeValue()); @@ -134,21 +147,21 @@ namespace MWGui void ReviewDialog::setHealth(const MWMechanics::DynamicStat& value) { mHealth->setValue(value.getCurrent(), value.getModified()); - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + std::string valStr = MyGUI::utility::toString(value.getCurrent()) + "/" + MyGUI::utility::toString(value.getModified()); mHealth->setUserString("Caption_HealthDescription", "#{sHealthDesc}\n" + valStr); } void ReviewDialog::setMagicka(const MWMechanics::DynamicStat& value) { mMagicka->setValue(value.getCurrent(), value.getModified()); - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + std::string valStr = MyGUI::utility::toString(value.getCurrent()) + "/" + MyGUI::utility::toString(value.getModified()); mMagicka->setUserString("Caption_HealthDescription", "#{sIntDesc}\n" + valStr); } void ReviewDialog::setFatigue(const MWMechanics::DynamicStat& value) { mFatigue->setValue(value.getCurrent(), value.getModified()); - std::string valStr = boost::lexical_cast(value.getCurrent()) + "/" + boost::lexical_cast(value.getModified()); + std::string valStr = MyGUI::utility::toString(value.getCurrent()) + "/" + MyGUI::utility::toString(value.getModified()); mFatigue->setUserString("Caption_HealthDescription", "#{sFatDesc}\n" + valStr); } @@ -168,7 +181,7 @@ namespace MWGui if (widget) { float modified = value.getModified(), base = value.getBase(); - std::string text = boost::lexical_cast(std::floor(modified)); + std::string text = MyGUI::utility::toString(std::floor(modified)); std::string state = "normal"; if (modified > base) state = "increased"; @@ -288,7 +301,7 @@ namespace MWGui state = "increased"; else if (modified < base) state = "decreased"; - MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), boost::lexical_cast(static_cast(modified)), state, coord1, coord2); + MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); for (int i=0; i<2; ++i) { diff --git a/apps/openmw/mwgui/review.hpp b/apps/openmw/mwgui/review.hpp index 01b106d907..111d7de1dc 100644 --- a/apps/openmw/mwgui/review.hpp +++ b/apps/openmw/mwgui/review.hpp @@ -1,6 +1,8 @@ #ifndef MWGUI_REVIEW_H #define MWGUI_REVIEW_H +#include +#include #include "windowbase.hpp" #include "widgets.hpp" @@ -9,11 +11,6 @@ namespace MWGui class WindowManager; } -/* -This file contains the dialog for reviewing the generated character. -Layout is defined by resources/mygui/openmw_chargen_review.layout. -*/ - namespace MWGui { class ReviewDialog : public WindowModal diff --git a/apps/openmw/mwgui/savegamedialog.cpp b/apps/openmw/mwgui/savegamedialog.cpp index 66c7a92386..d022c8e251 100644 --- a/apps/openmw/mwgui/savegamedialog.cpp +++ b/apps/openmw/mwgui/savegamedialog.cpp @@ -4,6 +4,11 @@ #include #include +#include +#include +#include +#include + #include #include @@ -12,6 +17,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwstate/character.hpp" @@ -245,7 +251,7 @@ namespace MWGui else { assert (mCurrentCharacter && mCurrentSlot); - MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot); + MWBase::Environment::get().getStateManager()->loadGame (mCurrentCharacter, mCurrentSlot->mPath.string()); } } @@ -304,7 +310,7 @@ namespace MWGui mOkButton->setEnabled(pos != MyGUI::ITEM_NONE || mSaving); mDeleteButton->setEnabled(pos != MyGUI::ITEM_NONE); - if (pos == MyGUI::ITEM_NONE) + if (pos == MyGUI::ITEM_NONE || !mCurrentCharacter) { mCurrentSlot = NULL; mInfoText->setCaption(""); diff --git a/apps/openmw/mwgui/screenfader.cpp b/apps/openmw/mwgui/screenfader.cpp index a0421ec288..473776a82a 100644 --- a/apps/openmw/mwgui/screenfader.cpp +++ b/apps/openmw/mwgui/screenfader.cpp @@ -1,5 +1,7 @@ #include "screenfader.hpp" +#include + namespace MWGui { diff --git a/apps/openmw/mwgui/scrollwindow.cpp b/apps/openmw/mwgui/scrollwindow.cpp index 038d91ca91..d61693d39e 100644 --- a/apps/openmw/mwgui/scrollwindow.cpp +++ b/apps/openmw/mwgui/scrollwindow.cpp @@ -1,6 +1,9 @@ #include "scrollwindow.hpp" +#include + #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" diff --git a/apps/openmw/mwgui/scrollwindow.hpp b/apps/openmw/mwgui/scrollwindow.hpp index 08ce6a9054..e1f86529a3 100644 --- a/apps/openmw/mwgui/scrollwindow.hpp +++ b/apps/openmw/mwgui/scrollwindow.hpp @@ -3,10 +3,13 @@ #include "windowbase.hpp" -#include - #include "../mwworld/ptr.hpp" +namespace Gui +{ + class ImageButton; +} + namespace MWGui { class ScrollWindow : public WindowBase diff --git a/apps/openmw/mwgui/settingswindow.cpp b/apps/openmw/mwgui/settingswindow.cpp index 8ef0a331a4..a4f87e598b 100644 --- a/apps/openmw/mwgui/settingswindow.cpp +++ b/apps/openmw/mwgui/settingswindow.cpp @@ -1,15 +1,22 @@ #include "settingswindow.hpp" #include -#include -#include +#include +#include +#include +#include +#include +#include + #include #include #include +#include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -48,8 +55,8 @@ namespace assert (split.size() >= 2); boost::trim(split[0]); boost::trim(split[1]); - x = boost::lexical_cast (split[0]); - y = boost::lexical_cast (split[1]); + x = MyGUI::utility::parseInt (split[0]); + y = MyGUI::utility::parseInt (split[1]); } bool sortResolutions (std::pair left, std::pair right) @@ -67,7 +74,7 @@ namespace // special case: 8 : 5 is usually referred to as 16:10 if (xaspect == 8 && yaspect == 5) return "16 : 10"; - return boost::lexical_cast(xaspect) + " : " + boost::lexical_cast(yaspect); + return MyGUI::utility::toString(xaspect) + " : " + MyGUI::utility::toString(yaspect); } std::string hlslGlsl () @@ -105,9 +112,9 @@ namespace min = 0.f; max = 1.f; if (!widget->getUserString(settingMin).empty()) - min = boost::lexical_cast(widget->getUserString(settingMin)); + min = MyGUI::utility::parseFloat(widget->getUserString(settingMin)); if (!widget->getUserString(settingMax).empty()) - max = boost::lexical_cast(widget->getUserString(settingMax)); + max = MyGUI::utility::parseFloat(widget->getUserString(settingMax)); } } @@ -166,6 +173,7 @@ namespace MWGui getWidget(mResolutionList, "ResolutionList"); getWidget(mFullscreenButton, "FullscreenButton"); getWidget(mVSyncButton, "VSyncButton"); + getWidget(mWindowBorderButton, "WindowBorderButton"); getWidget(mFPSButton, "FPSButton"); getWidget(mFOVSlider, "FOVSlider"); getWidget(mAnisotropySlider, "AnisotropySlider"); @@ -181,6 +189,20 @@ namespace MWGui getWidget(mRefractionButton, "RefractionButton"); getWidget(mDifficultySlider, "DifficultySlider"); +#ifndef WIN32 + // hide gamma controls since it currently does not work under Linux + MyGUI::ScrollBar *gammaSlider; + getWidget(gammaSlider, "GammaSlider"); + gammaSlider->setVisible(false); + MyGUI::TextBox *textBox; + getWidget(textBox, "GammaText"); + textBox->setVisible(false); + getWidget(textBox, "GammaTextDark"); + textBox->setVisible(false); + getWidget(textBox, "GammaTextLight"); + textBox->setVisible(false); +#endif + mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SettingsWindow::onWindowResize); mOkButton->eventMouseButtonClick += MyGUI::newDelegate(this, &SettingsWindow::onOkButtonClicked); @@ -209,7 +231,7 @@ namespace MWGui for (std::vector < std::pair >::const_iterator it=resolutions.begin(); it!=resolutions.end(); ++it) { - std::string str = boost::lexical_cast(it->first) + " x " + boost::lexical_cast(it->second) + std::string str = MyGUI::utility::toString(it->first) + " x " + MyGUI::utility::toString(it->second) + " (" + getAspect(it->first,it->second) + ")"; if (mResolutionList->findItemIndexWith(str) == MyGUI::ITEM_NONE) @@ -218,7 +240,7 @@ namespace MWGui std::string tf = Settings::Manager::getString("texture filtering", "General"); mTextureFilteringButton->setCaption(textureFilteringToStr(tf)); - mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(Settings::Manager::getInt("anisotropy", "General")) + ")"); + mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(Settings::Manager::getInt("anisotropy", "General")) + ")"); mShadowsTextureSize->setCaption (Settings::Manager::getString ("texture size", "Shadows")); @@ -234,11 +256,13 @@ namespace MWGui MyGUI::TextBox* fovText; getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + boost::lexical_cast(int(Settings::Manager::getInt("field of view", "General"))) + ")"); + fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(Settings::Manager::getInt("field of view", "General"))) + ")"); MyGUI::TextBox* diffText; getWidget(diffText, "DifficultyText"); - diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast(int(Settings::Manager::getInt("difficulty", "Game"))) + ")"); + diffText->setCaptionWithReplacing("#{sDifficulty} (" + MyGUI::utility::toString(int(Settings::Manager::getInt("difficulty", "Game"))) + ")"); + + mWindowBorderButton->setEnabled(!Settings::Manager::getBool("fullscreen", "Video")); } void SettingsWindow::onOkButtonClicked(MyGUI::Widget* _sender) @@ -354,6 +378,8 @@ namespace MWGui _sender->castType()->setCaption(off); return; } + + mWindowBorderButton->setEnabled(!newState); } if (getSettingType(_sender) == checkButtonType) @@ -406,13 +432,13 @@ namespace MWGui { MyGUI::TextBox* fovText; getWidget(fovText, "FovText"); - fovText->setCaption("Field of View (" + boost::lexical_cast(int(value)) + ")"); + fovText->setCaption("Field of View (" + MyGUI::utility::toString(int(value)) + ")"); } if (scroller == mDifficultySlider) { MyGUI::TextBox* diffText; getWidget(diffText, "DifficultyText"); - diffText->setCaptionWithReplacing("#{sDifficulty} (" + boost::lexical_cast(int(value)) + ")"); + diffText->setCaptionWithReplacing("#{sDifficulty} (" + MyGUI::utility::toString(int(value)) + ")"); } } else @@ -420,7 +446,7 @@ namespace MWGui Settings::Manager::setInt(getSettingName(scroller), getSettingCategory(scroller), pos); if (scroller == mAnisotropySlider) { - mAnisotropyLabel->setCaption("Anisotropy (" + boost::lexical_cast(pos) + ")"); + mAnisotropyLabel->setCaption("Anisotropy (" + MyGUI::utility::toString(pos) + ")"); } } apply(); diff --git a/apps/openmw/mwgui/settingswindow.hpp b/apps/openmw/mwgui/settingswindow.hpp index 2619943f98..8dcc8dd077 100644 --- a/apps/openmw/mwgui/settingswindow.hpp +++ b/apps/openmw/mwgui/settingswindow.hpp @@ -28,6 +28,7 @@ namespace MWGui MyGUI::ListBox* mResolutionList; MyGUI::Button* mFullscreenButton; MyGUI::Button* mVSyncButton; + MyGUI::Button* mWindowBorderButton; MyGUI::Button* mFPSButton; MyGUI::ScrollBar* mFOVSlider; MyGUI::ScrollBar* mDifficultySlider; diff --git a/apps/openmw/mwgui/sortfilteritemmodel.cpp b/apps/openmw/mwgui/sortfilteritemmodel.cpp index 93e5432ca9..6c164df888 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.cpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.cpp @@ -1,5 +1,7 @@ #include "sortfilteritemmodel.hpp" +#include + #include #include #include @@ -41,20 +43,26 @@ namespace return std::find(mapping.begin(), mapping.end(), type1) < std::find(mapping.begin(), mapping.end(), type2); } - bool compare (const MWGui::ItemStack& left, const MWGui::ItemStack& right) + struct Compare { - if (left.mType != right.mType) - return left.mType < right.mType; - - if (left.mBase.getTypeName() == right.mBase.getTypeName()) + bool mSortByType; + Compare() : mSortByType(true) {} + bool operator() (const MWGui::ItemStack& left, const MWGui::ItemStack& right) { - int cmp = left.mBase.getClass().getName(left.mBase).compare( - right.mBase.getClass().getName(right.mBase)); - return cmp < 0; + if (mSortByType && left.mType != right.mType) + return left.mType < right.mType; + + if (left.mBase.getTypeName() == right.mBase.getTypeName()) + { + std::string leftName = Misc::StringUtils::lowerCase(left.mBase.getClass().getName(left.mBase)); + std::string rightName = Misc::StringUtils::lowerCase(right.mBase.getClass().getName(right.mBase)); + + return leftName.compare(rightName) < 0; + } + else + return compareType(left.mBase.getTypeName(), right.mBase.getTypeName()); } - else - return compareType(left.mBase.getTypeName(), right.mBase.getTypeName()); - } + }; } namespace MWGui @@ -63,6 +71,7 @@ namespace MWGui SortFilterItemModel::SortFilterItemModel(ItemModel *sourceModel) : mCategory(Category_All) , mShowEquipped(true) + , mSortByType(true) , mFilter(0) { mSourceModel = sourceModel; @@ -183,7 +192,9 @@ namespace MWGui mItems.push_back(item); } - std::sort(mItems.begin(), mItems.end(), compare); + Compare cmp; + cmp.mSortByType = mSortByType; + std::sort(mItems.begin(), mItems.end(), cmp); } } diff --git a/apps/openmw/mwgui/sortfilteritemmodel.hpp b/apps/openmw/mwgui/sortfilteritemmodel.hpp index 4af35e7a8b..1b68bdd4f1 100644 --- a/apps/openmw/mwgui/sortfilteritemmodel.hpp +++ b/apps/openmw/mwgui/sortfilteritemmodel.hpp @@ -26,6 +26,9 @@ namespace MWGui void setFilter (int filter); void setShowEquipped (bool show) { mShowEquipped = show; } + /// Use ItemStack::Type for sorting? + void setSortByType(bool sort) { mSortByType = sort; } + static const int Category_Weapon = (1<<1); static const int Category_Apparel = (1<<2); static const int Category_Misc = (1<<3); @@ -47,6 +50,7 @@ namespace MWGui int mCategory; int mFilter; bool mShowEquipped; + bool mSortByType; }; } diff --git a/apps/openmw/mwgui/soulgemdialog.cpp b/apps/openmw/mwgui/soulgemdialog.cpp index 6d70c85d9d..0232eb7b32 100644 --- a/apps/openmw/mwgui/soulgemdialog.cpp +++ b/apps/openmw/mwgui/soulgemdialog.cpp @@ -1,6 +1,7 @@ #include "soulgemdialog.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/windowmanager.hpp" #include "messagebox.hpp" diff --git a/apps/openmw/mwgui/spellbuyingwindow.cpp b/apps/openmw/mwgui/spellbuyingwindow.cpp index 38b1bce7b5..54094b6062 100644 --- a/apps/openmw/mwgui/spellbuyingwindow.cpp +++ b/apps/openmw/mwgui/spellbuyingwindow.cpp @@ -1,6 +1,8 @@ #include "spellbuyingwindow.hpp" -#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -65,7 +67,7 @@ namespace MWGui mCurrentY += sLineHeight; toAdd->setUserData(price); - toAdd->setCaptionWithReplacing(spell->mName+" - "+boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing(spell->mName+" - "+MyGUI::utility::toString(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &SpellBuyingWindow::onMouseWheel); toAdd->setUserString("ToolTipType", "Spell"); @@ -166,7 +168,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, diff --git a/apps/openmw/mwgui/spellcreationdialog.cpp b/apps/openmw/mwgui/spellcreationdialog.cpp index 00cab6c455..f4c9d021a5 100644 --- a/apps/openmw/mwgui/spellcreationdialog.cpp +++ b/apps/openmw/mwgui/spellcreationdialog.cpp @@ -1,8 +1,11 @@ #include "spellcreationdialog.hpp" -#include +#include +#include #include +#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/soundmanager.hpp" @@ -12,6 +15,7 @@ #include "../mwworld/containerstore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" @@ -19,6 +23,7 @@ #include "tooltips.hpp" #include "class.hpp" +#include "widgets.hpp" namespace { @@ -31,6 +36,18 @@ namespace return gmst.find(ESM::MagicEffect::effectIdToString (id1))->getString() < gmst.find(ESM::MagicEffect::effectIdToString (id2))->getString(); } + + void init(ESM::ENAMstruct& effect) + { + effect.mArea = 0; + effect.mDuration = 0; + effect.mEffectID = -1; + effect.mMagnMax = 0; + effect.mMagnMin = 0; + effect.mRange = 0; + effect.mSkill = -1; + effect.mAttribute = -1; + } } namespace MWGui @@ -40,7 +57,11 @@ namespace MWGui : WindowModal("openmw_edit_effect.layout") , mEditing(false) , mMagicEffect(NULL) + , mConstantEffect(false) { + init(mEffect); + init(mOldEffect); + getWidget(mCancelButton, "CancelButton"); getWidget(mOkButton, "OkButton"); getWidget(mDeleteButton, "DeleteButton"); @@ -69,7 +90,11 @@ namespace MWGui mMagnitudeMaxSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onMagnitudeMaxChanged); mDurationSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onDurationChanged); mAreaSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &EditEffectDialog::onAreaChanged); - constantEffect=false; + } + + void EditEffectDialog::setConstantEffect(bool constant) + { + mConstantEffect = constant; } void EditEffectDialog::open() @@ -89,15 +114,22 @@ namespace MWGui void EditEffectDialog::newEffect (const ESM::MagicEffect *effect) { + bool allowSelf = effect->mData.mFlags & ESM::MagicEffect::CastSelf; + bool allowTouch = (effect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (effect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + + if (!allowSelf && !allowTouch && !allowTarget) + return; // TODO: Show an error message popup? + setMagicEffect(effect); mEditing = false; mDeleteButton->setVisible (false); mEffect.mRange = ESM::RT_Self; - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf)) + if (!allowSelf) mEffect.mRange = ESM::RT_Touch; - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch)) + if (!allowTouch) mEffect.mRange = ESM::RT_Target; mEffect.mMagnMin = 1; mEffect.mMagnMax = 1; @@ -118,6 +150,8 @@ namespace MWGui mMagnitudeMinValue->setCaption("1"); mMagnitudeMaxValue->setCaption("- 1"); mAreaValue->setCaption("0"); + + setVisible(true); } void EditEffectDialog::editEffect (ESM::ENAMstruct effect) @@ -172,7 +206,7 @@ namespace MWGui mMagnitudeBox->setVisible (true); curY += mMagnitudeBox->getSize().height; } - if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)&&constantEffect==false) + if (!(mMagicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)&&mConstantEffect==false) { mDurationBox->setPosition(mDurationBox->getPosition().left, curY); mDurationBox->setVisible (true); @@ -190,6 +224,24 @@ namespace MWGui { mEffect.mRange = (mEffect.mRange+1)%3; + // cycle through range types until we find something that's allowed + // does not handle the case where nothing is allowed (this should be prevented before opening the Add Effect dialog) + bool allowSelf = mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf; + bool allowTouch = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch) && !mConstantEffect; + bool allowTarget = (mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget) && !mConstantEffect; + if (mEffect.mRange == ESM::RT_Self && !allowSelf) + mEffect.mRange = (mEffect.mRange+1)%3; + if (mEffect.mRange == ESM::RT_Touch && !allowTouch) + mEffect.mRange = (mEffect.mRange+1)%3; + if (mEffect.mRange == ESM::RT_Target && !allowTarget) + mEffect.mRange = (mEffect.mRange+1)%3; + + if(mEffect.mRange == ESM::RT_Self) + { + mAreaSlider->setScrollPosition(0); + onAreaChanged(mAreaSlider,0); + } + if (mEffect.mRange == ESM::RT_Self) mRangeButton->setCaptionWithReplacing ("#{sRangeSelf}"); else if (mEffect.mRange == ESM::RT_Target) @@ -197,19 +249,6 @@ namespace MWGui else if (mEffect.mRange == ESM::RT_Touch) mRangeButton->setCaptionWithReplacing ("#{sRangeTouch}"); - // cycle through range types until we find something that's allowed - if (mEffect.mRange == ESM::RT_Target && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTarget)) - onRangeButtonClicked(sender); - if (mEffect.mRange == ESM::RT_Self && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastSelf)) - onRangeButtonClicked(sender); - if (mEffect.mRange == ESM::RT_Touch && !(mMagicEffect->mData.mFlags & ESM::MagicEffect::CastTouch)) - onRangeButtonClicked(sender); - - if(mEffect.mRange == ESM::RT_Self) - { - mAreaSlider->setScrollPosition(0); - onAreaChanged(mAreaSlider,0); - } updateBoxes(); eventEffectModified(mEffect); } @@ -245,7 +284,7 @@ namespace MWGui void EditEffectDialog::onMagnitudeMinChanged (MyGUI::ScrollBar* sender, size_t pos) { - mMagnitudeMinValue->setCaption(boost::lexical_cast(pos+1)); + mMagnitudeMinValue->setCaption(MyGUI::utility::toString(pos+1)); mEffect.mMagnMin = pos+1; // trigger the check again (see below) @@ -265,21 +304,21 @@ namespace MWGui mEffect.mMagnMax = pos+1; - mMagnitudeMaxValue->setCaption("- " + boost::lexical_cast(pos+1)); + mMagnitudeMaxValue->setCaption("- " + MyGUI::utility::toString(pos+1)); eventEffectModified(mEffect); } void EditEffectDialog::onDurationChanged (MyGUI::ScrollBar* sender, size_t pos) { - mDurationValue->setCaption(boost::lexical_cast(pos+1)); + mDurationValue->setCaption(MyGUI::utility::toString(pos+1)); mEffect.mDuration = pos+1; eventEffectModified(mEffect); } void EditEffectDialog::onAreaChanged (MyGUI::ScrollBar* sender, size_t pos) { - mAreaValue->setCaption(boost::lexical_cast(pos)); + mAreaValue->setCaption(MyGUI::utility::toString(pos)); mEffect.mArea = pos; eventEffectModified(mEffect); } @@ -341,7 +380,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - if (boost::lexical_cast(mPriceLabel->getCaption()) > playerGold) + if (MyGUI::utility::parseInt(mPriceLabel->getCaption()) > playerGold) { MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage18}"); return; @@ -349,7 +388,7 @@ namespace MWGui mSpell.mName = mNameEdit->getCaption(); - int price = boost::lexical_cast(mPriceLabel->getCaption()); + int price = MyGUI::utility::parseInt(mPriceLabel->getCaption()); player.getClass().getContainerStore(player).remove(MWWorld::ContainerStore::sGoldId, price, player); @@ -357,7 +396,7 @@ namespace MWGui MWMechanics::CreatureStats& npcStats = mPtr.getClass().getCreatureStats(mPtr); npcStats.setGoldPool(npcStats.getGoldPool() + price); - MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); + MWBase::Environment::get().getSoundManager()->playSound ("Mysticism Hit", 1.0, 1.0); const ESM::Spell* spell = MWBase::Environment::get().getWorld()->createRecord(mSpell); @@ -365,8 +404,6 @@ namespace MWGui MWMechanics::Spells& spells = stats.getSpells(); spells.add (spell->mId); - MWBase::Environment::get().getSoundManager()->playSound ("Item Gold Up", 1.0, 1.0); - MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_SpellCreation); } @@ -429,17 +466,17 @@ namespace MWGui mSpell.mData.mType = ESM::Spell::ST_Spell; mSpell.mData.mFlags = 0; - mMagickaCost->setCaption(boost::lexical_cast(int(y))); + mMagickaCost->setCaption(MyGUI::utility::toString(int(y))); float fSpellMakingValueMult = store.get().find("fSpellMakingValueMult")->getFloat(); int price = MWBase::Environment::get().getMechanicsManager()->getBarterOffer(mPtr,int(y) * fSpellMakingValueMult,true); - mPriceLabel->setCaption(boost::lexical_cast(int(price))); + mPriceLabel->setCaption(MyGUI::utility::toString(int(price))); float chance = MWMechanics::getSpellSuccessChance(&mSpell, MWBase::Environment::get().getWorld()->getPlayerPtr()); - mSuccessChance->setCaption(boost::lexical_cast(int(chance))); + mSuccessChance->setCaption(MyGUI::utility::toString(int(chance))); } // ------------------------------------------------------------------------------------------------ @@ -454,6 +491,7 @@ namespace MWGui , mSelectedEffect(0) , mSelectedKnownEffectId(0) , mType(type) + , mConstantEffect(false) { mAddEffectDialog.eventEffectAdded += MyGUI::newDelegate(this, &EffectEditorBase::onEffectAdded); mAddEffectDialog.eventEffectModified += MyGUI::newDelegate(this, &EffectEditorBase::onEffectModified); @@ -542,7 +580,6 @@ namespace MWGui mAddEffectDialog.newEffect(effect); mAddEffectDialog.setAttribute (mSelectAttributeDialog->getAttributeId()); - mAddEffectDialog.setVisible(true); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectAttributeDialog); mSelectAttributeDialog = 0; } @@ -554,7 +591,6 @@ namespace MWGui mAddEffectDialog.newEffect(effect); mAddEffectDialog.setSkill (mSelectSkillDialog->getSkillId()); - mAddEffectDialog.setVisible(true); MWBase::Environment::get().getWindowManager ()->removeDialog (mSelectSkillDialog); mSelectSkillDialog = 0; } @@ -580,14 +616,6 @@ namespace MWGui int buttonId = *sender->getUserData(); mSelectedKnownEffectId = mButtonMapping[buttonId]; - for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) - { - if (it->mEffectID == mSelectedKnownEffectId) - { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); - return; - } - } const ESM::MagicEffect* effect = MWBase::Environment::get().getWorld()->getStore().get().find(mSelectedKnownEffectId); @@ -610,8 +638,16 @@ namespace MWGui } else { + for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) + { + if (it->mEffectID == mSelectedKnownEffectId) + { + MWBase::Environment::get().getWindowManager()->messageBox ("#{sOnetypeEffectMessage}"); + return; + } + } + mAddEffectDialog.newEffect(effect); - mAddEffectDialog.setVisible(true); } } @@ -647,6 +683,7 @@ namespace MWGui params.mMagnMax = it->mMagnMax; params.mRange = it->mRange; params.mArea = it->mArea; + params.mIsConstant = mConstantEffect; MyGUI::Button* button = mUsedEffectsView->createWidget("", MyGUI::IntCoord(0, size.height, 0, 24), MyGUI::Align::Default); button->setUserData(i); @@ -691,4 +728,10 @@ namespace MWGui mAddEffectDialog.editEffect (mEffects[id]); mAddEffectDialog.setVisible (true); } + + void EffectEditorBase::setConstantEffect(bool constant) + { + mAddEffectDialog.setConstantEffect(constant); + mConstantEffect = constant; + } } diff --git a/apps/openmw/mwgui/spellcreationdialog.hpp b/apps/openmw/mwgui/spellcreationdialog.hpp index ecccf3baf9..6cdf74d103 100644 --- a/apps/openmw/mwgui/spellcreationdialog.hpp +++ b/apps/openmw/mwgui/spellcreationdialog.hpp @@ -1,11 +1,16 @@ #ifndef MWGUI_SPELLCREATION_H #define MWGUI_SPELLCREATION_H -#include +#include +#include #include "windowbase.hpp" #include "referenceinterface.hpp" -#include "widgets.hpp" + +namespace Gui +{ + class MWList; +} namespace MWGui { @@ -21,12 +26,13 @@ namespace MWGui virtual void open(); virtual void exit(); + void setConstantEffect(bool constant); + void setSkill(int skill); void setAttribute(int attribute); void newEffect (const ESM::MagicEffect* effect); void editEffect (ESM::ENAMstruct effect); - bool constantEffect; typedef MyGUI::delegates::CMultiDelegate1 EventHandle_Effect; EventHandle_Effect eventEffectAdded; @@ -80,6 +86,8 @@ namespace MWGui ESM::ENAMstruct mOldEffect; const ESM::MagicEffect* mMagicEffect; + + bool mConstantEffect; }; @@ -95,6 +103,8 @@ namespace MWGui EffectEditorBase(Type type); virtual ~EffectEditorBase(); + void setConstantEffect(bool constant); + protected: std::map mButtonMapping; // maps button ID to effect ID @@ -108,6 +118,8 @@ namespace MWGui int mSelectedEffect; short mSelectedKnownEffectId; + bool mConstantEffect; + std::vector mEffects; void onEffectAdded(ESM::ENAMstruct effect); diff --git a/apps/openmw/mwgui/spellicons.cpp b/apps/openmw/mwgui/spellicons.cpp index dbd91ab753..20c6f3ba83 100644 --- a/apps/openmw/mwgui/spellicons.cpp +++ b/apps/openmw/mwgui/spellicons.cpp @@ -1,10 +1,11 @@ #include "spellicons.hpp" -#include - #include #include +#include + +#include #include #include "../mwbase/world.hpp" @@ -12,6 +13,7 @@ #include "../mwbase/windowmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -23,8 +25,8 @@ namespace MWGui { void EffectSourceVisitor::visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, - float magnitude, float remainingTime) + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime, float totalTime) { MagicEffectInfo newEffectSource; newEffectSource.mKey = key; @@ -32,6 +34,7 @@ namespace MWGui newEffectSource.mPermanent = mIsPermanent; newEffectSource.mRemainingTime = remainingTime; newEffectSource.mSource = sourceName; + newEffectSource.mTotalTime = totalTime; mEffectSources[key.mId].push_back(newEffectSource); } @@ -67,10 +70,11 @@ namespace MWGui MWBase::Environment::get().getWorld ()->getStore ().get().find(it->first); float remainingDuration = 0; + float totalDuration = 0; std::string sourcesDescription; - const float fadeTime = 5.f; + static const float fadeTime = MWBase::Environment::get().getWorld()->getStore().get().find("fMagicStartIconBlink")->getFloat(); for (std::vector::const_iterator effectIt = it->second.begin(); effectIt != it->second.end(); ++effectIt) @@ -80,9 +84,15 @@ namespace MWGui // if at least one of the effect sources is permanent, the effect will never wear off if (effectIt->mPermanent) + { remainingDuration = fadeTime; + totalDuration = fadeTime; + } else + { remainingDuration = std::max(remainingDuration, effectIt->mRemainingTime); + totalDuration = std::max(totalDuration, effectIt->mTotalTime); + } sourcesDescription += effectIt->mSource; @@ -105,7 +115,7 @@ namespace MWGui } else if ( displayType != ESM::MagicEffect::MDT_None ) { - sourcesDescription += ": " + boost::lexical_cast(effectIt->mMagnitude); + sourcesDescription += ": " + MyGUI::utility::toString(effectIt->mMagnitude); if ( displayType == ESM::MagicEffect::MDT_Percentage ) sourcesDescription += MWBase::Environment::get().getWindowManager()->getGameSettingString("spercent", ""); @@ -158,8 +168,9 @@ namespace MWGui ToolTipInfo* tooltipInfo = image->getUserData(); tooltipInfo->text = sourcesDescription; - // Fade out during the last 5 seconds - image->setAlpha(std::min(remainingDuration/fadeTime, 1.f)); + // Fade out + if (totalDuration >= fadeTime && fadeTime > 0.f) + image->setAlpha(std::min(remainingDuration/fadeTime, 1.f)); } else if (mWidgetMap.find(it->first) != mWidgetMap.end()) { diff --git a/apps/openmw/mwgui/spellicons.hpp b/apps/openmw/mwgui/spellicons.hpp index 7df9ad8b91..5099fc4d63 100644 --- a/apps/openmw/mwgui/spellicons.hpp +++ b/apps/openmw/mwgui/spellicons.hpp @@ -26,12 +26,14 @@ namespace MWGui MagicEffectInfo() : mPermanent(false) , mMagnitude(0) - , mRemainingTime(0) + , mRemainingTime(0.f) + , mTotalTime(0.f) {} std::string mSource; // display name for effect source (e.g. potion name) MWMechanics::EffectKey mKey; int mMagnitude; float mRemainingTime; + float mTotalTime; bool mPermanent; // the effect is permanent }; @@ -45,8 +47,8 @@ namespace MWGui virtual ~EffectSourceVisitor() {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, - float magnitude, float remainingTime = -1); + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1); }; class SpellIcons diff --git a/apps/openmw/mwgui/spellmodel.cpp b/apps/openmw/mwgui/spellmodel.cpp new file mode 100644 index 0000000000..4713720cd0 --- /dev/null +++ b/apps/openmw/mwgui/spellmodel.cpp @@ -0,0 +1,135 @@ +#include "spellmodel.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwbase/windowmanager.hpp" + +#include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/spellcasting.hpp" + +#include "../mwworld/esmstore.hpp" +#include "../mwworld/inventorystore.hpp" +#include "../mwworld/class.hpp" + +namespace +{ + + bool sortSpells(const MWGui::Spell& left, const MWGui::Spell& right) + { + if (left.mType != right.mType) + return left.mType < right.mType; + + std::string leftName = Misc::StringUtils::lowerCase(left.mName); + std::string rightName = Misc::StringUtils::lowerCase(right.mName); + + return leftName.compare(rightName) < 0; + } + +} + +namespace MWGui +{ + + SpellModel::SpellModel(const MWWorld::Ptr &actor) + : mActor(actor) + { + + } + + void SpellModel::update() + { + mSpells.clear(); + + MWMechanics::CreatureStats& stats = mActor.getClass().getCreatureStats(mActor); + const MWMechanics::Spells& spells = stats.getSpells(); + + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) + { + const ESM::Spell* spell = esmStore.get().find(it->first); + if (spell->mData.mType != ESM::Spell::ST_Power && spell->mData.mType != ESM::Spell::ST_Spell) + continue; + + Spell newSpell; + newSpell.mName = spell->mName; + if (spell->mData.mType == ESM::Spell::ST_Spell) + { + newSpell.mType = Spell::Type_Spell; + std::string cost = boost::lexical_cast(spell->mData.mCost); + std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(spell, mActor))); + newSpell.mCostColumn = cost + "/" + chance; + } + else + newSpell.mType = Spell::Type_Power; + newSpell.mId = it->first; + + newSpell.mSelected = (MWBase::Environment::get().getWindowManager()->getSelectedSpell() == it->first); + newSpell.mActive = true; + mSpells.push_back(newSpell); + } + + MWWorld::InventoryStore& invStore = mActor.getClass().getInventoryStore(mActor); + for (MWWorld::ContainerStoreIterator it = invStore.begin(); it != invStore.end(); ++it) + { + MWWorld::Ptr item = *it; + const std::string enchantId = item.getClass().getEnchantment(item); + if (enchantId.empty()) + continue; + const ESM::Enchantment* enchant = + esmStore.get().find(item.getClass().getEnchantment(item)); + if (enchant->mData.mType != ESM::Enchantment::WhenUsed && enchant->mData.mType != ESM::Enchantment::CastOnce) + continue; + + Spell newSpell; + newSpell.mItem = item; + newSpell.mId = item.getClass().getId(item); + newSpell.mName = item.getClass().getName(item); + newSpell.mType = Spell::Type_EnchantedItem; + newSpell.mSelected = invStore.getSelectedEnchantItem() == it; + + // FIXME: move to mwmechanics + if (enchant->mData.mType == ESM::Enchantment::CastOnce) + { + newSpell.mCostColumn = "100/100"; + newSpell.mActive = false; + } + else + { + if (!item.getClass().getEquipmentSlots(item).first.empty() + && item.getClass().canBeEquipped(item, mActor).first == 0) + continue; + + int castCost = MWMechanics::getEffectiveEnchantmentCastCost(enchant->mData.mCost, mActor); + + std::string cost = boost::lexical_cast(castCost); + int currentCharge = int(item.getCellRef().getEnchantmentCharge()); + if (currentCharge == -1) + currentCharge = enchant->mData.mCharge; + std::string charge = boost::lexical_cast(currentCharge); + newSpell.mCostColumn = cost + "/" + charge; + + newSpell.mActive = invStore.isEquipped(item); + } + mSpells.push_back(newSpell); + } + + std::stable_sort(mSpells.begin(), mSpells.end(), sortSpells); + } + + size_t SpellModel::getItemCount() const + { + return mSpells.size(); + } + + Spell SpellModel::getItem(ModelIndex index) const + { + if (index < 0 || index >= int(mSpells.size())) + throw std::runtime_error("invalid spell index supplied"); + return mSpells[index]; + } + +} diff --git a/apps/openmw/mwgui/spellmodel.hpp b/apps/openmw/mwgui/spellmodel.hpp new file mode 100644 index 0000000000..7859c8a7b6 --- /dev/null +++ b/apps/openmw/mwgui/spellmodel.hpp @@ -0,0 +1,57 @@ +#ifndef OPENMW_GUI_SPELLMODEL_H +#define OPENMW_GUI_SPELLMODEL_H + +#include "../mwworld/ptr.hpp" + +namespace MWGui +{ + + struct Spell + { + enum Type + { + Type_Power, + Type_Spell, + Type_EnchantedItem + }; + + Type mType; + std::string mName; + std::string mCostColumn; // Cost/chance or Cost/charge + std::string mId; // Item ID or spell ID + MWWorld::Ptr mItem; // Only for Type_EnchantedItem + bool mSelected; // Is this the currently selected spell/item (only one can be selected at a time) + bool mActive; // (Items only) is the item equipped? + + Spell() + : mSelected(false) + , mActive(false) + , mType(Type_Spell) + { + } + }; + + ///@brief Model that lists all usable powers, spells and enchanted items for an actor. + class SpellModel + { + public: + SpellModel(const MWWorld::Ptr& actor); + + typedef int ModelIndex; + + void update(); + + Spell getItem (ModelIndex index) const; + ///< throws for invalid index + + size_t getItemCount() const; + + private: + MWWorld::Ptr mActor; + + std::vector mSpells; + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellview.cpp b/apps/openmw/mwgui/spellview.cpp new file mode 100644 index 0000000000..1c17a11f66 --- /dev/null +++ b/apps/openmw/mwgui/spellview.cpp @@ -0,0 +1,245 @@ +#include "spellview.hpp" + +#include +#include +#include +#include + +#include + +namespace MWGui +{ + + SpellView::SpellView() + : mShowCostColumn(true) + , mHighlightSelected(true) + , mScrollView(NULL) + { + } + + void SpellView::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mScrollView, "ScrollView"); + if (mScrollView == NULL) + throw std::runtime_error("Item view needs a scroll view"); + + mScrollView->setCanvasAlign(MyGUI::Align::Left | MyGUI::Align::Top); + } + + void SpellView::registerComponents() + { + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + } + + void SpellView::setModel(SpellModel *model) + { + mModel.reset(model); + update(); + } + + SpellModel* SpellView::getModel() + { + return mModel.get(); + } + + void SpellView::setShowCostColumn(bool show) + { + if (show != mShowCostColumn) + { + mShowCostColumn = show; + update(); + } + } + + void SpellView::setHighlightSelected(bool highlight) + { + if (highlight != mHighlightSelected) + { + mHighlightSelected = highlight; + update(); + } + } + + void SpellView::update() + { + if (!mModel.get()) + return; + + mModel->update(); + + int curType = -1; + + const int spellHeight = 18; + + mLines.clear(); + + while (mScrollView->getChildCount()) + MyGUI::Gui::getInstance().destroyWidget(mScrollView->getChildAt(0)); + + for (SpellModel::ModelIndex i = 0; igetItemCount()); ++i) + { + const Spell& spell = mModel->getItem(i); + if (curType != spell.mType) + { + if (spell.mType == Spell::Type_Power) + addGroup("#{sPowers}", ""); + else if (spell.mType == Spell::Type_Spell) + addGroup("#{sSpells}", "#{sCostChance}"); + else + addGroup("#{sMagicItem}", "#{sCostCharge}"); + curType = spell.mType; + } + + const std::string skin = spell.mActive ? "SandTextButton" : "SpellTextUnequipped"; + + Gui::SharedStateButton* t = mScrollView->createWidget(skin, + MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + t->setCaption(spell.mName); + t->setTextAlign(MyGUI::Align::Left); + adjustSpellWidget(spell, i, t); + + if (!spell.mCostColumn.empty() && mShowCostColumn) + { + Gui::SharedStateButton* costChance = mScrollView->createWidget(skin, + MyGUI::IntCoord(0, 0, 0, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); + costChance->setCaption(spell.mCostColumn); + costChance->setTextAlign(MyGUI::Align::Right); + adjustSpellWidget(spell, i, costChance); + + Gui::ButtonGroup group; + group.push_back(t); + group.push_back(costChance); + Gui::SharedStateButton::createButtonGroup(group); + + mLines.push_back(std::make_pair(t, costChance)); + } + else + mLines.push_back(std::make_pair(t, (MyGUI::Widget*)NULL)); + + t->setStateSelected(spell.mSelected); + } + + layoutWidgets(); + } + + void SpellView::layoutWidgets() + { + int height = 0; + for (std::vector< std::pair >::iterator it = mLines.begin(); + it != mLines.end(); ++it) + { + height += (it->first)->getHeight(); + } + + bool scrollVisible = height > mScrollView->getHeight(); + int width = mScrollView->getWidth() - (scrollVisible ? 18 : 0); + + height = 0; + for (std::vector< std::pair >::iterator it = mLines.begin(); + it != mLines.end(); ++it) + { + int lineHeight = (it->first)->getHeight(); + (it->first)->setCoord(4, height, width-8, lineHeight); + if (it->second) + { + (it->second)->setCoord(4, height, width-8, lineHeight); + MyGUI::TextBox* second = (it->second)->castType(false); + if (second) + (it->first)->setSize(width-8-second->getTextSize().width, lineHeight); + } + + height += lineHeight; + } + + // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden + mScrollView->setVisibleVScroll(false); + mScrollView->setCanvasSize(mScrollView->getWidth(), std::max(mScrollView->getHeight(), height)); + mScrollView->setVisibleVScroll(true); + } + + void SpellView::addGroup(const std::string &label, const std::string& label2) + { + if (mScrollView->getChildCount() > 0) + { + MyGUI::ImageBox* separator = mScrollView->createWidget("MW_HLine", + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 18), + MyGUI::Align::Left | MyGUI::Align::Top); + separator->setNeedMouseFocus(false); + mLines.push_back(std::make_pair(separator, (MyGUI::Widget*)NULL)); + } + + MyGUI::TextBox* groupWidget = mScrollView->createWidget("SandBrightText", + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), + MyGUI::Align::Left | MyGUI::Align::Top); + groupWidget->setCaptionWithReplacing(label); + groupWidget->setTextAlign(MyGUI::Align::Left); + groupWidget->setNeedMouseFocus(false); + + if (label2 != "") + { + MyGUI::TextBox* groupWidget2 = mScrollView->createWidget("SandBrightText", + MyGUI::IntCoord(0, 0, mScrollView->getWidth(), 24), + MyGUI::Align::Left | MyGUI::Align::Top); + groupWidget2->setCaptionWithReplacing(label2); + groupWidget2->setTextAlign(MyGUI::Align::Right); + groupWidget2->setNeedMouseFocus(false); + + mLines.push_back(std::make_pair(groupWidget, groupWidget2)); + } + else + mLines.push_back(std::make_pair(groupWidget, (MyGUI::Widget*)NULL)); + } + + + void SpellView::setSize(const MyGUI::IntSize &_value) + { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); + Base::setSize(_value); + if (changed) + layoutWidgets(); + } + + void SpellView::setCoord(const MyGUI::IntCoord &_value) + { + bool changed = (_value.width != getWidth() || _value.height != getHeight()); + Base::setCoord(_value); + if (changed) + layoutWidgets(); + } + + void SpellView::adjustSpellWidget(const Spell &spell, SpellModel::ModelIndex index, MyGUI::Widget *widget) + { + if (spell.mType == Spell::Type_EnchantedItem) + { + widget->setUserData(spell.mItem); + widget->setUserString("ToolTipType", "ItemPtr"); + } + else + { + widget->setUserString("ToolTipType", "Spell"); + widget->setUserString("Spell", spell.mId); + } + + widget->setUserString("SpellModelIndex", MyGUI::utility::toString(index)); + + widget->eventMouseWheel += MyGUI::newDelegate(this, &SpellView::onMouseWheel); + widget->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellView::onSpellSelected); + } + + void SpellView::onSpellSelected(MyGUI::Widget* _sender) + { + SpellModel::ModelIndex i = MyGUI::utility::parseInt(_sender->getUserString("SpellModelIndex")); + eventSpellClicked(i); + } + + void SpellView::onMouseWheel(MyGUI::Widget* _sender, int _rel) + { + if (mScrollView->getViewOffset().top + _rel*0.3 > 0) + mScrollView->setViewOffset(MyGUI::IntPoint(0, 0)); + else + mScrollView->setViewOffset(MyGUI::IntPoint(0, mScrollView->getViewOffset().top + _rel*0.3)); + } + +} diff --git a/apps/openmw/mwgui/spellview.hpp b/apps/openmw/mwgui/spellview.hpp new file mode 100644 index 0000000000..005d206f4d --- /dev/null +++ b/apps/openmw/mwgui/spellview.hpp @@ -0,0 +1,69 @@ +#ifndef OPENMW_GUI_SPELLVIEW_H +#define OPENMW_GUI_SPELLVIEW_H + +#include + +#include "spellmodel.hpp" + +namespace MyGUI +{ + class ScrollView; +} + +namespace MWGui +{ + + class SpellModel; + + ///@brief Displays a SpellModel in a list widget + class SpellView : public MyGUI::Widget + { + MYGUI_RTTI_DERIVED(SpellView) + public: + SpellView(); + + /// Register needed components with MyGUI's factory manager + static void registerComponents (); + + /// Should the cost/chance column be shown? + void setShowCostColumn(bool show); + + void setHighlightSelected(bool highlight); + + /// Takes ownership of \a model + void setModel (SpellModel* model); + + SpellModel* getModel(); + + void update(); + + typedef MyGUI::delegates::CMultiDelegate1 EventHandle_ModelIndex; + /// Fired when a spell was clicked + EventHandle_ModelIndex eventSpellClicked; + + virtual void initialiseOverride(); + + virtual void setSize(const MyGUI::IntSize& _value); + virtual void setCoord(const MyGUI::IntCoord& _value); + + private: + MyGUI::ScrollView* mScrollView; + + std::auto_ptr mModel; + + std::vector< std::pair > mLines; + + bool mShowCostColumn; + bool mHighlightSelected; + + void layoutWidgets(); + void addGroup(const std::string& label1, const std::string& label2); + void adjustSpellWidget(const Spell& spell, SpellModel::ModelIndex index, MyGUI::Widget* widget); + + void onSpellSelected(MyGUI::Widget* _sender); + void onMouseWheel(MyGUI::Widget* _sender, int _rel); + }; + +} + +#endif diff --git a/apps/openmw/mwgui/spellwindow.cpp b/apps/openmw/mwgui/spellwindow.cpp index 7904c249ab..69365c1081 100644 --- a/apps/openmw/mwgui/spellwindow.cpp +++ b/apps/openmw/mwgui/spellwindow.cpp @@ -1,9 +1,8 @@ #include "spellwindow.hpp" -#include #include -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -11,6 +10,7 @@ #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellcasting.hpp" #include "../mwmechanics/spells.hpp" @@ -19,44 +19,24 @@ #include "spellicons.hpp" #include "inventorywindow.hpp" #include "confirmationdialog.hpp" +#include "spellview.hpp" namespace MWGui { - bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right) - { - int cmp = left.getClass().getName(left).compare( - right.getClass().getName(right)); - return cmp < 0; - } - - bool sortSpells(const std::string& left, const std::string& right) - { - const MWWorld::Store &spells = - MWBase::Environment::get().getWorld()->getStore().get(); - - const ESM::Spell* a = spells.find(left); - const ESM::Spell* b = spells.find(right); - - int cmp = a->mName.compare(b->mName); - return cmp < 0; - } - SpellWindow::SpellWindow(DragAndDrop* drag) : WindowPinnableBase("openmw_spell_window.layout") , NoDrop(drag, mMainWidget) - , mHeight(0) - , mWidth(0) - , mWindowSize(mMainWidget->getSize()) + , mSpellView(NULL) { mSpellIcons = new SpellIcons(); getWidget(mSpellView, "SpellView"); getWidget(mEffectBox, "EffectsBox"); - setCoord(498, 300, 302, 300); + mSpellView->eventSpellClicked += MyGUI::newDelegate(this, &SpellWindow::onModelIndexSelected); - mMainWidget->castType()->eventWindowChangeCoord += MyGUI::newDelegate(this, &SpellWindow::onWindowResize); + setCoord(498, 300, 302, 300); } SpellWindow::~SpellWindow() @@ -84,261 +64,14 @@ namespace MWGui { mSpellIcons->updateWidgets(mEffectBox, false); - const int spellHeight = 18; - - mHeight = 0; - while (mSpellView->getChildCount()) - MyGUI::Gui::getInstance().destroyWidget(mSpellView->getChildAt(0)); - - // retrieve all player spells, divide them into Powers and Spells and sort them - std::vector spellList; - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWMechanics::CreatureStats& stats = player.getClass().getCreatureStats(player); - MWMechanics::Spells& spells = stats.getSpells(); - - for (MWMechanics::Spells::TIterator it = spells.begin(); it != spells.end(); ++it) - spellList.push_back (it->first); - - const MWWorld::ESMStore &esmStore = - MWBase::Environment::get().getWorld()->getStore(); - - std::vector powers; - std::vector::iterator it = spellList.begin(); - while (it != spellList.end()) - { - const ESM::Spell* spell = esmStore.get().find(*it); - - if (spell->mData.mType == ESM::Spell::ST_Power) - { - powers.push_back(*it); - it = spellList.erase(it); - } - else if (spell->mData.mType == ESM::Spell::ST_Ability - || spell->mData.mType == ESM::Spell::ST_Blight - || spell->mData.mType == ESM::Spell::ST_Curse - || spell->mData.mType == ESM::Spell::ST_Disease) - { - it = spellList.erase(it); - } - else - ++it; - } - std::sort(powers.begin(), powers.end(), sortSpells); - std::sort(spellList.begin(), spellList.end(), sortSpells); - - // retrieve player's enchanted items - std::vector items; - for (MWWorld::ContainerStoreIterator it(store.begin()); it != store.end(); ++it) - { - std::string enchantId = it->getClass().getEnchantment(*it); - if (enchantId != "") - { - // only add items with "Cast once" or "Cast on use" - const ESM::Enchantment* enchant = - esmStore.get().find(enchantId); - - int type = enchant->mData.mType; - if (type != ESM::Enchantment::CastOnce - && type != ESM::Enchantment::WhenUsed) - continue; - - items.push_back(*it); - } - } - std::sort(items.begin(), items.end(), sortItems); - - - int height = estimateHeight(items.size() + powers.size() + spellList.size()); - bool scrollVisible = height > mSpellView->getHeight(); - mWidth = mSpellView->getWidth() - (scrollVisible ? 18 : 0); - - // powers - addGroup("#{sPowers}", ""); - - for (std::vector::const_iterator it = powers.begin(); it != powers.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - MyGUI::Button* t = mSpellView->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - - if (*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()) - t->setStateSelected(true); - - mHeight += spellHeight; - } - - // other spells - addGroup("#{sSpells}", "#{sCostChance}"); - for (std::vector::const_iterator it = spellList.begin(); it != spellList.end(); ++it) - { - const ESM::Spell* spell = esmStore.get().find(*it); - Gui::SharedStateButton* t = mSpellView->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(spell->mName); - t->setTextAlign(MyGUI::Align::Left); - t->setUserString("ToolTipType", "Spell"); - t->setUserString("Spell", *it); - t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - - // cost / success chance - Gui::SharedStateButton* costChance = mSpellView->createWidget("SandTextButton", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - std::string cost = boost::lexical_cast(spell->mData.mCost); - std::string chance = boost::lexical_cast(int(MWMechanics::getSpellSuccessChance(*it, player))); - costChance->setCaption(cost + "/" + chance); - costChance->setTextAlign(MyGUI::Align::Right); - costChance->setUserString("ToolTipType", "Spell"); - costChance->setUserString("Spell", *it); - costChance->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - costChance->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onSpellSelected); - - t->setSize(mWidth-12-costChance->getTextSize().width, t->getHeight()); - - Gui::ButtonGroup group; - group.push_back(t); - group.push_back(costChance); - Gui::SharedStateButton::createButtonGroup(group); - - t->setStateSelected(*it == MWBase::Environment::get().getWindowManager()->getSelectedSpell()); - - mHeight += spellHeight; - } - - - // enchanted items - addGroup("#{sMagicItem}", "#{sCostCharge}"); - - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) - { - MWWorld::Ptr item = *it; - - const ESM::Enchantment* enchant = - esmStore.get().find(item.getClass().getEnchantment(item)); - - // check if the item is currently equipped (will display in a different color) - bool equipped = false; - for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) - { - if (store.getSlot(i) != store.end() && *store.getSlot(i) == item) - { - equipped = true; - break; - } - } - - Gui::SharedStateButton* t = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - t->setCaption(item.getClass().getName(item)); - t->setTextAlign(MyGUI::Align::Left); - t->setUserData(item); - t->setUserString("ToolTipType", "ItemPtr"); - t->setUserString("Equipped", equipped ? "true" : "false"); - t->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); - t->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - - // cost / charge - Gui::SharedStateButton* costCharge = mSpellView->createWidget(equipped ? "SandTextButton" : "SpellTextUnequipped", - MyGUI::IntCoord(4, mHeight, mWidth-8, spellHeight), MyGUI::Align::Left | MyGUI::Align::Top); - - float enchantCost = enchant->mData.mCost; - 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(castCost); - int currentCharge = int(item.getCellRef().getEnchantmentCharge()); - if (currentCharge == -1) - currentCharge = enchant->mData.mCharge; - std::string charge = boost::lexical_cast(currentCharge); - if (enchant->mData.mType == ESM::Enchantment::CastOnce) - { - // this is Morrowind behaviour - cost = "100"; - charge = "100"; - } - - - costCharge->setUserData(item); - costCharge->setUserString("ToolTipType", "ItemPtr"); - costCharge->setUserString("Equipped", equipped ? "true" : "false"); - costCharge->eventMouseButtonClick += MyGUI::newDelegate(this, &SpellWindow::onEnchantedItemSelected); - costCharge->eventMouseWheel += MyGUI::newDelegate(this, &SpellWindow::onMouseWheel); - costCharge->setCaption(cost + "/" + charge); - costCharge->setTextAlign(MyGUI::Align::Right); - - Gui::ButtonGroup group; - group.push_back(t); - group.push_back(costCharge); - Gui::SharedStateButton::createButtonGroup(group); - - if (store.getSelectedEnchantItem() != store.end()) - t->setStateSelected(item == *store.getSelectedEnchantItem()); - - t->setSize(mWidth-12-costCharge->getTextSize().width, t->getHeight()); - - mHeight += spellHeight; - } - - // Canvas size must be expressed with VScroll disabled, otherwise MyGUI would expand the scroll area when the scrollbar is hidden - mSpellView->setVisibleVScroll(false); - mSpellView->setCanvasSize(mSpellView->getWidth(), std::max(mSpellView->getHeight(), mHeight)); - mSpellView->setVisibleVScroll(true); + mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->update(); } - void SpellWindow::addGroup(const std::string &label, const std::string& label2) - { - if (mSpellView->getChildCount() > 0) - { - MyGUI::ImageBox* separator = mSpellView->createWidget("MW_HLine", - MyGUI::IntCoord(4, mHeight, mWidth-8, 18), - MyGUI::Align::Left | MyGUI::Align::Top); - separator->setNeedMouseFocus(false); - mHeight += 18; - } - - MyGUI::TextBox* groupWidget = mSpellView->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth, 24), - MyGUI::Align::Left | MyGUI::Align::Top | MyGUI::Align::HStretch); - groupWidget->setCaptionWithReplacing(label); - groupWidget->setTextAlign(MyGUI::Align::Left); - groupWidget->setNeedMouseFocus(false); - - if (label2 != "") - { - MyGUI::TextBox* groupWidget2 = mSpellView->createWidget("SandBrightText", - MyGUI::IntCoord(0, mHeight, mWidth-4, 24), - MyGUI::Align::Left | MyGUI::Align::Top); - groupWidget2->setCaptionWithReplacing(label2); - groupWidget2->setTextAlign(MyGUI::Align::Right); - groupWidget2->setNeedMouseFocus(false); - - groupWidget->setSize(mWidth-8-groupWidget2->getTextSize().width, groupWidget->getHeight()); - } - - mHeight += 24; - } - - void SpellWindow::onWindowResize(MyGUI::Window* _sender) - { - if (mMainWidget->getSize() != mWindowSize) - { - mWindowSize = mMainWidget->getSize(); - updateSpells(); - } - } - - void SpellWindow::onEnchantedItemSelected(MyGUI::Widget* _sender) + void SpellWindow::onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); - MWWorld::Ptr item = *_sender->getUserData(); // retrieve ContainerStoreIterator to the item MWWorld::ContainerStoreIterator it = store.begin(); @@ -349,27 +82,41 @@ namespace MWGui break; } } - assert(it != store.end()); + if (it == store.end()) + throw std::runtime_error("can't find selected item"); // equip, if it can be equipped and is not already equipped - if (_sender->getUserString("Equipped") == "false" + if (!alreadyEquipped && !item.getClass().getEquipmentSlots(item).first.empty()) { MWBase::Environment::get().getWindowManager()->getInventoryWindow()->useItem(item); + // make sure that item was successfully equipped + if (!store.isEquipped(item)) + return; } - MWBase::Environment::get().getWindowManager()->unsetSelectedSpell(); store.setSelectedEnchantItem(it); + // to reset WindowManager::mSelectedSpell immediately + MWBase::Environment::get().getWindowManager()->setSelectedEnchantItem(*it); updateSpells(); } - void SpellWindow::onSpellSelected(MyGUI::Widget* _sender) + void SpellWindow::onModelIndexSelected(SpellModel::ModelIndex index) { - std::string spellId = _sender->getUserString("Spell"); - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); + const Spell& spell = mSpellView->getModel()->getItem(index); + if (spell.mType == Spell::Type_EnchantedItem) + { + onEnchantedItemSelected(spell.mItem, spell.mActive); + } + else + { + onSpellSelected(spell.mId); + } + } + void SpellWindow::onSpellSelected(const std::string& spellId) + { if (MyGUI::InputManager::getInstance().isShiftPressed()) { // delete spell, if allowed @@ -396,6 +143,8 @@ namespace MWGui } else { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + MWWorld::InventoryStore& store = player.getClass().getInventoryStore(player); store.setSelectedEnchantItem(store.end()); MWBase::Environment::get().getWindowManager()->setSelectedSpell(spellId, int(MWMechanics::getSpellSuccessChance(spellId, player))); } @@ -403,22 +152,6 @@ namespace MWGui updateSpells(); } - int SpellWindow::estimateHeight(int numSpells) const - { - int height = 0; - height += 24 * 3 + 18 * 2; // group headings - height += numSpells * 18; - return height; - } - - void SpellWindow::onMouseWheel(MyGUI::Widget* _sender, int _rel) - { - if (mSpellView->getViewOffset().top + _rel*0.3 > 0) - mSpellView->setViewOffset(MyGUI::IntPoint(0, 0)); - else - mSpellView->setViewOffset(MyGUI::IntPoint(0, mSpellView->getViewOffset().top + _rel*0.3)); - } - void SpellWindow::onDeleteSpellAccept() { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -432,4 +165,25 @@ namespace MWGui updateSpells(); } + + void SpellWindow::cycle(bool next) + { + mSpellView->setModel(new SpellModel(MWBase::Environment::get().getWorld()->getPlayerPtr())); + mSpellView->getModel()->update(); + + SpellModel::ModelIndex selected = 0; + for (SpellModel::ModelIndex i = 0; igetModel()->getItemCount()); ++i) + { + if (mSpellView->getModel()->getItem(i).mSelected) + selected = i; + } + + selected += next ? 1 : -1; + int itemcount = mSpellView->getModel()->getItemCount(); + if (itemcount == 0) + return; + selected = (selected + itemcount) % itemcount; + + onModelIndexSelected(selected); + } } diff --git a/apps/openmw/mwgui/spellwindow.hpp b/apps/openmw/mwgui/spellwindow.hpp index a74847b907..650218d307 100644 --- a/apps/openmw/mwgui/spellwindow.hpp +++ b/apps/openmw/mwgui/spellwindow.hpp @@ -4,13 +4,12 @@ #include "windowpinnablebase.hpp" #include "../mwworld/ptr.hpp" +#include "spellmodel.hpp" + namespace MWGui { class SpellIcons; - - bool sortItems(const MWWorld::Ptr& left, const MWWorld::Ptr& right); - - bool sortSpells(const std::string& left, const std::string& right); + class SpellView; class SpellWindow : public WindowPinnableBase, public NoDrop { @@ -22,31 +21,24 @@ namespace MWGui void onFrame(float dt) { NoDrop::onFrame(dt); } + /// Cycle to next/previous spell + void cycle(bool next); + protected: - MyGUI::ScrollView* mSpellView; MyGUI::Widget* mEffectBox; - int mHeight; - int mWidth; - - MyGUI::IntSize mWindowSize; - std::string mSpellToDelete; - void addGroup(const std::string& label, const std::string& label2); - - int estimateHeight(int numSpells) const; - - void onWindowResize(MyGUI::Window* _sender); - void onEnchantedItemSelected(MyGUI::Widget* _sender); - void onSpellSelected(MyGUI::Widget* _sender); - void onMouseWheel(MyGUI::Widget* _sender, int _rel); + void onEnchantedItemSelected(MWWorld::Ptr item, bool alreadyEquipped); + void onSpellSelected(const std::string& spellId); + void onModelIndexSelected(SpellModel::ModelIndex index); void onDeleteSpellAccept(); virtual void onPinToggled(); virtual void onTitleDoubleClicked(); virtual void open(); + SpellView* mSpellView; SpellIcons* mSpellIcons; }; } diff --git a/apps/openmw/mwgui/statswindow.cpp b/apps/openmw/mwgui/statswindow.cpp index baa779c1c4..2a22f42393 100644 --- a/apps/openmw/mwgui/statswindow.cpp +++ b/apps/openmw/mwgui/statswindow.cpp @@ -1,6 +1,10 @@ #include "statswindow.hpp" -#include +#include +#include +#include +#include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -8,6 +12,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" @@ -103,7 +108,6 @@ namespace MWGui void StatsWindow::setPlayerName(const std::string& playerName) { mMainWidget->castType()->setCaption(playerName); - adjustWindowCaption(); } void StatsWindow::setValue (const std::string& id, const MWMechanics::AttributeValue& value) @@ -145,7 +149,7 @@ namespace MWGui // health, magicka, fatigue tooltip MyGUI::Widget* w; - std::string valStr = boost::lexical_cast(current) + "/" + boost::lexical_cast(modified); + std::string valStr = MyGUI::utility::toString(current) + "/" + MyGUI::utility::toString(modified); if (id == "HBar") { getWidget(w, "Health"); @@ -190,7 +194,7 @@ namespace MWGui if (widget) { int modified = value.getModified(), base = value.getBase(); - std::string text = boost::lexical_cast(modified); + std::string text = MyGUI::utility::toString(modified); std::string state = "normal"; if (modified > base) state = "increased"; @@ -239,10 +243,10 @@ namespace MWGui { int max = MWBase::Environment::get().getWorld()->getStore().get().find("iLevelUpTotal")->getInt(); getWidget(levelWidget, i==0 ? "Level_str" : "LevelText"); - levelWidget->setUserString("RangePosition_LevelProgress", boost::lexical_cast(PCstats.getLevelProgress())); - levelWidget->setUserString("Range_LevelProgress", boost::lexical_cast(max)); - levelWidget->setUserString("Caption_LevelProgressText", boost::lexical_cast(PCstats.getLevelProgress()) + "/" - + boost::lexical_cast(max)); + levelWidget->setUserString("RangePosition_LevelProgress", MyGUI::utility::toString(PCstats.getLevelProgress())); + levelWidget->setUserString("Range_LevelProgress", MyGUI::utility::toString(max)); + levelWidget->setUserString("Caption_LevelProgressText", MyGUI::utility::toString(PCstats.getLevelProgress()) + "/" + + MyGUI::utility::toString(max)); } setFactions(PCstats.getFactionRanks()); @@ -342,10 +346,14 @@ namespace MWGui { MyGUI::TextBox* skillNameWidget; - skillNameWidget = mSkillView->createWidget("SandText", coord1 + MyGUI::IntSize(coord2.width, 0), MyGUI::Align::Default); + skillNameWidget = mSkillView->createWidget("SandText", coord1, MyGUI::Align::Default); + skillNameWidget->setCaption(text); skillNameWidget->eventMouseWheel += MyGUI::newDelegate(this, &StatsWindow::onMouseWheel); + int textWidth = skillNameWidget->getTextSize().width; + skillNameWidget->setSize(textWidth, skillNameWidget->getHeight()); + mSkillWidgets.push_back(skillNameWidget); coord1.top += sLineHeight; @@ -368,18 +376,26 @@ namespace MWGui for (SkillList::const_iterator it = skills.begin(); it != end; ++it) { int skillId = *it; - if (skillId < 0 || skillId > ESM::Skill::Length) // Skip unknown skill indexes + if (skillId < 0 || skillId >= ESM::Skill::Length) // Skip unknown skill indexes continue; - assert(skillId >= 0 && skillId < ESM::Skill::Length); const std::string &skillNameId = ESM::Skill::sSkillNameIds[skillId]; const MWMechanics::SkillValue &stat = mSkillValues.find(skillId)->second; int base = stat.getBase(); int modified = stat.getModified(); - int progressPercent = stat.getProgress() * 100; + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); + float progressRequirement = player.getClass().getNpcStats(player).getSkillProgressRequirement(skillId, + *esmStore.get().find(player.get()->mBase->mClass)); + + // This is how vanilla MW displays the progress bar (I think). Note it's slightly inaccurate, + // due to the int casting in the skill levelup logic. Also the progress label could in rare cases + // reach 100% without the skill levelling up. + // Leaving the original display logic for now, for consistency with ess-imported savegames. + int progressPercent = int(float(stat.getProgress()) / float(progressRequirement) * 100.f + 0.5f); + const ESM::Skill* skill = esmStore.get().find(skillId); std::string icon = "icons\\k\\" + ESM::Skill::sIconNames[skillId]; @@ -393,7 +409,7 @@ namespace MWGui else if (modified < base) state = "decreased"; MyGUI::TextBox* widget = addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString(skillNameId, skillNameId), - boost::lexical_cast(static_cast(modified)), state, coord1, coord2); + MyGUI::utility::toString(static_cast(modified)), state, coord1, coord2); for (int i=0; i<2; ++i) { @@ -411,9 +427,9 @@ namespace MWGui mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillProgressVBox", "true"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillProgressVBox", "false"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", boost::lexical_cast(progressPercent)+"/100"); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Caption_SkillProgressText", MyGUI::utility::toString(progressPercent)+"/100"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Range_SkillProgress", "100"); - mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", boost::lexical_cast(progressPercent)); + mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("RangePosition_SkillProgress", MyGUI::utility::toString(progressPercent)); } else { mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("Visible_SkillMaxed", "true"); mSkillWidgets[mSkillWidgets.size()-1-i]->setUserString("UserData^Hidden_SkillMaxed", "false"); @@ -524,8 +540,8 @@ namespace MWGui const ESM::Attribute* attr1 = store.get().find(faction->mData.mAttribute[0]); const ESM::Attribute* attr2 = store.get().find(faction->mData.mAttribute[1]); - text += "\n#{fontcolourhtml=normal}#{" + attr1->mName + "}: " + boost::lexical_cast(rankData.mAttribute1) - + ", #{" + attr2->mName + "}: " + boost::lexical_cast(rankData.mAttribute2); + text += "\n#{fontcolourhtml=normal}#{" + attr1->mName + "}: " + MyGUI::utility::toString(rankData.mAttribute1) + + ", #{" + attr2->mName + "}: " + MyGUI::utility::toString(rankData.mAttribute2); text += "\n\n#{fontcolourhtml=header}#{sFavoriteSkills}"; text += "\n#{fontcolourhtml=normal}"; @@ -546,9 +562,9 @@ namespace MWGui text += "\n"; if (rankData.mSkill1 > 0) - text += "\n#{sNeedOneSkill} " + boost::lexical_cast(rankData.mSkill1); + text += "\n#{sNeedOneSkill} " + MyGUI::utility::toString(rankData.mSkill1); if (rankData.mSkill2 > 0) - text += "\n#{sNeedTwoSkills} " + boost::lexical_cast(rankData.mSkill2); + text += "\n#{sNeedTwoSkills} " + MyGUI::utility::toString(rankData.mSkill2); } } @@ -577,7 +593,7 @@ namespace MWGui addSeparator(coord1, coord2); addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sReputation", "Reputation"), - boost::lexical_cast(static_cast(mReputation)), "normal", coord1, coord2); + MyGUI::utility::toString(static_cast(mReputation)), "normal", coord1, coord2); for (int i=0; i<2; ++i) { @@ -587,7 +603,7 @@ namespace MWGui } addValueItem(MWBase::Environment::get().getWindowManager()->getGameSettingString("sBounty", "Bounty"), - boost::lexical_cast(static_cast(mBounty)), "normal", coord1, coord2); + MyGUI::utility::toString(static_cast(mBounty)), "normal", coord1, coord2); for (int i=0; i<2; ++i) { diff --git a/apps/openmw/mwgui/statswindow.hpp b/apps/openmw/mwgui/statswindow.hpp index f41995ac07..2cf4ca819b 100644 --- a/apps/openmw/mwgui/statswindow.hpp +++ b/apps/openmw/mwgui/statswindow.hpp @@ -1,11 +1,11 @@ #ifndef MWGUI_STATS_WINDOW_H #define MWGUI_STATS_WINDOW_H -#include "../mwworld/esmstore.hpp" - #include "../mwmechanics/stat.hpp" #include "windowpinnablebase.hpp" +#include + namespace MWGui { class WindowManager; diff --git a/apps/openmw/mwgui/textinput.cpp b/apps/openmw/mwgui/textinput.cpp index 954bc41abb..958b52dc08 100644 --- a/apps/openmw/mwgui/textinput.cpp +++ b/apps/openmw/mwgui/textinput.cpp @@ -3,6 +3,9 @@ #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" +#include +#include + namespace MWGui { @@ -61,13 +64,18 @@ namespace MWGui void TextInputDialog::onTextAccepted(MyGUI::Edit* _sender) { - if (mTextEdit->getCaption() == "") - { - MWBase::Environment::get().getWindowManager()->messageBox ("#{sNotifyMessage37}"); - MWBase::Environment::get().getWindowManager()->setKeyFocusWidget (mTextEdit); - } - else - eventDone(this); + onOkClicked(_sender); } + std::string TextInputDialog::getTextInput() const + { + return mTextEdit->getCaption(); + } + + void TextInputDialog::setTextInput(const std::string &text) + { + mTextEdit->setCaption(text); + } + + } diff --git a/apps/openmw/mwgui/textinput.hpp b/apps/openmw/mwgui/textinput.hpp index c83e3ffa99..57dd34dd62 100644 --- a/apps/openmw/mwgui/textinput.hpp +++ b/apps/openmw/mwgui/textinput.hpp @@ -15,8 +15,8 @@ namespace MWGui public: TextInputDialog(); - std::string getTextInput() const { return mTextEdit->getCaption(); } - void setTextInput(const std::string &text) { mTextEdit->setCaption(text); } + std::string getTextInput() const; + void setTextInput(const std::string &text); void setNextButtonShow(bool shown); void setTextLabel(const std::string &label); diff --git a/apps/openmw/mwgui/timeadvancer.cpp b/apps/openmw/mwgui/timeadvancer.cpp new file mode 100644 index 0000000000..43504ce870 --- /dev/null +++ b/apps/openmw/mwgui/timeadvancer.cpp @@ -0,0 +1,73 @@ +#include "timeadvancer.hpp" + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +namespace MWGui +{ + TimeAdvancer::TimeAdvancer(float delay) + : mRunning(false), + mCurHour(0), + mHours(1), + mInterruptAt(-1), + mDelay(delay), + mRemainingTime(delay) + { + } + + void TimeAdvancer::run(int hours, int interruptAt) + { + mHours = hours; + mCurHour = 0; + mInterruptAt = interruptAt; + mRemainingTime = mDelay; + + mRunning = true; + } + + void TimeAdvancer::stop() + { + mRunning = false; + } + + void TimeAdvancer::onFrame(float dt) + { + if (!mRunning) + return; + + if (mCurHour == mInterruptAt) + { + stop(); + eventInterrupted(); + return; + } + + mRemainingTime -= dt; + + while (mRemainingTime <= 0) + { + mRemainingTime += mDelay; + ++mCurHour; + + if (mCurHour <= mHours) + eventProgressChanged(mCurHour, mHours); + else + { + stop(); + eventFinished(); + return; + } + + } + } + + int TimeAdvancer::getHours() + { + return mHours; + } + + bool TimeAdvancer::isRunning() + { + return mRunning; + } +} diff --git a/apps/openmw/mwgui/timeadvancer.hpp b/apps/openmw/mwgui/timeadvancer.hpp new file mode 100644 index 0000000000..8367b5a8b2 --- /dev/null +++ b/apps/openmw/mwgui/timeadvancer.hpp @@ -0,0 +1,40 @@ +#ifndef MWGUI_TIMEADVANCER_H +#define MWGUI_TIMEADVANCER_H + +#include + +namespace MWGui +{ + class TimeAdvancer + { + public: + TimeAdvancer(float delay); + + void run(int hours, int interruptAt=-1); + void stop(); + void onFrame(float dt); + + int getHours(); + bool isRunning(); + + // signals + typedef MyGUI::delegates::CMultiDelegate0 EventHandle_Void; + typedef MyGUI::delegates::CMultiDelegate2 EventHandle_IntInt; + + EventHandle_IntInt eventProgressChanged; + EventHandle_Void eventInterrupted; + EventHandle_Void eventFinished; + + private: + bool mRunning; + + int mCurHour; + int mHours; + int mInterruptAt; + + float mDelay; + float mRemainingTime; + }; +} + +#endif diff --git a/apps/openmw/mwgui/tooltips.cpp b/apps/openmw/mwgui/tooltips.cpp index 396c8fa489..2c10004a63 100644 --- a/apps/openmw/mwgui/tooltips.cpp +++ b/apps/openmw/mwgui/tooltips.cpp @@ -2,15 +2,21 @@ #include -#include +#include +#include +#include +#include #include +#include #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/spellcasting.hpp" #include "mapwindow.hpp" @@ -159,15 +165,19 @@ namespace MWGui // special handling for markers on the local map: the tooltip should only be visible // if the marker is not hidden due to the fog of war. - if (focus->getUserString ("IsMarker") == "true") + if (type == "MapMarker") { - LocalMapBase::MarkerPosition pos = *focus->getUserData(); + LocalMapBase::MarkerUserData data = *focus->getUserData(); - if (!MWBase::Environment::get().getWorld ()->isPositionExplored (pos.nX, pos.nY, pos.cellX, pos.cellY, pos.interior)) + if (!MWBase::Environment::get().getWorld ()->isPositionExplored (data.nX, data.nY, data.cellX, data.cellY, data.interior)) return; - } - if (type == "ItemPtr") + ToolTipInfo info; + info.text = data.caption; + info.notes = data.notes; + tooltipSize = createToolTip(info); + } + else if (type == "ItemPtr") { mFocusObject = *focus->getUserData(); tooltipSize = getToolTipViaPtr(false); @@ -399,16 +409,33 @@ namespace MWGui MyGUI::IntSize totalSize = MyGUI::IntSize( std::min(std::max(textSize.width,captionSize.width + ((image != "") ? imageCaptionHPadding : 0)),maximumWidth), ((text != "") ? textSize.height + imageCaptionVPadding : 0) + captionHeight ); + for (std::vector::const_iterator it = info.notes.begin(); it != info.notes.end(); ++it) + { + MyGUI::ImageBox* icon = mDynamicToolTipBox->createWidget("MarkerButton", + MyGUI::IntCoord(padding.left, totalSize.height+padding.top, 8, 8), MyGUI::Align::Default); + icon->setColour(MyGUI::Colour(1.0,0.3,0.3)); + MyGUI::EditBox* edit = mDynamicToolTipBox->createWidget("SandText", + MyGUI::IntCoord(padding.left+8+4, totalSize.height+padding.top, 300-padding.left-8-4, 300-totalSize.height), + MyGUI::Align::Default); + edit->setEditMultiLine(true); + edit->setEditWordWrap(true); + edit->setCaption(*it); + edit->setSize(edit->getWidth(), edit->getTextSize().height); + icon->setPosition(icon->getLeft(),(edit->getTop()+edit->getBottom())/2-icon->getHeight()/2); + totalSize.height += std::max(edit->getHeight(), icon->getHeight()); + totalSize.width = std::max(totalSize.width, edit->getWidth()+8+4); + } + if (!info.effects.empty()) { MyGUI::Widget* effectArea = mDynamicToolTipBox->createWidget("", MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), - MyGUI::Align::Stretch, "ToolTipEffectArea"); + MyGUI::Align::Stretch); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); Widgets::MWEffectListPtr effectsWidget = effectArea->createWidget - ("MW_StatName", coord, MyGUI::Align::Default, "ToolTipEffectsWidget"); + ("MW_StatName", coord, MyGUI::Align::Default); effectsWidget->setEffectList(info.effects); std::vector effectItems; @@ -422,12 +449,12 @@ namespace MWGui assert(enchant); MyGUI::Widget* enchantArea = mDynamicToolTipBox->createWidget("", MyGUI::IntCoord(padding.left, totalSize.height, 300-padding.left, 300-totalSize.height), - MyGUI::Align::Stretch, "ToolTipEnchantArea"); + MyGUI::Align::Stretch); MyGUI::IntCoord coord(0, 6, totalSize.width, 24); Widgets::MWEffectListPtr enchantWidget = enchantArea->createWidget - ("MW_StatName", coord, MyGUI::Align::Default, "ToolTipEnchantWidget"); + ("MW_StatName", coord, MyGUI::Align::Default); enchantWidget->setEffectList(Widgets::MWEffectList::effectListFromESM(&enchant->mEffects)); std::vector enchantEffectItems; @@ -466,7 +493,7 @@ namespace MWGui chargeCoord = MyGUI::IntCoord((totalSize.width - chargeAndTextWidth)/2 + chargeTextWidth, coord.top+6, chargeWidth, 18); } Widgets::MWDynamicStatPtr chargeWidget = enchantArea->createWidget - ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default, "ToolTipEnchantCharge"); + ("MW_ChargeBar", chargeCoord, MyGUI::Align::Default); chargeWidget->setValue(charge, maxCharge); totalSize.height += 24; } @@ -501,7 +528,7 @@ namespace MWGui { MyGUI::ImageBox* imageWidget = mDynamicToolTipBox->createWidget("ImageBox", MyGUI::IntCoord((totalSize.width - captionSize.width - imageCaptionHPadding)/2, 0, imageSize, imageSize), - MyGUI::Align::Left | MyGUI::Align::Top, "ToolTipImage"); + MyGUI::Align::Left | MyGUI::Align::Top); imageWidget->setImageTexture(realImage); imageWidget->setPosition (imageWidget->getPosition() + padding); } @@ -550,7 +577,7 @@ namespace MWGui if (value == 1) return ""; else - return " (" + boost::lexical_cast(value) + ")"; + return " (" + MyGUI::utility::toString(value) + ")"; } std::string ToolTips::getCellRefString(const MWWorld::CellRef& cellref) @@ -560,6 +587,15 @@ namespace MWGui ret += getMiscString(cellref.getFaction(), "Faction"); if (cellref.getFactionRank() > 0) ret += getValueString(cellref.getFactionRank(), "Rank"); + + std::vector > itemOwners = + MWBase::Environment::get().getMechanicsManager()->getStolenItemOwners(cellref.getRefId()); + + for (std::vector >::const_iterator it = itemOwners.begin(); it != itemOwners.end(); ++it) + { + ret += std::string("\nStolen from ") + it->first; + } + ret += getMiscString(cellref.getGlobalVariable(), "Global"); return ret; } diff --git a/apps/openmw/mwgui/tooltips.hpp b/apps/openmw/mwgui/tooltips.hpp index ffbb35e047..4bd4d88aa2 100644 --- a/apps/openmw/mwgui/tooltips.hpp +++ b/apps/openmw/mwgui/tooltips.hpp @@ -7,6 +7,12 @@ #include "widgets.hpp" +namespace ESM +{ + class Class; + struct Race; +} + namespace MWGui { // Info about tooltip that is supplied by the MWWorld::Class object @@ -32,6 +38,9 @@ namespace MWGui // effects (for potions, ingredients) Widgets::SpellEffectList effects; + // local map notes + std::vector notes; + bool isPotion; // potions do not show target in the tooltip bool wordWrap; }; diff --git a/apps/openmw/mwgui/tradeitemmodel.cpp b/apps/openmw/mwgui/tradeitemmodel.cpp index 3abfac997b..a5a2b3e881 100644 --- a/apps/openmw/mwgui/tradeitemmodel.cpp +++ b/apps/openmw/mwgui/tradeitemmodel.cpp @@ -135,11 +135,9 @@ namespace MWGui if (i == sourceModel->getItemCount()) throw std::runtime_error("The borrowed item disappeared"); - // reset owner while copying, but only for items bought by the player - bool setNewOwner = (mMerchant.isEmpty()); const ItemStack& item = sourceModel->getItem(i); // copy the borrowed items to our model - copyItem(item, it->mCount, setNewOwner); + copyItem(item, it->mCount); // then remove them from the source model sourceModel->removeItem(item, it->mCount); } @@ -175,17 +173,8 @@ namespace MWGui // don't show equipped items if(mMerchant.getClass().hasInventoryStore(mMerchant)) { - bool isEquipped = false; MWWorld::InventoryStore& store = mMerchant.getClass().getInventoryStore(mMerchant); - for (int slot=0; slot +#include +#include +#include #include @@ -14,6 +16,7 @@ #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" @@ -94,6 +97,20 @@ namespace MWGui setCoord(400, 0, 400, 300); } + void TradeWindow::restock() + { + // Restock items on the actor inventory + mPtr.getClass().restock(mPtr); + + // Also restock any containers owned by this merchant, which are also available to buy in the trade window + std::vector itemSources; + MWBase::Environment::get().getWorld()->getContainersOwnedBy(mPtr, itemSources); + for (std::vector::iterator it = itemSources.begin(); it != itemSources.end(); ++it) + { + it->getClass().restock(*it); + } + } + void TradeWindow::startTrade(const MWWorld::Ptr& actor) { mPtr = actor; @@ -101,6 +118,8 @@ namespace MWGui mCurrentBalance = 0; mCurrentMerchantOffer = 0; + restock(); + std::vector itemSources; MWBase::Environment::get().getWorld()->getContainersOwnedBy(actor, itemSources); @@ -115,8 +134,6 @@ namespace MWGui updateLabels(); - // Careful here. setTitle may cause size updates, causing itemview redraw, so make sure to do it last - // or we end up using a possibly invalid model. setTitle(actor.getClass().getName(actor)); onFilterChanged(mFilterAll); @@ -284,22 +301,24 @@ namespace MWGui // check if the player is attempting to sell back an item stolen from this actor for (std::vector::iterator it = merchantBought.begin(); it != merchantBought.end(); ++it) { - if (Misc::StringUtils::ciEqual(it->mBase.getCellRef().getOwner(), mPtr.getCellRef().getRefId())) + if (MWBase::Environment::get().getMechanicsManager()->isItemStolenFrom(it->mBase.getCellRef().getRefId(), + mPtr.getCellRef().getRefId())) { 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, + MWBase::Environment::get().getMechanicsManager()->commitCrime(player, mPtr, MWBase::MechanicsManager::OT_Theft, it->mBase.getClass().getValue(it->mBase) - * it->mCount); + * it->mCount, true); onCancelButtonClicked(mCancelButton); MWBase::Environment::get().getDialogueManager()->goodbyeSelected(); return; } } + // TODO: move to mwmechanics + // Is the player buying? bool buying = (mCurrentMerchantOffer < 0); @@ -469,7 +488,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + boost::lexical_cast(playerGold)); + mPlayerGold->setCaptionWithReplacing("#{sYourGold} " + MyGUI::utility::toString(playerGold)); if (mCurrentBalance > 0) { @@ -482,7 +501,7 @@ namespace MWGui mTotalBalance->setValue(std::abs(mCurrentBalance)); - mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + boost::lexical_cast(getMerchantGold())); + mMerchantGold->setCaptionWithReplacing("#{sSellerGold} " + MyGUI::utility::toString(getMerchantGold())); } void TradeWindow::updateOffer() diff --git a/apps/openmw/mwgui/tradewindow.hpp b/apps/openmw/mwgui/tradewindow.hpp index 47de9215a2..a23196d70e 100644 --- a/apps/openmw/mwgui/tradewindow.hpp +++ b/apps/openmw/mwgui/tradewindow.hpp @@ -1,19 +1,19 @@ #ifndef MWGUI_TRADEWINDOW_H #define MWGUI_TRADEWINDOW_H -#include "container.hpp" +#include "referenceinterface.hpp" +#include "windowbase.hpp" namespace Gui { class NumericEditBox; } -namespace MWGui +namespace MyGUI { - class WindowManager; + class ControllerItem; } - namespace MWGui { class ItemView; @@ -98,6 +98,8 @@ namespace MWGui virtual void onReferenceUnavailable(); int getMerchantGold(); + + void restock(); }; } diff --git a/apps/openmw/mwgui/trainingwindow.cpp b/apps/openmw/mwgui/trainingwindow.cpp index 6ff5ae35f6..5a5f611153 100644 --- a/apps/openmw/mwgui/trainingwindow.cpp +++ b/apps/openmw/mwgui/trainingwindow.cpp @@ -1,6 +1,6 @@ #include "trainingwindow.hpp" -#include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/environment.hpp" @@ -10,6 +10,7 @@ #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/npcstats.hpp" @@ -39,12 +40,18 @@ namespace MWGui TrainingWindow::TrainingWindow() : WindowBase("openmw_trainingwindow.layout") , mFadeTimeRemaining(0) + , mTimeAdvancer(0.05) { getWidget(mTrainingOptions, "TrainingOptions"); getWidget(mCancelButton, "CancelButton"); getWidget(mPlayerGold, "PlayerGold"); mCancelButton->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onCancelButtonClicked); + + mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &TrainingWindow::onTrainingProgressChanged); + mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &TrainingWindow::onTrainingFinished); + + mProgressBar.setVisible(false); } void TrainingWindow::open() @@ -64,7 +71,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats (actor); @@ -99,7 +106,7 @@ namespace MWGui button->setUserData(skills[i].first); button->eventMouseButtonClick += MyGUI::newDelegate(this, &TrainingWindow::onTrainingSelected); - button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + boost::lexical_cast(price)); + button->setCaptionWithReplacing("#{" + ESM::Skill::sSkillNameIds[skills[i].first] + "} - " + MyGUI::utility::toString(price)); button->setSize(button->getTextSize ().width+12, button->getSize().height); @@ -172,12 +179,28 @@ namespace MWGui MWBase::Environment::get().getMechanicsManager()->rest(false); MWBase::Environment::get().getMechanicsManager()->rest(false); + mProgressBar.setVisible(true); + mProgressBar.setProgress(0, 2); + mTimeAdvancer.run(2); + MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.25); mFadeTimeRemaining = 0.5; } + void TrainingWindow::onTrainingProgressChanged(int cur, int total) + { + mProgressBar.setProgress(cur, total); + } + + void TrainingWindow::onTrainingFinished() + { + mProgressBar.setVisible(false); + } + void TrainingWindow::onFrame(float dt) { + mTimeAdvancer.onFrame(dt); + if (mFadeTimeRemaining <= 0) return; diff --git a/apps/openmw/mwgui/trainingwindow.hpp b/apps/openmw/mwgui/trainingwindow.hpp index 1fc902b20a..7c45820626 100644 --- a/apps/openmw/mwgui/trainingwindow.hpp +++ b/apps/openmw/mwgui/trainingwindow.hpp @@ -3,6 +3,8 @@ #include "windowbase.hpp" #include "referenceinterface.hpp" +#include "timeadvancer.hpp" +#include "waitdialog.hpp" namespace MWGui { @@ -26,11 +28,17 @@ namespace MWGui void onCancelButtonClicked (MyGUI::Widget* sender); void onTrainingSelected(MyGUI::Widget* sender); + void onTrainingProgressChanged(int cur, int total); + void onTrainingFinished(); + MyGUI::Widget* mTrainingOptions; MyGUI::Button* mCancelButton; MyGUI::TextBox* mPlayerGold; float mFadeTimeRemaining; + + WaitDialogProgressBar mProgressBar; + TimeAdvancer mTimeAdvancer; }; } diff --git a/apps/openmw/mwgui/travelwindow.cpp b/apps/openmw/mwgui/travelwindow.cpp index 6a0c99b8a3..50e08223e4 100644 --- a/apps/openmw/mwgui/travelwindow.cpp +++ b/apps/openmw/mwgui/travelwindow.cpp @@ -1,6 +1,8 @@ #include "travelwindow.hpp" -#include +#include +#include +#include #include @@ -87,7 +89,7 @@ namespace MWGui oss << price; toAdd->setUserString("price",oss.str()); - toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + boost::lexical_cast(price)+"#{sgp}"); + toAdd->setCaptionWithReplacing("#{sCell=" + name + "} - " + MyGUI::utility::toString(price)+"#{sgp}"); toAdd->setSize(toAdd->getTextSize().width,sLineHeight); toAdd->eventMouseWheel += MyGUI::newDelegate(this, &TravelWindow::onMouseWheel); toAdd->setUserString("Destination", name); @@ -192,7 +194,7 @@ namespace MWGui MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); int playerGold = player.getClass().getContainerStore(player).count(MWWorld::ContainerStore::sGoldId); - mPlayerGold->setCaptionWithReplacing("#{sGold}: " + boost::lexical_cast(playerGold)); + mPlayerGold->setCaptionWithReplacing("#{sGold}: " + MyGUI::utility::toString(playerGold)); mPlayerGold->setCoord(8, mPlayerGold->getTop(), mPlayerGold->getTextSize().width, diff --git a/apps/openmw/mwgui/travelwindow.hpp b/apps/openmw/mwgui/travelwindow.hpp index 3f19492562..3230f897f2 100644 --- a/apps/openmw/mwgui/travelwindow.hpp +++ b/apps/openmw/mwgui/travelwindow.hpp @@ -1,7 +1,9 @@ #ifndef MWGUI_TravelWINDOW_H #define MWGUI_TravelWINDOW_H -#include "container.hpp" + +#include "windowbase.hpp" +#include "referenceinterface.hpp" namespace MyGUI { diff --git a/apps/openmw/mwgui/videowidget.cpp b/apps/openmw/mwgui/videowidget.cpp index f8054925b6..0460708411 100644 --- a/apps/openmw/mwgui/videowidget.cpp +++ b/apps/openmw/mwgui/videowidget.cpp @@ -1,5 +1,9 @@ #include "videowidget.hpp" +#include + +#include + #include "../mwsound/movieaudiofactory.hpp" namespace MWGui @@ -7,40 +11,61 @@ namespace MWGui VideoWidget::VideoWidget() { + mPlayer.reset(new Video::VideoPlayer()); setNeedKeyFocus(true); } void VideoWidget::playVideo(const std::string &video) { - mPlayer.setAudioFactory(new MWSound::MovieAudioFactory()); - mPlayer.playVideo(video); + mPlayer->setAudioFactory(new MWSound::MovieAudioFactory()); + mPlayer->playVideo(video); - setImageTexture(mPlayer.getTextureName()); + setImageTexture(mPlayer->getTextureName()); } int VideoWidget::getVideoWidth() { - return mPlayer.getVideoWidth(); + return mPlayer->getVideoWidth(); } int VideoWidget::getVideoHeight() { - return mPlayer.getVideoHeight(); + return mPlayer->getVideoHeight(); } bool VideoWidget::update() { - return mPlayer.update(); + return mPlayer->update(); } void VideoWidget::stop() { - mPlayer.close(); + mPlayer->close(); } bool VideoWidget::hasAudioStream() { - return mPlayer.hasAudioStream(); + return mPlayer->hasAudioStream(); +} + +void VideoWidget::autoResize(bool stretch) +{ + MyGUI::IntSize screenSize = MyGUI::RenderManager::getInstance().getViewSize(); + if (getParent()) + screenSize = getParent()->getSize(); + + if (getVideoHeight() > 0 && !stretch) + { + double imageaspect = static_cast(getVideoWidth())/getVideoHeight(); + + int leftPadding = std::max(0.0, (screenSize.width - screenSize.height * imageaspect) / 2); + int topPadding = std::max(0.0, (screenSize.height - screenSize.width / imageaspect) / 2); + + setCoord(leftPadding, topPadding, + screenSize.width - leftPadding*2, screenSize.height - topPadding*2); + } + else + setCoord(0,0,screenSize.width,screenSize.height); } } diff --git a/apps/openmw/mwgui/videowidget.hpp b/apps/openmw/mwgui/videowidget.hpp index 79e1c26bb0..ee44328eb6 100644 --- a/apps/openmw/mwgui/videowidget.hpp +++ b/apps/openmw/mwgui/videowidget.hpp @@ -3,7 +3,10 @@ #include -#include +namespace Video +{ + class VideoPlayer; +} namespace MWGui { @@ -32,8 +35,14 @@ namespace MWGui /// Stop video and free resources (done automatically on destruction) void stop(); + /// Adjust the coordinates of this video widget relative to its parent, + /// based on the dimensions of the playing video. + /// @param stretch Stretch the video to fill the whole screen? If false, + /// black bars may be added to fix the aspect ratio. + void autoResize (bool stretch); + private: - Video::VideoPlayer mPlayer; + std::auto_ptr mPlayer; }; } diff --git a/apps/openmw/mwgui/waitdialog.cpp b/apps/openmw/mwgui/waitdialog.cpp index f95ec56751..ad873a7c3f 100644 --- a/apps/openmw/mwgui/waitdialog.cpp +++ b/apps/openmw/mwgui/waitdialog.cpp @@ -1,8 +1,9 @@ #include "waitdialog.hpp" -#include +#include #include +#include #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" @@ -12,12 +13,15 @@ #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwstate/charactermanager.hpp" +#include "widgets.hpp" + namespace MWGui { @@ -37,7 +41,7 @@ namespace MWGui { mProgressBar->setProgressRange (total); mProgressBar->setProgressPosition (cur); - mProgressText->setCaption(boost::lexical_cast(cur) + "/" + boost::lexical_cast(total)); + mProgressText->setCaption(MyGUI::utility::toString(cur) + "/" + MyGUI::utility::toString(total)); } // --------------------------------------------------------------------------------------------------------- @@ -45,12 +49,11 @@ namespace MWGui WaitDialog::WaitDialog() : WindowBase("openmw_wait_dialog.layout") , mProgressBar() - , mWaiting(false) + , mTimeAdvancer(0.05) , mSleeping(false) , mHours(1) - , mRemainingTime(0.05) - , mCurHour(0) , mManualHours(1) + , mFadeTimeRemaining(0) , mInterruptAt(-1) { getWidget(mDateTimeText, "DateTimeText"); @@ -66,6 +69,10 @@ namespace MWGui mWaitButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WaitDialog::onWaitButtonClicked); mHourSlider->eventScrollChangePosition += MyGUI::newDelegate(this, &WaitDialog::onHourSliderChangedPosition); + mTimeAdvancer.eventProgressChanged += MyGUI::newDelegate(this, &WaitDialog::onWaitingProgressChanged); + mTimeAdvancer.eventInterrupted += MyGUI::newDelegate(this, &WaitDialog::onWaitingInterrupted); + mTimeAdvancer.eventFinished += MyGUI::newDelegate(this, &WaitDialog::onWaitingFinished); + mProgressBar.setVisible (false); } @@ -103,9 +110,9 @@ namespace MWGui if (hour == 0) hour = 12; std::string dateTimeText = - boost::lexical_cast(MWBase::Environment::get().getWorld ()->getDay ()) + " " - + month + " (#{sDay} " + boost::lexical_cast(MWBase::Environment::get().getWorld ()->getTimeStamp ().getDay()) - + ") " + boost::lexical_cast(hour) + " " + (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); + MyGUI::utility::toString(MWBase::Environment::get().getWorld ()->getDay ()) + " " + + month + " (#{sDay} " + MyGUI::utility::toString(MWBase::Environment::get().getWorld ()->getTimeStamp ().getDay()) + + ") " + MyGUI::utility::toString(hour) + " " + (pm ? "#{sSaveMenuHelp05}" : "#{sSaveMenuHelp04}"); mDateTimeText->setCaptionWithReplacing (dateTimeText); } @@ -129,11 +136,9 @@ namespace MWGui MWBase::World* world = MWBase::Environment::get().getWorld(); MWBase::Environment::get().getWindowManager()->fadeScreenOut(0.2); + mFadeTimeRemaining = 0.4; setVisible(false); - mProgressBar.setVisible (true); - mWaiting = true; - mCurHour = 0; mHours = hoursToWait; // FIXME: move this somewhere else? @@ -160,8 +165,7 @@ namespace MWGui } } - mRemainingTime = 0.05; - mProgressBar.setProgress (0, mHours); + mProgressBar.setProgress (0, hoursToWait); } void WaitDialog::onCancelButtonClicked(MyGUI::Widget* sender) @@ -171,10 +175,40 @@ namespace MWGui void WaitDialog::onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position) { - mHourText->setCaptionWithReplacing (boost::lexical_cast(position+1) + " #{sRestMenu2}"); + mHourText->setCaptionWithReplacing (MyGUI::utility::toString(position+1) + " #{sRestMenu2}"); mManualHours = position+1; } + void WaitDialog::onWaitingProgressChanged(int cur, int total) + { + mProgressBar.setProgress(cur, total); + MWBase::Environment::get().getWorld()->advanceTime(1); + MWBase::Environment::get().getMechanicsManager()->rest(mSleeping); + } + + void WaitDialog::onWaitingInterrupted() + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sSleepInterrupt}"); + MWBase::Environment::get().getWorld()->spawnRandomCreature(mInterruptCreatureList); + stopWaiting(); + } + + void WaitDialog::onWaitingFinished() + { + stopWaiting(); + + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); + + // trigger levelup if possible + const MWWorld::Store &gmst = + MWBase::Environment::get().getWorld()->getStore().get(); + if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) + { + MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); + } + } + void WaitDialog::setCanRest (bool canRest) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -201,45 +235,17 @@ namespace MWGui void WaitDialog::onFrame(float dt) { - if (!mWaiting) + mTimeAdvancer.onFrame(dt); + + if (mFadeTimeRemaining <= 0) return; - if (mCurHour == mInterruptAt) + mFadeTimeRemaining -= dt; + + if (mFadeTimeRemaining <= 0) { - MWBase::Environment::get().getWindowManager()->messageBox("#{sSleepInterrupt}"); - MWBase::Environment::get().getWorld()->spawnRandomCreature(mInterruptCreatureList); - stopWaiting(); - } - - mRemainingTime -= dt; - - while (mRemainingTime < 0) - { - mRemainingTime += 0.05; - ++mCurHour; - mProgressBar.setProgress (mCurHour, mHours); - - if (mCurHour <= mHours) - { - MWBase::Environment::get().getWorld ()->advanceTime (1); - MWBase::Environment::get().getMechanicsManager ()->rest (mSleeping); - } - } - - if (mCurHour > mHours) - { - stopWaiting(); - - MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const MWMechanics::NpcStats &pcstats = player.getClass().getNpcStats(player); - - // trigger levelup if possible - const MWWorld::Store &gmst = - MWBase::Environment::get().getWorld()->getStore().get(); - if (mSleeping && pcstats.getLevelProgress () >= gmst.find("iLevelUpTotal")->getInt()) - { - MWBase::Environment::get().getWindowManager()->pushGuiMode (GM_Levelup); - } + mProgressBar.setVisible(true); + mTimeAdvancer.run(mHours, mInterruptAt); } } @@ -249,14 +255,14 @@ namespace MWGui mProgressBar.setVisible (false); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_Rest); MWBase::Environment::get().getWindowManager()->removeGuiMode (GM_RestBed); - mWaiting = false; + mTimeAdvancer.stop(); } void WaitDialog::wakeUp () { mSleeping = false; - mWaiting = false; + mTimeAdvancer.stop(); stopWaiting(); } diff --git a/apps/openmw/mwgui/waitdialog.hpp b/apps/openmw/mwgui/waitdialog.hpp index 1cf05bb06f..e8f58c574d 100644 --- a/apps/openmw/mwgui/waitdialog.hpp +++ b/apps/openmw/mwgui/waitdialog.hpp @@ -1,12 +1,18 @@ #ifndef MWGUI_WAIT_DIALOG_H #define MWGUI_WAIT_DIALOG_H +#include "timeadvancer.hpp" + #include "windowbase.hpp" -#include "widgets.hpp" namespace MWGui { + namespace Widgets + { + class MWScrollBar; + } + class WaitDialogProgressBar : public WindowBase { public: @@ -34,7 +40,7 @@ namespace MWGui void bedActivated() { setCanRest(true); } - bool getSleeping() { return mWaiting && mSleeping; } + bool getSleeping() { return mTimeAdvancer.isRunning() && mSleeping; } void wakeUp(); void autosave(); @@ -47,12 +53,11 @@ namespace MWGui MyGUI::Button* mCancelButton; MWGui::Widgets::MWScrollBar* mHourSlider; - bool mWaiting; + TimeAdvancer mTimeAdvancer; bool mSleeping; - int mCurHour; int mHours; int mManualHours; // stores the hours to rest selected via slider - float mRemainingTime; + float mFadeTimeRemaining; int mInterruptAt; std::string mInterruptCreatureList; @@ -64,6 +69,10 @@ namespace MWGui void onCancelButtonClicked(MyGUI::Widget* sender); void onHourSliderChangedPosition(MyGUI::ScrollBar* sender, size_t position); + void onWaitingProgressChanged(int cur, int total); + void onWaitingInterrupted(); + void onWaitingFinished(); + void setCanRest(bool canRest); void startWaiting(int hoursToWait); diff --git a/apps/openmw/mwgui/widgets.cpp b/apps/openmw/mwgui/widgets.cpp index d7bc2c96e9..26fe315679 100644 --- a/apps/openmw/mwgui/widgets.cpp +++ b/apps/openmw/mwgui/widgets.cpp @@ -1,20 +1,22 @@ #include "widgets.hpp" -#include - #include #include -#include - #include #include #include +#include + #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwworld/esmstore.hpp" + +#include "controllers.hpp" + #undef min #undef max @@ -70,7 +72,7 @@ namespace MWGui if (mSkillValueWidget) { SkillValue::Type modified = mValue.getModified(), base = mValue.getBase(); - mSkillValueWidget->setCaption(boost::lexical_cast(modified)); + mSkillValueWidget->setCaption(MyGUI::utility::toString(modified)); if (modified > base) mSkillValueWidget->_setWidgetState("increased"); else if (modified < base) @@ -166,7 +168,7 @@ namespace MWGui if (mAttributeValueWidget) { int modified = mValue.getModified(), base = mValue.getBase(); - mAttributeValueWidget->setCaption(boost::lexical_cast(modified)); + mAttributeValueWidget->setCaption(MyGUI::utility::toString(modified)); if (modified > base) mAttributeValueWidget->_setWidgetState("increased"); else if (modified < base) @@ -428,9 +430,9 @@ namespace MWGui spellLine += formatter.str(); } else if ( displayType != ESM::MagicEffect::MDT_None ) { - spellLine += " " + boost::lexical_cast(mEffectParams.mMagnMin); + spellLine += " " + MyGUI::utility::toString(mEffectParams.mMagnMin); if (mEffectParams.mMagnMin != mEffectParams.mMagnMax) - spellLine += to + boost::lexical_cast(mEffectParams.mMagnMax); + spellLine += to + MyGUI::utility::toString(mEffectParams.mMagnMax); if ( displayType == ESM::MagicEffect::MDT_Percentage ) spellLine += pct; @@ -446,14 +448,14 @@ namespace MWGui // constant effects have no duration and no target if (!mEffectParams.mIsConstant) { - if (mEffectParams.mDuration >= 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) + if (mEffectParams.mDuration > 0 && !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration)) { - spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfor", "") + " " + boost::lexical_cast(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs); + spellLine += " " + MWBase::Environment::get().getWindowManager()->getGameSettingString("sfor", "") + " " + MyGUI::utility::toString(mEffectParams.mDuration) + ((mEffectParams.mDuration == 1) ? sec : secs); } if (mEffectParams.mArea > 0) { - spellLine += " #{sin} " + boost::lexical_cast(mEffectParams.mArea) + " #{sfootarea}"; + spellLine += " #{sin} " + MyGUI::utility::toString(mEffectParams.mArea) + " #{sfootarea}"; } // potions have no target diff --git a/apps/openmw/mwgui/widgets.hpp b/apps/openmw/mwgui/widgets.hpp index aae28a6862..09a3c11acf 100644 --- a/apps/openmw/mwgui/widgets.hpp +++ b/apps/openmw/mwgui/widgets.hpp @@ -1,9 +1,10 @@ #ifndef MWGUI_WIDGETS_H #define MWGUI_WIDGETS_H -#include "../mwworld/esmstore.hpp" #include "../mwmechanics/stat.hpp" -#include "controllers.hpp" + +#include +#include #include #include @@ -12,6 +13,7 @@ namespace MyGUI { class ImageBox; + class ControllerItem; } namespace MWBase diff --git a/apps/openmw/mwgui/windowbase.cpp b/apps/openmw/mwgui/windowbase.cpp index 9c12b04ef7..8fdcf6b207 100644 --- a/apps/openmw/mwgui/windowbase.cpp +++ b/apps/openmw/mwgui/windowbase.cpp @@ -1,9 +1,13 @@ #include "windowbase.hpp" +#include + +#include + #include "../mwbase/windowmanager.hpp" -#include "container.hpp" #include "../mwbase/environment.hpp" -#include "../mwgui/windowmanagerimp.hpp" + +#include "draganddrop.hpp" using namespace MWGui; diff --git a/apps/openmw/mwgui/windowmanagerimp.cpp b/apps/openmw/mwgui/windowmanagerimp.cpp index 48f28d300c..c74cff31c0 100644 --- a/apps/openmw/mwgui/windowmanagerimp.cpp +++ b/apps/openmw/mwgui/windowmanagerimp.cpp @@ -5,11 +5,22 @@ #include #include +#include -#include "MyGUI_UString.h" -#include "MyGUI_IPointer.h" -#include "MyGUI_ResourceImageSetPointer.h" -#include "MyGUI_TextureUtility.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include #include #include @@ -18,6 +29,8 @@ #include +#include + #include #include @@ -27,7 +40,9 @@ #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" +#include "../mwmechanics/stat.hpp" #include "../mwmechanics/npcstats.hpp" #include "../mwsound/soundmanagerimp.hpp" @@ -73,12 +88,17 @@ #include "itemwidget.hpp" #include "screenfader.hpp" #include "debugwindow.hpp" +#include "spellview.hpp" +#include "draganddrop.hpp" +#include "container.hpp" +#include "controllers.hpp" +#include "jailscreen.hpp" namespace MWGui { WindowManager::WindowManager( - const Compiler::Extensions& extensions, int fpsLevel, OEngine::Render::OgreRenderer *ogre, + const Compiler::Extensions& extensions, OEngine::Render::OgreRenderer *ogre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap) : mConsoleOnlyScripts(consoleOnlyScripts) @@ -125,6 +145,7 @@ namespace MWGui , mHitFader(NULL) , mScreenFader(NULL) , mDebugWindow(NULL) + , mJailScreen(NULL) , mTranslationDataStorage (translationDataStorage) , mCharGen(NULL) , mInputBlocker(NULL) @@ -149,7 +170,6 @@ namespace MWGui , mForceHidden(GW_None) , mAllowed(GW_ALL) , mRestAllowed(true) - , mShowFPSLevel(fpsLevel) , mFPS(0.0f) , mTriangleCount(0) , mBatchCount(0) @@ -179,6 +199,7 @@ namespace MWGui BookPage::registerMyGUIComponents (); ItemView::registerComponents(); ItemWidget::registerComponents(); + SpellView::registerComponents(); Gui::registerAllWidgets(); MyGUI::FactoryManager::getInstance().registerFactory("Controller"); @@ -250,7 +271,7 @@ namespace MWGui trackWindow(mDialogueWindow, "dialogue"); mContainerWindow = new ContainerWindow(mDragAndDrop); trackWindow(mContainerWindow, "container"); - mHud = new HUD(mCustomMarkers, mShowFPSLevel, mDragAndDrop); + mHud = new HUD(mCustomMarkers, Settings::Manager::getInt("fps", "HUD"), mDragAndDrop); mToolTips = new ToolTips(); mScrollWindow = new ScrollWindow(); mBookWindow = new BookWindow(); @@ -272,6 +293,7 @@ namespace MWGui mSoulgemDialog = new SoulgemDialog(mMessageBoxManager); mCompanionWindow = new CompanionWindow(mDragAndDrop, mMessageBoxManager); trackWindow(mCompanionWindow, "companion"); + mJailScreen = new JailScreen(); mWerewolfFader = new ScreenFader("textures\\werewolfoverlay.dds"); mBlindnessFader = new ScreenFader("black.png"); @@ -338,6 +360,12 @@ namespace MWGui WindowManager::~WindowManager() { + MyGUI::LanguageManager::getInstance().eventRequestTag.clear(); + MyGUI::PointerManager::getInstance().eventChangeMousePointer.clear(); + MyGUI::InputManager::getInstance().eventChangeKeyFocus.clear(); + MyGUI::ClipboardManager::getInstance().eventClipboardChanged.clear(); + MyGUI::ClipboardManager::getInstance().eventClipboardRequested.clear(); + delete mConsole; delete mMessageBoxManager; delete mHud; @@ -371,7 +399,6 @@ namespace MWGui delete mMerchantRepair; delete mRepair; delete mSoulgemDialog; - delete mCursorManager; delete mRecharge; delete mCompanionWindow; delete mHitFader; @@ -379,6 +406,9 @@ namespace MWGui delete mScreenFader; delete mBlindnessFader; delete mDebugWindow; + delete mJailScreen; + + delete mCursorManager; cleanupGarbage(); @@ -442,8 +472,10 @@ namespace MWGui mInventoryWindow->setTrading(false); mRecharge->setVisible(false); mVideoBackground->setVisible(false); + mJailScreen->setVisible(false); mHud->setVisible(mHudEnabled && mGuiEnabled); + mToolTips->setVisible(mGuiEnabled); bool gameMode = !isGuiMode(); @@ -586,18 +618,16 @@ namespace MWGui case GM_Journal: mJournal->setVisible(true); break; - case GM_LoadingWallpaper: - mHud->setVisible(false); - setCursorVisible(false); + case GM_Jail: + mJailScreen->setVisible(true); break; + case GM_LoadingWallpaper: case GM_Loading: - // Show the pinned windows - mMap->setVisible(mMap->pinned() && !(mForceHidden & GW_Map)); - mStatsWindow->setVisible(mStatsWindow->pinned() && !(mForceHidden & GW_Stats)); - mInventoryWindow->setVisible(mInventoryWindow->pinned() && !(mForceHidden & GW_Inventory)); - mSpellWindow->setVisible(mSpellWindow->pinned() && !(mForceHidden & GW_Magic)); - - setCursorVisible(false); + // Don't need to show anything here - GM_LoadingWallpaper covers everything else anyway, + // GM_Loading uses a texture of the last rendered frame so everything previously visible will be rendered. + mHud->setVisible(false); + mToolTips->setVisible(false); + setCursorVisible(mMessageBoxManager && mMessageBoxManager->isInteractiveMessageBox()); break; default: // Unsupported mode, switch back to game @@ -795,19 +825,31 @@ namespace MWGui } } - void WindowManager::messageBox (const std::string& message, const std::vector& buttons, enum MWGui::ShowInDialogueMode showInDialogueMode) + void WindowManager::interactiveMessageBox(const std::string &message, const std::vector &buttons, bool block) { - 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 && showInDialogueMode != MWGui::ShowInDialogueMode_Never) { - mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); - } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) { - mMessageBoxManager->createMessageBox(message); + mMessageBoxManager->createInteractiveMessageBox(message, buttons); + MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); + updateVisible(); + + if (block) + { + while (mMessageBoxManager->readPressedButton(false) == -1 + && !MWBase::Environment::get().getStateManager()->hasQuitRequest()) + { + mMessageBoxManager->onFrame(0.f); + MWBase::Environment::get().getInputManager()->update(0, true, false); + + mRendering->getWindow()->update(); } - } else { - mMessageBoxManager->createInteractiveMessageBox(message, buttons); - MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); - updateVisible(); + } + } + + void WindowManager::messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode) + { + if (getMode() == GM_Dialogue && showInDialogueMode != MWGui::ShowInDialogueMode_Never) { + mDialogueWindow->addMessageBox(MyGUI::LanguageManager::getInstance().replaceTags(message)); + } else if (showInDialogueMode != MWGui::ShowInDialogueMode_Only) { + mMessageBoxManager->createMessageBox(message); } } @@ -879,6 +921,7 @@ namespace MWGui mCompanionWindow->checkReferenceAvailable(); mConsole->checkReferenceAvailable(); mCompanionWindow->onFrame(); + mJailScreen->onFrame(frameDuration); mWerewolfFader->update(frameDuration); mBlindnessFader->update(frameDuration); @@ -1154,6 +1197,12 @@ namespace MWGui updateVisible(); } + void WindowManager::goToJail(int days) + { + pushGuiMode(MWGui::GM_Jail); + mJailScreen->goToJail(days); + } + void WindowManager::setSelectedSpell(const std::string& spellId, int successChancePercent) { mSelectedSpell = spellId; @@ -1457,6 +1506,8 @@ namespace MWGui void WindowManager::showSoulgemDialog(MWWorld::Ptr item) { mSoulgemDialog->show(item); + MWBase::Environment::get().getInputManager()->changeInputMode(isGuiMode()); + updateVisible(); } void WindowManager::frameStarted (float dt) @@ -1580,26 +1631,23 @@ namespace MWGui mMap->write(writer, progress); mQuickKeysMenu->write(writer); - progress.increaseProgress(); if (!mSelectedSpell.empty()) { writer.startRecord(ESM::REC_ASPL); writer.writeHNString("ID__", mSelectedSpell); writer.endRecord(ESM::REC_ASPL); - progress.increaseProgress(); } - for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) + for (std::vector::const_iterator it = mCustomMarkers.begin(); it != mCustomMarkers.end(); ++it) { writer.startRecord(ESM::REC_MARK); (*it).save(writer); writer.endRecord(ESM::REC_MARK); - progress.increaseProgress(); } } - void WindowManager::readRecord(ESM::ESMReader &reader, int32_t type) + void WindowManager::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_GMAP) mMap->readRecord(reader, type); @@ -1612,7 +1660,7 @@ namespace MWGui } else if (type == ESM::REC_MARK) { - CustomMarker marker; + ESM::CustomMarker marker; marker.load(reader); mCustomMarkers.addMarker(marker, false); } @@ -1691,15 +1739,9 @@ namespace MWGui void WindowManager::sizeVideo(int screenWidth, int screenHeight) { // Use black bars to correct aspect ratio + bool stretch = Settings::Manager::getBool("stretch menu background", "GUI"); mVideoBackground->setSize(screenWidth, screenHeight); - - double imageaspect = static_cast(mVideoWidget->getVideoWidth())/mVideoWidget->getVideoHeight(); - - int leftPadding = std::max(0.0, (screenWidth - screenHeight * imageaspect) / 2); - int topPadding = std::max(0.0, (screenHeight - screenWidth / imageaspect) / 2); - - mVideoWidget->setCoord(leftPadding, topPadding, - screenWidth - leftPadding*2, screenHeight - topPadding*2); + mVideoWidget->autoResize(stretch); } WindowModal* WindowManager::getCurrentModal() const @@ -1822,4 +1864,14 @@ namespace MWGui mDebugWindow->setVisible(!mDebugWindow->isVisible()); } + void WindowManager::cycleSpell(bool next) + { + mSpellWindow->cycle(next); + } + + void WindowManager::cycleWeapon(bool next) + { + mInventoryWindow->cycle(next); + } + } diff --git a/apps/openmw/mwgui/windowmanagerimp.hpp b/apps/openmw/mwgui/windowmanagerimp.hpp index 94d8a93db2..735b02d2d6 100644 --- a/apps/openmw/mwgui/windowmanagerimp.hpp +++ b/apps/openmw/mwgui/windowmanagerimp.hpp @@ -5,13 +5,15 @@ This class owns and controls all the MW specific windows in the GUI. It can enable/disable Gui mode, and is responsible for sending and retrieving information from the Gui. - - MyGUI should be initialized separately before creating instances of - this class. **/ +#include + #include "../mwbase/windowmanager.hpp" +#include +#include + #include "mapwindow.hpp" #include @@ -89,6 +91,7 @@ namespace MWGui class WindowModal; class ScreenFader; class DebugWindow; + class JailScreen; class WindowManager : public MWBase::WindowManager { @@ -96,7 +99,7 @@ namespace MWGui typedef std::pair Faction; typedef std::vector FactionList; - WindowManager(const Compiler::Extensions& extensions, int fpsLevel, + WindowManager(const Compiler::Extensions& extensions, OEngine::Render::OgreRenderer *mOgre, const std::string& logpath, const std::string& cacheDir, bool consoleOnlyScripts, Translation::Storage& translationDataStorage, ToUTF8::FromType encoding, bool exportFonts, const std::map& fallbackMap); @@ -127,6 +130,8 @@ namespace MWGui virtual void popGuiMode(); virtual void removeGuiMode(GuiMode mode); ///< can be anywhere in the stack + virtual void goToJail(int days); + virtual GuiMode getMode() const; virtual bool containsMode(GuiMode mode) const; @@ -238,9 +243,12 @@ namespace MWGui ///Gracefully attempts to exit the topmost GUI mode virtual void exitCurrentGuiMode(); - virtual void messageBox (const std::string& message, const std::vector& buttons = std::vector(), enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible); + virtual void messageBox (const std::string& message, enum MWGui::ShowInDialogueMode showInDialogueMode = MWGui::ShowInDialogueMode_IfPossible); virtual void staticMessageBox(const std::string& message); virtual void removeStaticMessageBox(); + virtual void interactiveMessageBox (const std::string& message, + const std::vector& buttons = std::vector(), bool block=false); + virtual int readPressedButton (); ///< returns the index of the pressed button or -1 if no button was pressed (->MessageBoxmanager->InteractiveMessageBox) virtual void onFrame (float frameDuration); @@ -303,7 +311,7 @@ namespace MWGui virtual void clear(); virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress); - virtual void readRecord (ESM::ESMReader& reader, int32_t type); + virtual void readRecord (ESM::ESMReader& reader, uint32_t type); virtual int countSavedGameRecords() const; /// Does the current stack of GUI-windows permit saving? @@ -338,6 +346,11 @@ namespace MWGui virtual void toggleDebugWindow(); + /// Cycle to next or previous spell + virtual void cycleSpell(bool next); + /// Cycle to next or previous weapon + virtual void cycleWeapon(bool next); + private: bool mConsoleOnlyScripts; @@ -395,6 +408,7 @@ namespace MWGui ScreenFader* mHitFader; ScreenFader* mScreenFader; DebugWindow* mDebugWindow; + JailScreen* mJailScreen; Translation::Storage& mTranslationDataStorage; @@ -442,7 +456,6 @@ namespace MWGui void updateVisible(); // Update visibility of all windows based on mode, shown and allowed settings - int mShowFPSLevel; float mFPS; unsigned int mTriangleCount; unsigned int mBatchCount; diff --git a/apps/openmw/mwgui/windowpinnablebase.cpp b/apps/openmw/mwgui/windowpinnablebase.cpp index f9bbca6653..a6984b5a48 100644 --- a/apps/openmw/mwgui/windowpinnablebase.cpp +++ b/apps/openmw/mwgui/windowpinnablebase.cpp @@ -1,5 +1,7 @@ #include "windowpinnablebase.hpp" +#include + #include "exposedwindow.hpp" namespace MWGui @@ -10,7 +12,7 @@ namespace MWGui ExposedWindow* window = mMainWidget->castType(); mPinButton = window->getSkinWidget ("Button"); - mPinButton->eventMouseButtonClick += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonClicked); + mPinButton->eventMouseButtonPressed += MyGUI::newDelegate(this, &WindowPinnableBase::onPinButtonPressed); MyGUI::Button* button = NULL; MyGUI::VectorWidgetPtr widgets = window->getSkinWidgetsByName("Action"); @@ -24,8 +26,11 @@ namespace MWGui button->eventMouseButtonDoubleClick += MyGUI::newDelegate(this, &WindowPinnableBase::onDoubleClick); } - void WindowPinnableBase::onPinButtonClicked(MyGUI::Widget* _sender) + void WindowPinnableBase::onPinButtonPressed(MyGUI::Widget* _sender, int left, int top, MyGUI::MouseButton id) { + if (id != MyGUI::MouseButton::Left) + return; + mPinned = !mPinned; if (mPinned) @@ -44,7 +49,7 @@ namespace MWGui void WindowPinnableBase::setPinned(bool pinned) { if (pinned != mPinned) - onPinButtonClicked(mPinButton); + onPinButtonPressed(mPinButton, 0, 0, MyGUI::MouseButton::Left); } void WindowPinnableBase::setPinButtonVisible(bool visible) diff --git a/apps/openmw/mwgui/windowpinnablebase.hpp b/apps/openmw/mwgui/windowpinnablebase.hpp index 8b7bbefaf9..c085bebf2e 100644 --- a/apps/openmw/mwgui/windowpinnablebase.hpp +++ b/apps/openmw/mwgui/windowpinnablebase.hpp @@ -16,7 +16,7 @@ namespace MWGui void setPinButtonVisible(bool visible); private: - void onPinButtonClicked(MyGUI::Widget* _sender); + void onPinButtonPressed(MyGUI::Widget* _sender, int left, int top, MyGUI::MouseButton id); void onDoubleClick(MyGUI::Widget* _sender); protected: diff --git a/apps/openmw/mwinput/inputmanagerimp.cpp b/apps/openmw/mwinput/inputmanagerimp.cpp index b0d2bfefff..fed864dfcc 100644 --- a/apps/openmw/mwinput/inputmanagerimp.cpp +++ b/apps/openmw/mwinput/inputmanagerimp.cpp @@ -286,26 +286,24 @@ namespace MWInput case A_QuickLoad: quickLoad(); break; + case A_CycleSpellLeft: + MWBase::Environment::get().getWindowManager()->cycleSpell(false); + break; + case A_CycleSpellRight: + MWBase::Environment::get().getWindowManager()->cycleSpell(true); + break; + case A_CycleWeaponLeft: + MWBase::Environment::get().getWindowManager()->cycleWeapon(false); + break; + case A_CycleWeaponRight: + MWBase::Environment::get().getWindowManager()->cycleWeapon(true); + break; } } } - void InputManager::update(float dt, bool disableControls, bool disableEvents) + void InputManager::updateCursorMode() { - mControlsDisabled = disableControls; - - mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); - - mInputManager->capture(disableEvents); - // inject some fake mouse movement to force updating MyGUI's widget states - MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); - - if (mControlsDisabled) - return; - - // update values of channels (as a result of pressed keys) - mInputBinder->update(dt); - bool grab = !MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_MainMenu) && MWBase::Environment::get().getWindowManager()->getMode() != MWGui::GM_Console; @@ -325,6 +323,28 @@ namespace MWInput { mInputManager->warpMouse(mMouseX, mMouseY); } + } + + void InputManager::update(float dt, bool disableControls, bool disableEvents) + { + mControlsDisabled = disableControls; + + mInputManager->setMouseVisible(MWBase::Environment::get().getWindowManager()->getCursorVisible()); + + mInputManager->capture(disableEvents); + // inject some fake mouse movement to force updating MyGUI's widget states + MyGUI::InputManager::getInstance().injectMouseMove( int(mMouseX), int(mMouseY), mMouseWheel); + + if (mControlsDisabled) + { + updateCursorMode(); + return; + } + + // update values of channels (as a result of pressed keys) + mInputBinder->update(dt); + + updateCursorMode(); // Disable movement in Gui mode if (!(MWBase::Environment::get().getWindowManager()->isGuiMode() @@ -397,8 +417,7 @@ namespace MWInput if (mControlSwitch["playerviewswitch"]) { - // work around preview mode toggle when pressing Alt+Tab - if (actionIsActive(A_TogglePOV) && !mInputManager->isModifierHeld(SDL_Keymod(KMOD_ALT))) { + if (actionIsActive(A_TogglePOV)) { if (mPreviewPOVDelay <= 0.5 && (mPreviewPOVDelay += dt) > 0.5) { @@ -732,8 +751,7 @@ namespace MWInput { mEngine.screenshot(); - std::vector empty; - MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved", empty); + MWBase::Environment::get().getWindowManager()->messageBox ("Screenshot saved"); } void InputManager::toggleInventory() @@ -894,6 +912,11 @@ namespace MWInput defaultKeyBindings[A_MoveRight] = SDL_SCANCODE_D; defaultKeyBindings[A_ToggleWeapon] = SDL_SCANCODE_F; defaultKeyBindings[A_ToggleSpell] = SDL_SCANCODE_R; + defaultKeyBindings[A_CycleSpellLeft] = SDL_SCANCODE_MINUS; + defaultKeyBindings[A_CycleSpellRight] = SDL_SCANCODE_EQUALS; + defaultKeyBindings[A_CycleWeaponLeft] = SDL_SCANCODE_LEFTBRACKET; + defaultKeyBindings[A_CycleWeaponRight] = SDL_SCANCODE_RIGHTBRACKET; + defaultKeyBindings[A_QuickKeysMenu] = SDL_SCANCODE_F1; defaultKeyBindings[A_Console] = SDL_SCANCODE_GRAVE; defaultKeyBindings[A_Run] = SDL_SCANCODE_LSHIFT; @@ -972,6 +995,10 @@ namespace MWInput descriptions[A_MoveRight] = "sRight"; descriptions[A_ToggleWeapon] = "sReady_Weapon"; descriptions[A_ToggleSpell] = "sReady_Magic"; + descriptions[A_CycleSpellLeft] = "sPrevSpell"; + descriptions[A_CycleSpellRight] = "sNextSpell"; + descriptions[A_CycleWeaponLeft] = "sPrevWeapon"; + descriptions[A_CycleWeaponRight] = "sNextWeapon"; descriptions[A_Console] = "sConsoleTitle"; descriptions[A_Run] = "sRun"; descriptions[A_Sneak] = "sCrouch_Sneak"; @@ -1032,6 +1059,10 @@ namespace MWInput ret.push_back(A_Use); ret.push_back(A_ToggleWeapon); ret.push_back(A_ToggleSpell); + ret.push_back(A_CycleSpellLeft); + ret.push_back(A_CycleSpellRight); + ret.push_back(A_CycleWeaponLeft); + ret.push_back(A_CycleWeaponRight); ret.push_back(A_AutoMove); ret.push_back(A_Jump); ret.push_back(A_Inventory); diff --git a/apps/openmw/mwinput/inputmanagerimp.hpp b/apps/openmw/mwinput/inputmanagerimp.hpp index 346e02ff95..851f249715 100644 --- a/apps/openmw/mwinput/inputmanagerimp.hpp +++ b/apps/openmw/mwinput/inputmanagerimp.hpp @@ -176,6 +176,8 @@ namespace MWInput void setPlayerControlsEnabled(bool enabled); + void updateCursorMode(); + private: void toggleMainMenu(); void toggleSpell(); diff --git a/apps/openmw/mwmechanics/activespells.cpp b/apps/openmw/mwmechanics/activespells.cpp index d87e225434..6e15449e19 100644 --- a/apps/openmw/mwmechanics/activespells.cpp +++ b/apps/openmw/mwmechanics/activespells.cpp @@ -195,7 +195,7 @@ namespace MWMechanics float magnitude = effectIt->mMagnitude; if (magnitude) - visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->second.mCasterActorId, magnitude, remainingTime); + visitor.visit(MWMechanics::EffectKey(effectIt->mEffectId, effectIt->mArg), name, it->first, it->second.mCasterActorId, magnitude, remainingTime, effectIt->mDuration); } } } @@ -229,6 +229,22 @@ namespace MWMechanics mSpellsChanged = true; } + void ActiveSpells::purgeEffect(short effectId, const std::string& sourceId) + { + for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) + { + for (std::vector::iterator effectIt = it->second.mEffects.begin(); + effectIt != it->second.mEffects.end();) + { + if (effectIt->mEffectId == effectId && it->first == sourceId) + effectIt = it->second.mEffects.erase(effectIt); + else + ++effectIt; + } + } + mSpellsChanged = true; + } + void ActiveSpells::purge(int casterActorId) { for (TContainer::iterator it = mSpells.begin(); it != mSpells.end(); ++it) diff --git a/apps/openmw/mwmechanics/activespells.hpp b/apps/openmw/mwmechanics/activespells.hpp index 9c1a5e613b..4f9d15d8c1 100644 --- a/apps/openmw/mwmechanics/activespells.hpp +++ b/apps/openmw/mwmechanics/activespells.hpp @@ -82,6 +82,9 @@ namespace MWMechanics /// Remove all active effects with this effect id void purgeEffect (short effectId); + /// Remove all active effects with this effect id and source id + void purgeEffect (short effectId, const std::string& sourceId); + /// Remove all active effects, if roll succeeds (for each effect) void purgeAll (float chance); diff --git a/apps/openmw/mwmechanics/actor.cpp b/apps/openmw/mwmechanics/actor.cpp new file mode 100644 index 0000000000..675bd160a3 --- /dev/null +++ b/apps/openmw/mwmechanics/actor.cpp @@ -0,0 +1,28 @@ +#include "actor.hpp" + +#include "character.hpp" + +namespace MWMechanics +{ + + Actor::Actor(const MWWorld::Ptr &ptr, MWRender::Animation *animation) + { + mCharacterController.reset(new CharacterController(ptr, animation)); + } + + void Actor::updatePtr(const MWWorld::Ptr &newPtr) + { + mCharacterController->updatePtr(newPtr); + } + + CharacterController* Actor::getCharacterController() + { + return mCharacterController.get(); + } + + AiState& Actor::getAiState() + { + return mAiState; + } + +} diff --git a/apps/openmw/mwmechanics/actor.hpp b/apps/openmw/mwmechanics/actor.hpp new file mode 100644 index 0000000000..846af14671 --- /dev/null +++ b/apps/openmw/mwmechanics/actor.hpp @@ -0,0 +1,42 @@ +#ifndef OPENMW_MECHANICS_ACTOR_H +#define OPENMW_MECHANICS_ACTOR_H + +#include + +#include "aistate.hpp" + +namespace MWRender +{ + class Animation; +} +namespace MWWorld +{ + class Ptr; +} + +namespace MWMechanics +{ + class CharacterController; + + /// @brief Holds temporary state for an actor that will be discarded when the actor leaves the scene. + class Actor + { + public: + Actor(const MWWorld::Ptr& ptr, MWRender::Animation* animation); + + /// Notify this actor of its new base object Ptr, use when the object changed cells + void updatePtr(const MWWorld::Ptr& newPtr); + + CharacterController* getCharacterController(); + + AiState& getAiState(); + + private: + std::auto_ptr mCharacterController; + + AiState mAiState; + }; + +} + +#endif diff --git a/apps/openmw/mwmechanics/actors.cpp b/apps/openmw/mwmechanics/actors.cpp index a3cfbfd49a..f8068e8000 100644 --- a/apps/openmw/mwmechanics/actors.cpp +++ b/apps/openmw/mwmechanics/actors.cpp @@ -4,6 +4,7 @@ #include #include +#include #include @@ -34,9 +35,19 @@ #include "aifollow.hpp" #include "aipursue.hpp" +#include "actor.hpp" +#include "summoning.hpp" +#include "combat.hpp" + namespace { +bool isConscious(const MWWorld::Ptr& ptr) +{ + const MWMechanics::CreatureStats& stats = ptr.getClass().getCreatureStats(ptr); + return !stats.isDead() && !stats.getKnockedDown(); +} + void adjustBoundItem (const std::string& item, bool bound, const MWWorld::Ptr& actor) { if (bound) @@ -70,6 +81,8 @@ bool disintegrateSlot (MWWorld::Ptr ptr, int slot, float disintegrate) if (charge == 0) return false; + // FIXME: charge should be a float, not int so that damage < 1 per frame can be applied. + // This was also a bug in the original engine. charge -= std::min(disintegrate, static_cast(charge)); @@ -100,8 +113,8 @@ public: , mCommanded(false){} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, - float magnitude, float remainingTime = -1) + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) { MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if ( ((key.mId == ESM::MagicEffect::CommandHumanoid && mActor.getClass().isNpc()) @@ -124,7 +137,7 @@ void adjustCommandedActor (const MWWorld::Ptr& actor) for (it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow && - dynamic_cast(*it)->isCommanded()) + static_cast(*it)->isCommanded()) { hasCommandPackage = true; break; @@ -160,30 +173,6 @@ void getRestorationPerHourOfSleep (const MWWorld::Ptr& ptr, float& health, float } } -void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) -{ - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); - if (!ptr.isEmpty()) - { - // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation - // plays though, which is a rather lame exploit in vanilla. - MWBase::Environment::get().getWorld()->deleteObject(ptr); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - } - else - { - // We didn't find the creature. It's probably in an inactive cell. - // Add to graveyard so we can delete it when the cell becomes active. - std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); - graveyard.push_back(creatureActorId); - } -} - } namespace MWMechanics @@ -198,8 +187,8 @@ namespace MWMechanics : mCreature(trappedCreature) {} virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, - float magnitude, float remainingTime = -1) + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1) { if (key.mId != ESM::MagicEffect::Soultrap) return; @@ -273,6 +262,43 @@ namespace MWMechanics calculateRestoration(ptr, duration); } + void Actors::updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, + MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance) + { + static const float fMaxHeadTrackDistance = MWBase::Environment::get().getWorld()->getStore().get() + .find("fMaxHeadTrackDistance")->getFloat(); + static const float fInteriorHeadTrackMult = MWBase::Environment::get().getWorld()->getStore().get() + .find("fInteriorHeadTrackMult")->getFloat(); + float maxDistance = fMaxHeadTrackDistance; + const ESM::Cell* currentCell = actor.getCell()->getCell(); + if (!currentCell->isExterior() && !(currentCell->mData.mFlags & ESM::Cell::QuasiEx)) + maxDistance *= fInteriorHeadTrackMult; + + const ESM::Position& actor1Pos = actor.getRefData().getPosition(); + const ESM::Position& actor2Pos = targetActor.getRefData().getPosition(); + float sqrDist = Ogre::Vector3(actor1Pos.pos).squaredDistance(Ogre::Vector3(actor2Pos.pos)); + + if (sqrDist > maxDistance*maxDistance) + return; + + if (targetActor.getClass().getCreatureStats(targetActor).isDead()) + return; + + // stop tracking when target is behind the actor + Ogre::Vector3 actorDirection (actor.getRefData().getBaseNode()->getOrientation().yAxis()); + Ogre::Vector3 targetDirection (Ogre::Vector3(actor2Pos.pos) - Ogre::Vector3(actor1Pos.pos)); + actorDirection.z = 0; + targetDirection.z = 0; + if (actorDirection.angleBetween(targetDirection) < Ogre::Degree(90) + && sqrDist <= sqrHeadTrackDistance + && MWBase::Environment::get().getWorld()->getLOS(actor, targetActor) // check LOS and awareness last as it's the most expensive function + && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(targetActor, actor)) + { + sqrHeadTrackDistance = sqrDist; + headTrackTarget = targetActor; + } + } + void Actors::engageCombat (const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer) { CreatureStats& creatureStats = actor1.getClass().getCreatureStats(actor1); @@ -290,10 +316,10 @@ namespace MWMechanics // pure water creatures won't try to fight with the target on the ground // except that creature is already hostile if ((againstPlayer || !creatureStats.getAiSequence().isInCombat()) - && ((actor1.getClass().canSwim(actor1) && !actor1.getClass().canWalk(actor1) // pure water creature - && !MWBase::Environment::get().getWorld()->isSwimming(actor2)) - || (!actor1.getClass().canSwim(actor1) && MWBase::Environment::get().getWorld()->isSwimming(actor2)))) // creature can't swim to target + && !MWMechanics::isEnvironmentCompatible(actor1, actor2)) // creature can't swim to target + { return; + } bool aggressive; @@ -335,7 +361,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) { - MWWorld::Ptr followTarget = dynamic_cast(*it)->getTarget(); + MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); if (followTarget.isEmpty()) continue; @@ -482,6 +508,9 @@ namespace MWMechanics bool wasDead = creatureStats.isDead(); + // FIXME: effect ticks should go into separate functions so they can be used with either + // magnitude (instant effect) or magnitude*duration + // attributes for(int i = 0;i < ESM::Attribute::Length;++i) { @@ -677,7 +706,22 @@ namespace MWMechanics // TODO: dirty flag for magic effects to avoid some unnecessary work below? + // any value of calm > 0 will stop the actor from fighting + if ((creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmHumanoid).getMagnitude() > 0 && ptr.getClass().isNpc()) + || (creatureStats.getMagicEffects().get(ESM::MagicEffect::CalmCreature).getMagnitude() > 0 && !ptr.getClass().isNpc())) + { + for (std::list::const_iterator it = creatureStats.getAiSequence().begin(); it != creatureStats.getAiSequence().end(); ) + { + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + it = creatureStats.getAiSequence().erase(it); + else + ++it; + } + } + // Update bound effects + // Note: in vanilla MW multiple bound items of the same type can be created by different spells. + // As these extra copies are kinda useless this may or may not be important. static std::map boundItemsMap; if (boundItemsMap.empty()) { @@ -722,131 +766,11 @@ namespace MWMechanics } } - // Update summon effects - static std::map summonMap; - if (summonMap.empty()) - { - 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"; - } - - std::map& creatureMap = creatureStats.getSummonedCreatureMap(); - for (std::map::iterator it = summonMap.begin(); it != summonMap.end(); ++it) - { - bool found = creatureMap.find(it->first) != creatureMap.end(); - int magnitude = creatureStats.getMagicEffects().get(it->first).getMagnitude(); - if (found != (magnitude > 0)) - { - if (magnitude > 0) - { - ESM::Position ipos = ptr.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; - - std::string creatureID = - MWBase::Environment::get().getWorld()->getStore().get().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().setPosition(ipos); - - MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); - - // Make the summoned creature follow its master and help in fights - AiFollow package(ptr.getCellRef().getRefId()); - summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); - int creatureActorId = summonedCreatureStats.getActorId(); - - MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); - - MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); - if (anim) - { - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_Start"); - if (fx) - anim->addEffect("meshes\\" + fx->mModel, -1, false); - } - - creatureMap.insert(std::make_pair(it->first, creatureActorId)); - } - } - else - { - // Effect has ended - std::map::iterator foundCreature = creatureMap.find(it->first); - cleanupSummonedCreature(creatureStats, foundCreature->second); - creatureMap.erase(foundCreature); - } - } - } - - for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); - if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) - { - // Purge the magic effect so a new creature can be summoned if desired - creatureStats.getActiveSpells().purgeEffect(it->first); - if (ptr.getClass().hasInventoryStore(ptr)) - ptr.getClass().getInventoryStore(ptr).purgeEffect(it->first); - - cleanupSummonedCreature(creatureStats, it->second); - creatureMap.erase(it++); - } - else - ++it; - } - - std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); - for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) - { - MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); - if (!ptr.isEmpty()) - { - it = graveyard.erase(it); - - const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() - .search("VFX_Summon_End"); - if (fx) - MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, - "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); - - MWBase::Environment::get().getWorld()->deleteObject(ptr); - } - else - ++it; - } + UpdateSummonedCreatures updateSummonedCreatures(ptr); + creatureStats.getActiveSpells().visitEffectSources(updateSummonedCreatures); + if (ptr.getClass().hasInventoryStore(ptr)) + ptr.getClass().getInventoryStore(ptr).visitEffectSources(updateSummonedCreatures); + updateSummonedCreatures.finish(); } void Actors::calculateNpcStatModifiers (const MWWorld::Ptr& ptr, float duration) @@ -869,13 +793,19 @@ namespace MWMechanics void Actors::updateDrowning(const MWWorld::Ptr& ptr, float duration) { - MWBase::World *world = MWBase::Environment::get().getWorld(); + PtrActorMap::iterator it = mActors.find(ptr); + if (it == mActors.end()) + return; + CharacterController* ctrl = it->second->getCharacterController(); + NpcStats &stats = ptr.getClass().getNpcStats(ptr); - if(world->isSubmerged(ptr) && - stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) + MWBase::World *world = MWBase::Environment::get().getWorld(); + bool knockedOutUnderwater = (ctrl->isKnockedOut() && world->isUnderwater(ptr.getCell(), Ogre::Vector3(ptr.getRefData().getPosition().pos))); + if((world->isSubmerged(ptr) || knockedOutUnderwater) + && stats.getMagicEffects().get(ESM::MagicEffect::WaterBreathing).getMagnitude() == 0) { float timeLeft = 0.0f; - if(stats.getFatigue().getCurrent() == 0) + if(knockedOutUnderwater) stats.setTimeToStartDrowning(0); else { @@ -1050,6 +980,7 @@ namespace MWMechanics // Reset factors to attack creatureStats.setAttacked(false); creatureStats.setAlarmed(false); + creatureStats.setAiSetting(CreatureStats::AI_Fight, ptr.getClass().getBaseFightRating(ptr)); // Update witness crime id npcStats.setCrimeId(-1); @@ -1070,14 +1001,14 @@ namespace MWMechanics removeActor(ptr); MWRender::Animation *anim = MWBase::Environment::get().getWorld()->getAnimation(ptr); - mActors.insert(std::make_pair(ptr, new CharacterController(ptr, anim))); + mActors.insert(std::make_pair(ptr, new Actor(ptr, anim))); if (updateImmediately) - mActors[ptr]->update(0); + mActors[ptr]->getCharacterController()->update(0); } void Actors::removeActor (const MWWorld::Ptr& ptr) { - PtrControllerMap::iterator iter = mActors.find(ptr); + PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) { delete iter->second; @@ -1087,20 +1018,20 @@ namespace MWMechanics void Actors::updateActor(const MWWorld::Ptr &old, const MWWorld::Ptr &ptr) { - PtrControllerMap::iterator iter = mActors.find(old); + PtrActorMap::iterator iter = mActors.find(old); if(iter != mActors.end()) { - CharacterController *ctrl = iter->second; + Actor *actor = iter->second; mActors.erase(iter); - ctrl->updatePtr(ptr); - mActors.insert(std::make_pair(ptr, ctrl)); + actor->updatePtr(ptr); + mActors.insert(std::make_pair(ptr, actor)); } } void Actors::dropActors (const MWWorld::CellStore *cellStore, const MWWorld::Ptr& ignore) { - PtrControllerMap::iterator iter = mActors.begin(); + PtrActorMap::iterator iter = mActors.begin(); while(iter != mActors.end()) { if(iter->first.getCell()==cellStore && iter->first != ignore) @@ -1118,18 +1049,11 @@ namespace MWMechanics if(!paused) { static float timerUpdateAITargets = 0; + static float timerUpdateHeadTrack = 0; // target lists get updated once every 1.0 sec if (timerUpdateAITargets >= 1.0f) timerUpdateAITargets = 0; - - // 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()); - } + if (timerUpdateHeadTrack >= 0.3f) timerUpdateHeadTrack = 0; MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -1141,8 +1065,10 @@ namespace MWMechanics // using higher values will make a quest in Bloodmoon harder or impossible to complete (bug #1876) const float sqrProcessingDistance = 7168*7168; + /// \todo move update logic to Actor class where appropriate + // AI and magic effects update - for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (!iter->first.getClass().getCreatureStats(iter->first).isDead()) { @@ -1156,24 +1082,37 @@ namespace MWMechanics if (iter->first != player) adjustCommandedActor(iter->first); - for(PtrControllerMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) { if (it->first == iter->first || iter->first == player) // player is not AI-controlled continue; engageCombat(iter->first, it->first, it->first == player); } } + if (timerUpdateHeadTrack == 0) + { + float sqrHeadTrackDistance = std::numeric_limits::max(); + MWWorld::Ptr headTrackTarget; + + for(PtrActorMap::iterator it(mActors.begin()); it != mActors.end(); ++it) + { + if (it->first == iter->first) + continue; + updateHeadTracking(iter->first, it->first, headTrackTarget, sqrHeadTrackDistance); + } + iter->second->getCharacterController()->setHeadTrackTarget(headTrackTarget); + } if (iter->first.getClass().isNpc() && iter->first != player) updateCrimePersuit(iter->first, duration); if (iter->first != player) - iter->first.getClass().getCreatureStats(iter->first).getAiSequence().execute(iter->first,iter->second->getAiState(), duration); - - CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); - if(!stats.isDead()) { - if (stats.getAiSequence().isInCombat()) hostilesCount++; + CreatureStats &stats = iter->first.getClass().getCreatureStats(iter->first); + if (isConscious(iter->first)) + stats.getAiSequence().execute(iter->first,iter->second->getAiState(), duration); + + if (stats.getAiSequence().isInCombat() && !stats.isDead()) hostilesCount++; } } @@ -1183,18 +1122,19 @@ namespace MWMechanics } timerUpdateAITargets += duration; + timerUpdateHeadTrack += duration; // 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(); + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + iter->second->getCharacterController()->updateContinuousVfx(); // Animation/movement update CharacterController* playerCharacter = NULL; - for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first != player && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(iter->first.getRefData().getPosition().pos)) @@ -1203,22 +1143,22 @@ namespace MWMechanics if (iter->first.getClass().getCreatureStats(iter->first).getMagicEffects().get( ESM::MagicEffect::Paralyze).getMagnitude() > 0) - iter->second->skipAnim(); + iter->second->getCharacterController()->skipAnim(); // Handle player last, in case a cell transition occurs by casting a teleportation spell // (would invalidate the iterator) if (iter->first.getCellRef().getRefId() == "player") { - playerCharacter = iter->second; + playerCharacter = iter->second->getCharacterController(); continue; } - iter->second->update(duration); + iter->second->getCharacterController()->update(duration); } if (playerCharacter) playerCharacter->update(duration); - for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); @@ -1276,7 +1216,7 @@ namespace MWMechanics bool detected = false; - for (PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for (PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { if (iter->first == player) // not the player continue; @@ -1319,32 +1259,32 @@ namespace MWMechanics void Actors::killDeadActors() { - for(PtrControllerMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) + for(PtrActorMap::iterator iter(mActors.begin()); iter != mActors.end(); ++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); if(!stats.isDead()) { - if(iter->second->isDead()) + if(iter->second->getCharacterController()->isDead()) { // Actor has been resurrected. Notify the CharacterController and re-enable collision. MWBase::Environment::get().getWorld()->enableActorCollision(iter->first, true); - iter->second->resurrect(); + iter->second->getCharacterController()->resurrect(); } if(!stats.isDead()) continue; } - if (iter->second->kill()) + if (iter->second->getCharacterController()->kill()) { iter->first.getClass().getCreatureStats(iter->first).notifyDied(); ++mDeathCount[Misc::StringUtils::lowerCase(iter->first.getCellRef().getRefId())]; // Make sure spell effects with CasterLinked flag are removed - for (PtrControllerMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) + for (PtrActorMap::iterator iter2(mActors.begin());iter2 != mActors.end();++iter2) { MWMechanics::ActiveSpells& spells = iter2->first.getClass().getCreatureStats(iter2->first).getActiveSpells(); spells.purge(stats.getActorId()); @@ -1373,7 +1313,7 @@ namespace MWMechanics void Actors::restoreDynamicStats(bool sleep) { - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) restoreDynamicStats(iter->first, sleep); } @@ -1405,35 +1345,35 @@ namespace MWMechanics void Actors::forceStateUpdate(const MWWorld::Ptr & ptr) { - PtrControllerMap::iterator iter = mActors.find(ptr); + PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second->forceStateUpdate(); + iter->second->getCharacterController()->forceStateUpdate(); } void Actors::playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number) { - PtrControllerMap::iterator iter = mActors.find(ptr); + PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second->playGroup(groupName, mode, number); + iter->second->getCharacterController()->playGroup(groupName, mode, number); } void Actors::skipAnimation(const MWWorld::Ptr& ptr) { - PtrControllerMap::iterator iter = mActors.find(ptr); + PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - iter->second->skipAnim(); + iter->second->getCharacterController()->skipAnim(); } bool Actors::checkAnimationPlaying(const MWWorld::Ptr& ptr, const std::string& groupName) { - PtrControllerMap::iterator iter = mActors.find(ptr); + PtrActorMap::iterator iter = mActors.find(ptr); if(iter != mActors.end()) - return iter->second->isAnimPlaying(groupName); + return iter->second->getCharacterController()->isAnimPlaying(groupName); return false; } void Actors::getObjectsInRange(const Ogre::Vector3& position, float radius, std::vector& out) { - for (PtrControllerMap::iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + for (PtrActorMap::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); @@ -1443,7 +1383,7 @@ namespace MWMechanics std::list Actors::getActorsFollowing(const MWWorld::Ptr& actor) { std::list list; - for(PtrControllerMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) { const MWWorld::Class &cls = iter->first.getClass(); CreatureStats &stats = cls.getCreatureStats(iter->first); @@ -1455,7 +1395,7 @@ namespace MWMechanics { if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) { - MWWorld::Ptr followTarget = dynamic_cast(*it)->getTarget(); + MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); if (followTarget.isEmpty()) continue; if (followTarget == actor) @@ -1470,6 +1410,36 @@ namespace MWMechanics return list; } + std::list Actors::getActorsFollowingIndices(const MWWorld::Ptr &actor) + { + std::list list; + for(PtrActorMap::iterator iter(mActors.begin());iter != mActors.end();++iter) + { + const MWWorld::Class &cls = iter->first.getClass(); + CreatureStats &stats = cls.getCreatureStats(iter->first); + if (stats.isDead()) + continue; + + // An actor counts as following if AiFollow is the current AiPackage, or there are only Combat packages before the AiFollow package + for (std::list::const_iterator it = stats.getAiSequence().begin(); it != stats.getAiSequence().end(); ++it) + { + if ((*it)->getTypeId() == MWMechanics::AiPackage::TypeIdFollow) + { + MWWorld::Ptr followTarget = static_cast(*it)->getTarget(); + if (followTarget.isEmpty()) + continue; + if (followTarget == actor) + list.push_back(static_cast(*it)->getFollowIndex()); + else + break; + } + else if ((*it)->getTypeId() != MWMechanics::AiPackage::TypeIdCombat) + break; + } + } + return list; + } + std::list Actors::getActorsFighting(const MWWorld::Ptr& actor) { std::list list; std::vector neighbors; @@ -1496,11 +1466,9 @@ namespace MWMechanics writer.writeHNT ("COUN", it->second); } writer.endRecord(ESM::REC_DCOU); - - listener.increaseProgress(1); } - void Actors::readRecord (ESM::ESMReader& reader, int32_t type) + void Actors::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type == ESM::REC_DCOU) { @@ -1516,7 +1484,7 @@ namespace MWMechanics void Actors::clear() { - PtrControllerMap::iterator it(mActors.begin()); + PtrActorMap::iterator it(mActors.begin()); for (; it != mActors.end(); ++it) { delete it->second; @@ -1533,4 +1501,32 @@ namespace MWMechanics if (ptr.getClass().isNpc()) calculateNpcStatModifiers(ptr, 0.f); } + + bool Actors::isReadyToBlock(const MWWorld::Ptr &ptr) const + { + PtrActorMap::const_iterator it = mActors.find(ptr); + if (it == mActors.end()) + return false; + + return it->second->getCharacterController()->isReadyToBlock(); + } + + void Actors::fastForwardAi() + { + if (!MWBase::Environment::get().getMechanicsManager()->isAIActive()) + return; + + // making a copy since fast-forward could move actor to a different cell and invalidate the mActors iterator + PtrActorMap map = mActors; + for (PtrActorMap::iterator it = map.begin(); it != map.end(); ++it) + { + MWWorld::Ptr ptr = it->first; + if (ptr == MWBase::Environment::get().getWorld()->getPlayerPtr() + || !isConscious(ptr) + || ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + continue; + MWMechanics::AiSequence& seq = ptr.getClass().getCreatureStats(ptr).getAiSequence(); + seq.fastForward(ptr, it->second->getAiState()); + } + } } diff --git a/apps/openmw/mwmechanics/actors.hpp b/apps/openmw/mwmechanics/actors.hpp index 0ccfaad78a..70f1b47d91 100644 --- a/apps/openmw/mwmechanics/actors.hpp +++ b/apps/openmw/mwmechanics/actors.hpp @@ -5,8 +5,8 @@ #include #include #include +#include -#include "character.hpp" #include "movement.hpp" #include "../mwbase/world.hpp" @@ -23,6 +23,8 @@ namespace MWWorld namespace MWMechanics { + class Actor; + class Actors { std::map mDeathCount; @@ -51,10 +53,10 @@ namespace MWMechanics Actors(); ~Actors(); - typedef std::map PtrControllerMap; + typedef std::map PtrActorMap; - PtrControllerMap::const_iterator begin() { return mActors.begin(); } - PtrControllerMap::const_iterator end() { return mActors.end(); } + PtrActorMap::const_iterator begin() { return mActors.begin(); } + PtrActorMap::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) @@ -89,6 +91,9 @@ namespace MWMechanics */ void engageCombat(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2, bool againstPlayer); + void updateHeadTracking(const MWWorld::Ptr& actor, const MWWorld::Ptr& targetActor, + MWWorld::Ptr& headTrackTarget, float& sqrHeadTrackDistance); + void restoreDynamicStats(bool sleep); ///< If the player is sleeping, this should be called every hour. @@ -97,6 +102,9 @@ namespace MWMechanics int getHoursToRest(const MWWorld::Ptr& ptr) const; ///< Calculate how many hours the given actor needs to rest in order to be fully healed + void fastForwardAi(); + ///< Simulate the passing of time + int countDeaths (const std::string& id) const; ///< Return the number of deaths for actors with the given ID. @@ -112,18 +120,23 @@ namespace MWMechanics /**ie AiFollow is active and the target is the actor **/ std::list getActorsFollowing(const MWWorld::Ptr& actor); + /// Get the list of AiFollow::mFollowIndex for all actors following this target + std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); + ///Returns the list of actors which are fighting the given actor /**ie AiCombat is active and the target is the actor **/ std::list getActorsFighting(const MWWorld::Ptr& actor); void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; - void readRecord (ESM::ESMReader& reader, int32_t type); + void readRecord (ESM::ESMReader& reader, uint32_t type); void clear(); // Clear death counter + bool isReadyToBlock(const MWWorld::Ptr& ptr) const; + private: - PtrControllerMap mActors; + PtrActorMap mActors; }; } diff --git a/apps/openmw/mwmechanics/aicombat.cpp b/apps/openmw/mwmechanics/aicombat.cpp index 6249606322..f2b125add6 100644 --- a/apps/openmw/mwmechanics/aicombat.cpp +++ b/apps/openmw/mwmechanics/aicombat.cpp @@ -23,6 +23,7 @@ #include "character.hpp" // fixme: for getActiveWeapon #include "aicombataction.hpp" +#include "combat.hpp" namespace { @@ -57,7 +58,7 @@ namespace // magnitude of pits/obstacles is defined by PATHFIND_Z_REACH bool checkWayIsClear(const Ogre::Vector3& from, const Ogre::Vector3& to, float offsetXY) { - if((to - from).length() >= PATHFIND_CAUTION_DIST || abs(from.z - to.z) <= PATHFIND_Z_REACH) + if((to - from).length() >= PATHFIND_CAUTION_DIST || std::abs(from.z - to.z) <= PATHFIND_Z_REACH) { Ogre::Vector3 dir = to - from; dir.z = 0; @@ -68,7 +69,7 @@ namespace // cast up-down ray and find height in world space of hit float h = _from.z - MWBase::Environment::get().getWorld()->getDistToNearestRayHit(_from, -Ogre::Vector3::UNIT_Z, verticalOffset + PATHFIND_Z_REACH + 1); - if(abs(from.z - h) <= PATHFIND_Z_REACH) + if(std::abs(from.z - h) <= PATHFIND_Z_REACH) return true; } @@ -206,12 +207,8 @@ namespace MWMechanics const MWWorld::Class& actorClass = actor.getClass(); MWBase::World* world = MWBase::Environment::get().getWorld(); - if (!actorClass.isNpc() && - // 1. pure water creature and Player moved out of water - ((target == world->getPlayerPtr() && - actorClass.canSwim(actor) && !actor.getClass().canWalk(actor) && !world->isSwimming(target)) - // 2. creature can't swim to target - || (!actorClass.canSwim(actor) && world->isSwimming(target)))) + // can't fight if attacker can't go where target is. E.g. A fish can't attack person on land. + if (!actorClass.isNpc() && !MWMechanics::isEnvironmentCompatible(actor, target)) { actorClass.getCreatureStats(actor).setAttackingOrSpell(false); return true; @@ -300,6 +297,14 @@ namespace MWMechanics //Update with period = tReaction + // Stop attacking if target is not seen + if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 + || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) + { + movement.mPosition[1] = movement.mPosition[0] = 0; + return false; // TODO: run away instead of doing nothing + } + timerReact = 0; const MWWorld::CellStore*& currentCell = storage.mCell; bool cellChange = currentCell && (actor.getCell() != currentCell); @@ -326,10 +331,6 @@ namespace MWMechanics actionCooldown = currentAction->getActionCooldown(); } - // Stop attacking if target is not seen - if (!MWBase::Environment::get().getMechanicsManager()->awarenessCheck(target, actor)) - return true; - if (currentAction.get()) currentAction->getCombatRange(rangeAttack, rangeFollow); @@ -576,7 +577,7 @@ namespace MWMechanics buildNewPath(actor, target); //may fail to build a path, check before use //delete visited path node - mPathFinder.checkWaypoint(pos.pos[0],pos.pos[1],pos.pos[2]); + mPathFinder.checkPathCompleted(pos.pos[0],pos.pos[1],pos.pos[2]); // This works on the borders between the path grid and areas with no waypoints. if(inLOS && mPathFinder.getPath().size() > 1) diff --git a/apps/openmw/mwmechanics/aicombataction.cpp b/apps/openmw/mwmechanics/aicombataction.cpp index 6b4ede3059..175b980011 100644 --- a/apps/openmw/mwmechanics/aicombataction.cpp +++ b/apps/openmw/mwmechanics/aicombataction.cpp @@ -201,12 +201,16 @@ namespace MWMechanics return 0.f; const ESM::Enchantment* enchantment = MWBase::Environment::get().getWorld()->getStore().get().find(ptr.getClass().getEnchantment(ptr)); + if (enchantment->mData.mType == ESM::Enchantment::CastOnce) { return rateEffects(enchantment->mEffects, actor, target); } else + { + //if (!ptr.getClass().canBeEquipped(ptr, actor)) return 0.f; + } } float rateEffect(const ESM::ENAMstruct &effect, const MWWorld::Ptr &actor, const MWWorld::Ptr &target) @@ -290,7 +294,10 @@ namespace MWMechanics // Effect doesn't heal more than we need, *or* we are below 1/2 health if (current.getModified() - current.getCurrent() > toHeal || current.getCurrent() < current.getModified()*0.5) - return 10000.f * priority; + { + return 10000.f * priority + - (toHeal - (current.getModified()-current.getCurrent())); // prefer the most fitting potion + } else return -10000.f * priority; // Save for later } @@ -454,6 +461,11 @@ namespace MWMechanics float bestActionRating = 0.f; // Default to hand-to-hand combat boost::shared_ptr bestAction (new ActionWeapon(MWWorld::Ptr())); + if (actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf()) + { + bestAction->prepare(actor); + return bestAction; + } if (actor.getClass().hasInventoryStore(actor)) { diff --git a/apps/openmw/mwmechanics/aifollow.cpp b/apps/openmw/mwmechanics/aifollow.cpp index f309dc7402..161f4bb905 100644 --- a/apps/openmw/mwmechanics/aifollow.cpp +++ b/apps/openmw/mwmechanics/aifollow.cpp @@ -6,54 +6,100 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/mechanicsmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" #include "creaturestats.hpp" #include "movement.hpp" #include +#include #include "steering.hpp" -MWMechanics::AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) +namespace MWMechanics +{ + + +struct AiFollowStorage : AiTemporaryBase +{ + float mTimer; + + AiFollowStorage() : mTimer(0.f) {} +}; + +int AiFollow::mFollowIndexCounter = 0; + +AiFollow::AiFollow(const std::string &actorId,float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mCellId(""), mActorId(-1) +, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++), mActive(false) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) +AiFollow::AiFollow(const std::string &actorId,const std::string &cellId,float duration, float x, float y, float z) : mAlwaysFollow(false), mCommanded(false), mRemainingDuration(duration), mX(x), mY(y), mZ(z) -, mActorRefId(actorId), mCellId(cellId), mActorId(-1) +, mActorRefId(actorId), mCellId(cellId), mActorId(-1), mFollowIndex(mFollowIndexCounter++), mActive(false) { } -MWMechanics::AiFollow::AiFollow(const std::string &actorId, bool commanded) +AiFollow::AiFollow(const std::string &actorId, bool commanded) : mAlwaysFollow(true), mCommanded(commanded), mRemainingDuration(0), mX(0), mY(0), mZ(0) -, mActorRefId(actorId), mCellId(""), mActorId(-1) +, mActorRefId(actorId), mCellId(""), mActorId(-1), mFollowIndex(mFollowIndexCounter++), mActive(false) { + } -MWMechanics::AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) +AiFollow::AiFollow(const ESM::AiSequence::AiFollow *follow) : mAlwaysFollow(follow->mAlwaysFollow), mRemainingDuration(follow->mRemainingDuration) , mX(follow->mData.mX), mY(follow->mData.mY), mZ(follow->mData.mZ) , mActorRefId(follow->mTargetId), mActorId(-1), mCellId(follow->mCellId) - , mCommanded(follow->mCommanded) + , mCommanded(follow->mCommanded), mFollowIndex(mFollowIndexCounter++), mActive(follow->mActive) { } -bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration) +bool AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, float duration) { MWWorld::Ptr target = getTarget(); if (target.isEmpty() || !target.getRefData().getCount() || !target.getRefData().isEnabled() // Really we should be checking whether the target is currently registered // with the MechanicsManager ) - return true; //Target doesn't exist + return false; // Target is not here right now, wait for it to return actor.getClass().getCreatureStats(actor).setDrawState(DrawState_Nothing); + // AiFollow requires the target to be in range and within sight for the initial activation + if (!mActive) + { + AiFollowStorage& storage = state.get(); + storage.mTimer -= duration; + + if (storage.mTimer < 0) + { + if (Ogre::Vector3(actor.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(target.getRefData().getPosition().pos)) + < 500*500 + && MWBase::Environment::get().getWorld()->getLOS(actor, target)) + mActive = true; + storage.mTimer = 0.5f; + } + } + if (!mActive) + return false; + ESM::Position pos = actor.getRefData().getPosition(); //position of the actor + float followDistance = 180; + // When there are multiple actors following the same target, they form a group with each group member at 180*(i+1) distance to the target + int i=0; + std::list followers = MWBase::Environment::get().getMechanicsManager()->getActorsFollowingIndices(target); + followers.sort(); + for (std::list::iterator it = followers.begin(); it != followers.end(); ++it) + { + if (*it == mFollowIndex) + followDistance *= (i+1); + ++i; + } + if(!mAlwaysFollow) //Update if you only follow for a bit { //Check if we've run out of time @@ -66,7 +112,7 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, 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) //Close-ish to final position + (pos.pos[2]-mZ)*(pos.pos[2]-mZ) < followDistance*followDistance) //Close-ish to final position { if(actor.getCell()->isExterior()) //Outside? { @@ -84,9 +130,17 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, //Set the target destination from the actor ESM::Pathgrid::Point dest = target.getRefData().getPosition().pos; - if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < 100) //Stop when you get close + if(distance(dest, pos.pos[0], pos.pos[1], pos.pos[2]) < followDistance) //Stop when you get close + { actor.getClass().getMovementSettings(actor).mPosition[1] = 0; - else { + + // turn towards target anyway + float directionX = target.getRefData().getPosition().pos[0] - actor.getRefData().getPosition().pos[0]; + float directionY = target.getRefData().getPosition().pos[1] - actor.getRefData().getPosition().pos[1]; + zTurn(actor, Ogre::Math::ATan2(directionX,directionY), Ogre::Degree(5)); + } + else + { pathTo(actor, dest, duration); //Go to the destination } @@ -99,27 +153,27 @@ bool MWMechanics::AiFollow::execute (const MWWorld::Ptr& actor, AiState& state, return false; } -std::string MWMechanics::AiFollow::getFollowedActor() +std::string AiFollow::getFollowedActor() { return mActorRefId; } -MWMechanics::AiFollow *MWMechanics::AiFollow::clone() const +AiFollow *MWMechanics::AiFollow::clone() const { return new AiFollow(*this); } -int MWMechanics::AiFollow::getTypeId() const +int AiFollow::getTypeId() const { return TypeIdFollow; } -bool MWMechanics::AiFollow::isCommanded() const +bool AiFollow::isCommanded() const { return mCommanded; } -void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const +void AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr follow(new ESM::AiSequence::AiFollow()); follow->mData.mX = mX; @@ -130,6 +184,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co follow->mCellId = mCellId; follow->mAlwaysFollow = mAlwaysFollow; follow->mCommanded = mCommanded; + follow->mActive = mActive; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Follow; @@ -137,7 +192,7 @@ void MWMechanics::AiFollow::writeState(ESM::AiSequence::AiSequence &sequence) co sequence.mPackages.push_back(package); } -MWWorld::Ptr MWMechanics::AiFollow::getTarget() +MWWorld::Ptr AiFollow::getTarget() { if (mActorId == -2) return MWWorld::Ptr(); @@ -159,3 +214,10 @@ MWWorld::Ptr MWMechanics::AiFollow::getTarget() else return MWWorld::Ptr(); } + +int AiFollow::getFollowIndex() const +{ + return mFollowIndex; +} + +} diff --git a/apps/openmw/mwmechanics/aifollow.hpp b/apps/openmw/mwmechanics/aifollow.hpp index d5dd42826d..68a1f0ea5f 100644 --- a/apps/openmw/mwmechanics/aifollow.hpp +++ b/apps/openmw/mwmechanics/aifollow.hpp @@ -46,6 +46,8 @@ namespace MWMechanics bool isCommanded() const; + int getFollowIndex() const; + private: /// This will make the actor always follow. /** Thus ignoring mDuration and mX,mY,mZ (used for summoned creatures). **/ @@ -58,6 +60,10 @@ namespace MWMechanics std::string mActorRefId; int mActorId; std::string mCellId; + bool mActive; // have we spotted the target? + int mFollowIndex; + + static int mFollowIndexCounter; }; } #endif diff --git a/apps/openmw/mwmechanics/aipackage.hpp b/apps/openmw/mwmechanics/aipackage.hpp index f1c9ec7d25..80b48fc378 100644 --- a/apps/openmw/mwmechanics/aipackage.hpp +++ b/apps/openmw/mwmechanics/aipackage.hpp @@ -3,7 +3,6 @@ #include "pathfinding.hpp" #include -#include "../mwbase/world.hpp" #include "obstacle.hpp" #include "aistate.hpp" @@ -64,6 +63,9 @@ namespace MWMechanics virtual void writeState (ESM::AiSequence::AiSequence& sequence) const {} + /// Simulates the passing of time + virtual void fastForward(const MWWorld::Ptr& actor, AiState& state) {} + protected: /// Causes the actor to attempt to walk to the specified location /** \return If the actor has arrived at his destination **/ diff --git a/apps/openmw/mwmechanics/aipursue.cpp b/apps/openmw/mwmechanics/aipursue.cpp index 0c3de96432..8c31a10db6 100644 --- a/apps/openmw/mwmechanics/aipursue.cpp +++ b/apps/openmw/mwmechanics/aipursue.cpp @@ -43,6 +43,10 @@ bool AiPursue::execute (const MWWorld::Ptr& actor, AiState& state, float duratio ) return true; //Target doesn't exist + if (target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Invisibility).getMagnitude() > 0 + || target.getClass().getCreatureStats(target).getMagicEffects().get(ESM::MagicEffect::Chameleon).getMagnitude() > 75) + return true; + if(target.getClass().getCreatureStats(target).isDead()) return true; diff --git a/apps/openmw/mwmechanics/aisequence.cpp b/apps/openmw/mwmechanics/aisequence.cpp index 990145c8d5..533bcd17cc 100644 --- a/apps/openmw/mwmechanics/aisequence.cpp +++ b/apps/openmw/mwmechanics/aisequence.cpp @@ -86,15 +86,14 @@ std::list::const_iterator AiSequence::end() const return mPackages.end(); } -void AiSequence::erase(std::list::const_iterator package) +std::list::const_iterator AiSequence::erase(std::list::const_iterator package) { // Not sure if manually terminated packages should trigger mDone, probably not? for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ++it) { if (package == it) { - mPackages.erase(it); - return; + return mPackages.erase(it); } } throw std::runtime_error("can't find package to erase"); @@ -126,19 +125,23 @@ bool AiSequence::isInCombat(const MWWorld::Ptr &actor) const void AiSequence::stopCombat() { - while (getTypeId() == AiPackage::TypeIdCombat) + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ) { - delete *mPackages.begin(); - mPackages.erase (mPackages.begin()); + if ((*it)->getTypeId() == AiPackage::TypeIdCombat) + it = mPackages.erase(it); + else + ++it; } } void AiSequence::stopPursuit() { - while (getTypeId() == AiPackage::TypeIdPursue) + for(std::list::iterator it = mPackages.begin(); it != mPackages.end(); ) { - delete *mPackages.begin(); - mPackages.erase (mPackages.begin()); + if ((*it)->getTypeId() == AiPackage::TypeIdPursue) + it = mPackages.erase(it); + else + ++it; } } @@ -149,8 +152,7 @@ bool AiSequence::isPackageDone() const void AiSequence::execute (const MWWorld::Ptr& actor, AiState& state,float duration) { - if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr() - && !actor.getClass().getCreatureStats(actor).getKnockedDown()) + if(actor != MWBase::Environment::get().getWorld()->getPlayerPtr()) { if (!mPackages.empty()) { @@ -339,49 +341,49 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) case ESM::AiSequence::Ai_Wander: { MWMechanics::AiWander* wander = new AiWander( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(wander); break; } case ESM::AiSequence::Ai_Travel: { MWMechanics::AiTravel* travel = new AiTravel( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(travel); break; } case ESM::AiSequence::Ai_Escort: { MWMechanics::AiEscort* escort = new AiEscort( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(escort); break; } case ESM::AiSequence::Ai_Follow: { MWMechanics::AiFollow* follow = new AiFollow( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(follow); break; } case ESM::AiSequence::Ai_Activate: { MWMechanics::AiActivate* activate = new AiActivate( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(activate); break; } case ESM::AiSequence::Ai_Combat: { MWMechanics::AiCombat* combat = new AiCombat( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(combat); break; } case ESM::AiSequence::Ai_Pursue: { MWMechanics::AiPursue* pursue = new AiPursue( - dynamic_cast(it->mPackage)); + static_cast(it->mPackage)); mPackages.push_back(pursue); break; } @@ -391,4 +393,13 @@ void AiSequence::readState(const ESM::AiSequence::AiSequence &sequence) } } +void AiSequence::fastForward(const MWWorld::Ptr& actor, AiState& state) +{ + if (!mPackages.empty()) + { + MWMechanics::AiPackage* package = mPackages.front(); + package->fastForward(actor, state); + } +} + } // namespace MWMechanics diff --git a/apps/openmw/mwmechanics/aisequence.hpp b/apps/openmw/mwmechanics/aisequence.hpp index 25605ff447..e43ce72f1f 100644 --- a/apps/openmw/mwmechanics/aisequence.hpp +++ b/apps/openmw/mwmechanics/aisequence.hpp @@ -61,7 +61,7 @@ namespace MWMechanics std::list::const_iterator begin() const; std::list::const_iterator end() const; - void erase (std::list::const_iterator package); + std::list::const_iterator erase (std::list::const_iterator package); /// Returns currently executing AiPackage type /** \see enum AiPackage::TypeId **/ @@ -97,6 +97,9 @@ namespace MWMechanics /// Execute current package, switching if needed. void execute (const MWWorld::Ptr& actor, MWMechanics::AiState& state, float duration); + /// Simulate the passing of time using the currently active AI package + void fastForward(const MWWorld::Ptr &actor, AiState &state); + /// Remove all packages. void clear(); diff --git a/apps/openmw/mwmechanics/aistate.hpp b/apps/openmw/mwmechanics/aistate.hpp index 7b670ad476..581f45d078 100644 --- a/apps/openmw/mwmechanics/aistate.hpp +++ b/apps/openmw/mwmechanics/aistate.hpp @@ -76,24 +76,6 @@ namespace MWMechanics mStorage = p; } - /// \brief gives away ownership of object. Throws exception if storage does not contain Derived or is empty. - template< class Derived > - Derived* moveOut() - { - assert_derived(); - - - if(!mStorage) - throw std::runtime_error("Cant move out: empty storage."); - - Derived* result = dynamic_cast(mStorage); - - if(!mStorage) - throw std::runtime_error("Cant move out: wrong type requested."); - - return result; - } - bool empty() const { return mStorage == NULL; @@ -120,7 +102,7 @@ namespace MWMechanics /// \brief base class for the temporary storage of AiPackages. /** * Each AI package with temporary values needs a AiPackageStorage class - * which is derived from AiTemporaryBase. The CharacterController holds a container + * which is derived from AiTemporaryBase. The Actor holds a container * AiState where one of these storages can be stored at a time. * The execute(...) member function takes this container as an argument. * */ diff --git a/apps/openmw/mwmechanics/aitravel.cpp b/apps/openmw/mwmechanics/aitravel.cpp index 9597849837..7124a1102c 100644 --- a/apps/openmw/mwmechanics/aitravel.cpp +++ b/apps/openmw/mwmechanics/aitravel.cpp @@ -14,6 +14,19 @@ #include "movement.hpp" #include "creaturestats.hpp" +namespace +{ + +bool isWithinMaxRange(const Ogre::Vector3& pos1, const Ogre::Vector3& pos2) +{ + // Maximum travel distance for vanilla compatibility. + // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. + // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. + return (pos1.squaredDistance(pos2) <= 7168*7168); +} + +} + namespace MWMechanics { AiTravel::AiTravel(float x, float y, float z) @@ -71,10 +84,7 @@ namespace MWMechanics } } - // Maximum travel distance for vanilla compatibility. - // Was likely meant to prevent NPCs walking into non-loaded exterior cells, but for some reason is used in interior cells as well. - // We can make this configurable at some point, but the default *must* be the below value. Anything else will break shoddily-written content (*cough* MW *cough*) in bizarre ways. - if (Ogre::Vector3(mX, mY, mZ).squaredDistance(Ogre::Vector3(pos.pos)) > 7168*7168) + if (!isWithinMaxRange(Ogre::Vector3(mX, mY, mZ), Ogre::Vector3(pos.pos))) return false; bool cellChange = cell->mData.mX != mCellX || cell->mData.mY != mCellY; @@ -113,6 +123,16 @@ namespace MWMechanics return TypeIdTravel; } + void AiTravel::fastForward(const MWWorld::Ptr& actor, AiState& state) + { + if (!isWithinMaxRange(Ogre::Vector3(mX, mY, mZ), Ogre::Vector3(actor.getRefData().getPosition().pos))) + return; + // does not do any validation on the travel target (whether it's in air, inside collision geometry, etc), + // that is the user's responsibility + MWBase::Environment::get().getWorld()->moveObject(actor, mX, mY, mZ); + actor.getClass().adjustPosition(actor, false); + } + void AiTravel::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr travel(new ESM::AiSequence::AiTravel()); diff --git a/apps/openmw/mwmechanics/aitravel.hpp b/apps/openmw/mwmechanics/aitravel.hpp index c2c33c2cf4..c2732e3aa4 100644 --- a/apps/openmw/mwmechanics/aitravel.hpp +++ b/apps/openmw/mwmechanics/aitravel.hpp @@ -23,6 +23,9 @@ namespace MWMechanics AiTravel(float x, float y, float z); AiTravel(const ESM::AiSequence::AiTravel* travel); + /// Simulates the passing of time + virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); + void writeState(ESM::AiSequence::AiSequence &sequence) const; virtual AiTravel *clone() const; diff --git a/apps/openmw/mwmechanics/aiwander.cpp b/apps/openmw/mwmechanics/aiwander.cpp index c8a0c85d58..fbb147b341 100644 --- a/apps/openmw/mwmechanics/aiwander.cpp +++ b/apps/openmw/mwmechanics/aiwander.cpp @@ -9,6 +9,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/dialoguemanager.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" @@ -40,16 +41,9 @@ namespace MWMechanics AiWander::GreetingState mSaidGreeting; int mGreetingTimer; - - // Cached current cell location - int mCellX; - int mCellY; - // Cell location multiplied by ESM::Land::REAL_SIZE - float mXCell; - float mYCell; - + const MWWorld::CellStore* mCell; // for detecting cell change - + // AiWander states bool mChooseAction; bool mIdleNow; @@ -66,10 +60,6 @@ namespace MWMechanics mReaction(0), mSaidGreeting(AiWander::Greet_None), mGreetingTimer(0), - mCellX(std::numeric_limits::max()), - mCellY(std::numeric_limits::max()), - mXCell(0), - mYCell(0), mCell(NULL), mChooseAction(true), mIdleNow(false), @@ -81,6 +71,7 @@ namespace MWMechanics AiWander::AiWander(int distance, int duration, int timeOfDay, const std::vector& idle, bool repeat): mDistance(distance), mDuration(duration), mTimeOfDay(timeOfDay), mIdle(idle), mRepeat(repeat) + , mStoredInitialActorPosition(false) { mIdle.resize(8, 0); init(); @@ -183,7 +174,6 @@ namespace MWMechanics currentCell = actor.getCell(); mStoredAvailableNodes = false; // prob. not needed since mDistance = 0 } - const ESM::Cell *cell = currentCell->getCell(); cStats.setDrawState(DrawState_Nothing); cStats.setMovementFlag(CreatureStats::Flag_Run, false); @@ -213,7 +203,7 @@ namespace MWMechanics // Are we there yet? bool& chooseAction = storage.mChooseAction; if(walking && - storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2])) + storage.mPathFinder.checkPathCompleted(pos.pos[0], pos.pos[1], pos.pos[2], 64.f)) { stopWalking(actor, storage); moveNow = false; @@ -312,28 +302,36 @@ namespace MWMechanics playIdle(actor, playedIdle); chooseAction = false; idleNow = true; - - // Play idle voiced dialogue entries randomly - int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); - if (hello > 0) - { - int roll = std::rand()/ (static_cast (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 - static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() - .get().find("fVoiceIdleOdds")->getFloat(); - - // Only say Idle voices when player is in LOS - // A bit counterintuitive, likely vanilla did this to reduce the appearance of - // voices going through walls? - if (roll < fVoiceIdleOdds && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) < 1500*1500 - && MWBase::Environment::get().getWorld()->getLOS(player, actor)) - MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); - } } } + // Play idle voiced dialogue entries randomly + int hello = cStats.getAiSetting(CreatureStats::AI_Hello).getModified(); + if (hello > 0 && !MWBase::Environment::get().getWorld()->isSwimming(actor) + && MWBase::Environment::get().getSoundManager()->sayDone(actor)) + { + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); + + static float fVoiceIdleOdds = MWBase::Environment::get().getWorld()->getStore() + .get().find("fVoiceIdleOdds")->getFloat(); + + float roll = std::rand()/ (static_cast (RAND_MAX) + 1) * 10000; + + // In vanilla MW the chance was FPS dependent, and did not allow proper changing of fVoiceIdleOdds + // due to the roll being an integer. + // Our implementation does not have these issues, so needs to be recalibrated. We chose to + // use the chance MW would have when run at 60 FPS with the default value of the GMST for calibration. + float x = fVoiceIdleOdds * 0.6 * (MWBase::Environment::get().getFrameDuration() / 0.1); + + // Only say Idle voices when player is in LOS + // A bit counterintuitive, likely vanilla did this to reduce the appearance of + // voices going through walls? + if (roll < x && Ogre::Vector3(player.getRefData().getPosition().pos).squaredDistance(Ogre::Vector3(pos.pos)) + < 3000*3000 // maybe should be fAudioVoiceDefaultMaxDistance*fAudioMaxDistanceMult instead + && MWBase::Environment::get().getWorld()->getLOS(player, actor)) + MWBase::Environment::get().getDialogueManager()->say(actor, "idle"); + } + float& lastReaction = storage.mReaction; lastReaction += duration; if(lastReaction < REACTION_INTERVAL) @@ -371,81 +369,10 @@ namespace MWMechanics } } - - - int& cachedCellX = storage.mCellX; - int& cachedCellY = storage.mCellY; - float& cachedCellXposition = storage.mXCell; - float& cachedCellYposition = storage.mYCell; // Initialization to discover & store allowed node points for this actor. if(!mStoredAvailableNodes) { - // infrequently used, therefore no benefit in caching it as a member - const ESM::Pathgrid * - pathgrid = world->getStore().get().search(*cell); - - // cache the current cell location - cachedCellX = cell->mData.mX; - cachedCellY = cell->mData.mY; - - // If there is no path this actor doesn't go anywhere. See: - // https://forum.openmw.org/viewtopic.php?t=1556 - // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 - if(!pathgrid || pathgrid->mPoints.empty()) - mDistance = 0; - - // A distance value passed into the constructor indicates how far the - // actor can wander from the spawn position. AiWander assumes that - // pathgrid points are available, and uses them to randomly select wander - // destinations within the allowed set of pathgrid points (nodes). - if(mDistance) - { - cachedCellXposition = 0; - cachedCellYposition = 0; - if(cell->isExterior()) - { - cachedCellXposition = cachedCellX * ESM::Land::REAL_SIZE; - cachedCellYposition = cachedCellY * ESM::Land::REAL_SIZE; - } - - // FIXME: There might be a bug here. The allowed node points are - // based on the actor's current position rather than the actor's - // spawn point. As a result the allowed nodes for wander can change - // between saves, for example. - // - // convert npcPos to local (i.e. cell) co-ordinates - Ogre::Vector3 npcPos(pos.pos); - npcPos[0] = npcPos[0] - cachedCellXposition; - npcPos[1] = npcPos[1] - cachedCellYposition; - - // mAllowedNodes for this actor with pathgrid point indexes based on mDistance - // NOTE: mPoints and mAllowedNodes are in local co-ordinates - for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) - { - Ogre::Vector3 nodePos(pathgrid->mPoints[counter].mX, pathgrid->mPoints[counter].mY, - pathgrid->mPoints[counter].mZ); - if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) - mAllowedNodes.push_back(pathgrid->mPoints[counter]); - } - if(!mAllowedNodes.empty()) - { - Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ); - float closestNode = npcPos.squaredDistance(firstNodePos); - unsigned int index = 0; - for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) - { - Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, - mAllowedNodes[counterThree].mZ); - float tempDist = npcPos.squaredDistance(nodePos); - if(tempDist < closestNode) - index = counterThree; - } - mCurrentNode = mAllowedNodes[index]; - mAllowedNodes.erase(mAllowedNodes.begin() + index); - - mStoredAvailableNodes = true; // set only if successful in finding allowed nodes - } - } + getAllowedNodes(actor, currentCell->getCell()); } // Actor becomes stationary - see above URL's for previous research @@ -559,10 +486,8 @@ namespace MWMechanics if (greetingState == MWMechanics::AiWander::Greet_Done) { - static float fGreetDistanceReset = MWBase::Environment::get().getWorld()->getStore() - .get().find("fGreetDistanceReset")->getFloat(); - - if (playerDistSqr >= fGreetDistanceReset*fGreetDistanceReset) + float resetDist = 2*helloDistance; + if (playerDistSqr >= resetDist*resetDist) greetingState = Greet_None; } } @@ -581,9 +506,14 @@ namespace MWMechanics // convert dest to use world co-ordinates ESM::Pathgrid::Point dest; - dest.mX = destNodePos[0] + cachedCellXposition; - dest.mY = destNodePos[1] + cachedCellYposition; + dest.mX = destNodePos[0]; + dest.mY = destNodePos[1]; dest.mZ = destNodePos[2]; + if (currentCell->getCell()->isExterior()) + { + dest.mX += currentCell->getCell()->mData.mX * ESM::Land::REAL_SIZE; + dest.mY += currentCell->getCell()->mData.mY * ESM::Land::REAL_SIZE; + } // actor position is already in world co-ordinates ESM::Pathgrid::Point start; @@ -732,6 +662,103 @@ namespace MWMechanics } } + void AiWander::fastForward(const MWWorld::Ptr& actor, AiState &state) + { + if (mDistance == 0) + return; + + if (!mStoredAvailableNodes) + getAllowedNodes(actor, actor.getCell()->getCell()); + + if (mAllowedNodes.empty()) + return; + + state.moveIn(new AiWanderStorage()); + + int index = std::rand() / (static_cast (RAND_MAX) + 1) * mAllowedNodes.size(); + ESM::Pathgrid::Point dest = mAllowedNodes[index]; + + // apply a slight offset to prevent overcrowding + dest.mX += Ogre::Math::RangeRandom(-64, 64); + dest.mY += Ogre::Math::RangeRandom(-64, 64); + + if (actor.getCell()->isExterior()) + { + dest.mX += actor.getCell()->getCell()->mData.mX * ESM::Land::REAL_SIZE; + dest.mY += actor.getCell()->getCell()->mData.mY * ESM::Land::REAL_SIZE; + } + + MWBase::Environment::get().getWorld()->moveObject(actor, dest.mX, dest.mY, dest.mZ); + actor.getClass().adjustPosition(actor, false); + } + + void AiWander::getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell) + { + if (!mStoredInitialActorPosition) + { + mInitialActorPosition = Ogre::Vector3(actor.getRefData().getPosition().pos); + mStoredInitialActorPosition = true; + } + + // infrequently used, therefore no benefit in caching it as a member + const ESM::Pathgrid * + pathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); + + // If there is no path this actor doesn't go anywhere. See: + // https://forum.openmw.org/viewtopic.php?t=1556 + // http://www.fliggerty.com/phpBB3/viewtopic.php?f=30&t=5833 + if(!pathgrid || pathgrid->mPoints.empty()) + mDistance = 0; + + // A distance value passed into the constructor indicates how far the + // actor can wander from the spawn position. AiWander assumes that + // pathgrid points are available, and uses them to randomly select wander + // destinations within the allowed set of pathgrid points (nodes). + if(mDistance) + { + float cellXOffset = 0; + float cellYOffset = 0; + if(cell->isExterior()) + { + cellXOffset = cell->mData.mX * ESM::Land::REAL_SIZE; + cellYOffset = cell->mData.mY * ESM::Land::REAL_SIZE; + } + + // convert npcPos to local (i.e. cell) co-ordinates + Ogre::Vector3 npcPos(mInitialActorPosition); + npcPos[0] = npcPos[0] - cellXOffset; + npcPos[1] = npcPos[1] - cellYOffset; + + // mAllowedNodes for this actor with pathgrid point indexes based on mDistance + // NOTE: mPoints and mAllowedNodes are in local co-ordinates + for(unsigned int counter = 0; counter < pathgrid->mPoints.size(); counter++) + { + Ogre::Vector3 nodePos(pathgrid->mPoints[counter].mX, pathgrid->mPoints[counter].mY, + pathgrid->mPoints[counter].mZ); + if(npcPos.squaredDistance(nodePos) <= mDistance * mDistance) + mAllowedNodes.push_back(pathgrid->mPoints[counter]); + } + if(!mAllowedNodes.empty()) + { + Ogre::Vector3 firstNodePos(mAllowedNodes[0].mX, mAllowedNodes[0].mY, mAllowedNodes[0].mZ); + float closestNode = npcPos.squaredDistance(firstNodePos); + unsigned int index = 0; + for(unsigned int counterThree = 1; counterThree < mAllowedNodes.size(); counterThree++) + { + Ogre::Vector3 nodePos(mAllowedNodes[counterThree].mX, mAllowedNodes[counterThree].mY, + mAllowedNodes[counterThree].mZ); + float tempDist = npcPos.squaredDistance(nodePos); + if(tempDist < closestNode) + index = counterThree; + } + mCurrentNode = mAllowedNodes[index]; + mAllowedNodes.erase(mAllowedNodes.begin() + index); + + mStoredAvailableNodes = true; // set only if successful in finding allowed nodes + } + } + } + void AiWander::writeState(ESM::AiSequence::AiSequence &sequence) const { std::auto_ptr wander(new ESM::AiSequence::AiWander()); @@ -743,6 +770,9 @@ namespace MWMechanics for (int i=0; i<8; ++i) wander->mData.mIdle[i] = mIdle[i]; wander->mData.mShouldRepeat = mRepeat; + wander->mStoredInitialActorPosition = mStoredInitialActorPosition; + if (mStoredInitialActorPosition) + wander->mInitialActorPosition = mInitialActorPosition; ESM::AiSequence::AiPackageContainer package; package.mType = ESM::AiSequence::Ai_Wander; @@ -756,7 +786,10 @@ namespace MWMechanics , mStartTime(MWWorld::TimeStamp(wander->mStartTime)) , mTimeOfDay(wander->mData.mTimeOfDay) , mRepeat(wander->mData.mShouldRepeat) + , mStoredInitialActorPosition(wander->mStoredInitialActorPosition) { + if (mStoredInitialActorPosition) + mInitialActorPosition = wander->mInitialActorPosition; for (int i=0; i<8; ++i) mIdle.push_back(wander->mData.mIdle[i]); diff --git a/apps/openmw/mwmechanics/aiwander.hpp b/apps/openmw/mwmechanics/aiwander.hpp index b9b394a264..5e1b418139 100644 --- a/apps/openmw/mwmechanics/aiwander.hpp +++ b/apps/openmw/mwmechanics/aiwander.hpp @@ -17,6 +17,7 @@ namespace ESM { + class Cell; namespace AiSequence { struct AiWander; @@ -57,6 +58,7 @@ namespace MWMechanics virtual void writeState(ESM::AiSequence::AiSequence &sequence) const; + virtual void fastForward(const MWWorld::Ptr& actor, AiState& state); enum GreetingState { Greet_None, @@ -77,14 +79,14 @@ namespace MWMechanics int mTimeOfDay; std::vector mIdle; bool mRepeat; - bool mHasReturnPosition; // NOTE: Could be removed if mReturnPosition was initialized to actor position, // if we had the actor in the AiWander constructor... Ogre::Vector3 mReturnPosition; - + Ogre::Vector3 mInitialActorPosition; + bool mStoredInitialActorPosition; @@ -98,6 +100,9 @@ namespace MWMechanics // allowed pathgrid nodes based on mDistance from the spawn point std::vector mAllowedNodes; + + void getAllowedNodes(const MWWorld::Ptr& actor, const ESM::Cell* cell); + ESM::Pathgrid::Point mCurrentNode; bool mTrimCurrentNode; void trimAllowedNodes(std::vector& nodes, diff --git a/apps/openmw/mwmechanics/alchemy.cpp b/apps/openmw/mwmechanics/alchemy.cpp index da2492a771..f3d376a700 100644 --- a/apps/openmw/mwmechanics/alchemy.cpp +++ b/apps/openmw/mwmechanics/alchemy.cpp @@ -286,7 +286,8 @@ void MWMechanics::Alchemy::addPotion (const std::string& name) if (!iter->isEmpty()) newRecord.mData.mWeight += iter->get()->mBase->mData.mWeight; - newRecord.mData.mWeight /= countIngredients(); + if (countIngredients() > 0) + newRecord.mData.mWeight /= countIngredients(); newRecord.mData.mValue = mValue; newRecord.mData.mAutoCalc = 0; diff --git a/apps/openmw/mwmechanics/character.cpp b/apps/openmw/mwmechanics/character.cpp index 46e06f4604..449c030f45 100644 --- a/apps/openmw/mwmechanics/character.cpp +++ b/apps/openmw/mwmechanics/character.cpp @@ -27,6 +27,8 @@ #include "creaturestats.hpp" #include "security.hpp" +#include + #include "../mwrender/animation.hpp" #include "../mwbase/environment.hpp" @@ -42,6 +44,15 @@ namespace { +// Wraps a value to (-PI, PI] +void wrap(Ogre::Radian& rad) +{ + if (rad.valueRadians()>0) + rad = Ogre::Radian(std::fmod(rad.valueRadians()+Ogre::Math::PI, 2.0f*Ogre::Math::PI)-Ogre::Math::PI); + else + rad = Ogre::Radian(std::fmod(rad.valueRadians()-Ogre::Math::PI, 2.0f*Ogre::Math::PI)+Ogre::Math::PI); +} + std::string getBestAttack (const ESM::Weapon* weapon) { int slash = (weapon->mData.mSlash[0] + weapon->mData.mSlash[1])/2; @@ -92,6 +103,35 @@ MWMechanics::CharacterState runStateToWalkState (MWMechanics::CharacterState sta return ret; } +float getFallDamage(const MWWorld::Ptr& ptr, float fallHeight) +{ + MWBase::World *world = MWBase::Environment::get().getWorld(); + const MWWorld::Store &store = world->getStore().get(); + + const float fallDistanceMin = store.find("fFallDamageDistanceMin")->getFloat(); + + if (fallHeight >= fallDistanceMin) + { + const float acrobaticsSkill = ptr.getClass().getSkill(ptr, ESM::Skill::Acrobatics); + const float jumpSpellBonus = ptr.getClass().getCreatureStats(ptr).getMagicEffects().get(ESM::MagicEffect::Jump).getMagnitude(); + const float fallAcroBase = store.find("fFallAcroBase")->getFloat(); + const float fallAcroMult = store.find("fFallAcroMult")->getFloat(); + const float fallDistanceBase = store.find("fFallDistanceBase")->getFloat(); + const float fallDistanceMult = store.find("fFallDistanceMult")->getFloat(); + + float x = fallHeight - fallDistanceMin; + x -= (1.5 * acrobaticsSkill) + jumpSpellBonus; + x = std::max(0.0f, x); + + float a = fallAcroBase + fallAcroMult * (100 - acrobaticsSkill); + x = fallDistanceBase + fallDistanceMult * x; + x *= a; + + return x; + } + return 0.f; +} + } namespace MWMechanics @@ -258,40 +298,9 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat } const WeaponInfo *weap = std::find_if(sWeaponTypeList, sWeaponTypeListEnd, FindWeaponType(mWeaponType)); - if (!mPtr.getClass().hasInventoryStore(mPtr)) + if (!mPtr.getClass().isBipedal(mPtr)) weap = sWeaponTypeListEnd; - if(force || idle != mIdleState) - { - mIdleState = idle; - - std::string idle; - // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to - // "idle"+weapon or "idle". - if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) - idle = "idleswim"; - else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) - idle = "idlesneak"; - else if(mIdleState != CharState_None) - { - idle = "idle"; - if(weap != sWeaponTypeListEnd) - { - idle += weap->shortgroup; - if(!mAnimation->hasAnimation(idle)) - idle = "idle"; - } - } - - mAnimation->disable(mCurrentIdle); - mCurrentIdle = idle; - if(!mCurrentIdle.empty()) - mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, - 1.0f, "start", "stop", 0.0f, ~0ul); - } - - updateIdleStormState(); - if(force && mJumpState != JumpState_None) { std::string jump; @@ -432,6 +441,46 @@ void CharacterController::refreshCurrentAnims(CharacterState idle, CharacterStat speedmult, ((mode!=2)?"start":"loop start"), "stop", 0.0f, ~0ul); } } + + // idle handled last as it can depend on the other states + // FIXME: if one of the below states is close to their last animation frame (i.e. will be disabled in the coming update), + // the idle animation should be displayed + if ((mUpperBodyState != UpperCharState_Nothing + || mMovementState != CharState_None + || mHitState != CharState_None) + && !mPtr.getClass().isBipedal(mPtr)) + idle = CharState_None; + + if(force || idle != mIdleState) + { + mIdleState = idle; + + std::string idle; + // Only play "idleswim" or "idlesneak" if they exist. Otherwise, fallback to + // "idle"+weapon or "idle". + if(mIdleState == CharState_IdleSwim && mAnimation->hasAnimation("idleswim")) + idle = "idleswim"; + else if(mIdleState == CharState_IdleSneak && mAnimation->hasAnimation("idlesneak")) + idle = "idlesneak"; + else if(mIdleState != CharState_None) + { + idle = "idle"; + if(weap != sWeaponTypeListEnd) + { + idle += weap->shortgroup; + if(!mAnimation->hasAnimation(idle)) + idle = "idle"; + } + } + + mAnimation->disable(mCurrentIdle); + mCurrentIdle = idle; + if(!mCurrentIdle.empty()) + mAnimation->play(mCurrentIdle, Priority_Default, MWRender::Animation::Group_All, false, + 1.0f, "start", "stop", 0.0f, ~0ul, true); + } + + updateIdleStormState(); } @@ -619,7 +668,8 @@ CharacterController::CharacterController(const MWWorld::Ptr &ptr, MWRender::Anim mAnimation->showWeapons(true); mAnimation->setWeaponGroup(mCurrentWeapon); } - mAnimation->showCarriedLeft(mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand); + + mAnimation->showCarriedLeft(updateCarriedLeftVisible(mWeaponType)); } if(!cls.getCreatureStats(mPtr).isDead()) @@ -807,24 +857,57 @@ bool CharacterController::updateCreatureState() return false; } +bool CharacterController::updateCarriedLeftVisible(WeaponType weaptype) const +{ + // Shields/torches shouldn't be visible during any operation involving two hands + // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", + // but they are also present in weapon drawing animation. + switch (weaptype) + { + case WeapType_Spell: + case WeapType_BowAndArrow: + case WeapType_Crossbow: + case WeapType_HandToHand: + case WeapType_TwoHand: + case WeapType_TwoWide: + return false; + default: + return true; + } +} + bool CharacterController::updateWeaponState() { const MWWorld::Class &cls = mPtr.getClass(); CreatureStats &stats = cls.getCreatureStats(mPtr); WeaponType weaptype = WeapType_None; - MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); - MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + if(stats.getDrawState() == DrawState_Weapon) + weaptype = WeapType_HandToHand; + else if (stats.getDrawState() == DrawState_Spell) + weaptype = WeapType_Spell; + const bool isWerewolf = cls.isNpc() && cls.getNpcStats(mPtr).isWerewolf(); + std::string soundid; + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) + { + soundid = (weaptype == WeapType_None) ? + weapon->getClass().getDownSoundId(*weapon) : + weapon->getClass().getUpSoundId(*weapon); + } + } + bool forcestateupdate = false; - if(weaptype != mWeaponType && mHitState != CharState_KnockDown) + if(weaptype != mWeaponType && mHitState != CharState_KnockDown && mHitState != CharState_KnockOut + && mHitState != CharState_Hit) { forcestateupdate = true; - // Shields/torches shouldn't be visible during spellcasting or hand-to-hand - // There seems to be no text keys for this purpose, except maybe for "[un]equip start/stop", - // but they are also present in weapon drawing animation. - mAnimation->showCarriedLeft(weaptype != WeapType_Spell && weaptype != WeapType_HandToHand); + mAnimation->showCarriedLeft(updateCarriedLeftVisible(weaptype)); std::string weapgroup; if(weaptype == WeapType_None) @@ -858,16 +941,10 @@ bool CharacterController::updateWeaponState() } } - if(weapon != inv.end() && !(weaptype == WeapType_None && mWeaponType == WeapType_Spell)) + if(!soundid.empty()) { - std::string soundid = (weaptype == WeapType_None) ? - weapon->getClass().getDownSoundId(*weapon) : - weapon->getClass().getUpSoundId(*weapon); - if(!soundid.empty()) - { - MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); - sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); - } + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(mPtr, soundid, 1.0f, 1.0f); } mWeaponType = weaptype; @@ -890,22 +967,28 @@ bool CharacterController::updateWeaponState() sndMgr->stopSound3D(mPtr, "WolfRun"); } - bool isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); - float weapSpeed = 1.0f; - if(isWeapon) - weapSpeed = weapon->get()->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()->mBase->mData.mType == ESM::Weapon::Bolt); - else if (mWeaponType == WeapType_BowAndArrow) - ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); - if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + bool isWeapon = false; + float weapSpeed = 1.f; + if (mPtr.getClass().hasInventoryStore(mPtr)) { - mAnimation->disable(mCurrentWeapon); - mUpperBodyState = UpperCharState_WeapEquiped; + MWWorld::InventoryStore &inv = cls.getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator weapon = getActiveWeapon(stats, inv, &weaptype); + isWeapon = (weapon != inv.end() && weapon->getTypeName() == typeid(ESM::Weapon).name()); + if(isWeapon) + weapSpeed = weapon->get()->mBase->mData.mSpeed; + + MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); + if (mWeaponType == WeapType_Crossbow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Bolt); + else if (mWeaponType == WeapType_BowAndArrow) + ammunition = (ammo != inv.end() && ammo->get()->mBase->mData.mType == ESM::Weapon::Arrow); + if (!ammunition && mUpperBodyState > UpperCharState_WeapEquiped) + { + mAnimation->disable(mCurrentWeapon); + mUpperBodyState = UpperCharState_WeapEquiped; + } } float complete; @@ -946,15 +1029,14 @@ bool CharacterController::updateWeaponState() const ESM::Static* castStatic = MWBase::Environment::get().getWorld()->getStore().get().find ("VFX_Hands"); if (mAnimation->getNode("Left Hand")) - { mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Left Hand", effect->mParticle); - mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); - } else - { mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 L Hand", effect->mParticle); + + if (mAnimation->getNode("Right Hand")) + mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Right Hand", effect->mParticle); + else mAnimation->addEffect("meshes\\" + castStatic->mModel, -1, false, "Bip01 R Hand", effect->mParticle); - } switch(effectentry.mRange) { @@ -969,14 +1051,20 @@ bool CharacterController::updateWeaponState() 0.0f, 0); mUpperBodyState = UpperCharState_CastingSpell; } - if (inv.getSelectedEnchantItem() != inv.end()) + if (mPtr.getClass().hasInventoryStore(mPtr)) { - // Enchanted items cast immediately (no animation) - MWBase::Environment::get().getWorld()->castSpell(mPtr); + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + if (inv.getSelectedEnchantItem() != inv.end()) + { + // Enchanted items cast immediately (no animation) + MWBase::Environment::get().getWorld()->castSpell(mPtr); + } } + } else if(mWeaponType == WeapType_PickProbe) { + MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); MWWorld::Ptr item = *weapon; // TODO: this will only work for the player, and needs to be fixed if NPCs should ever use lockpicks/probes. MWWorld::Ptr target = MWBase::Environment::get().getWorld()->getFacedObject(); @@ -1008,7 +1096,10 @@ bool CharacterController::updateWeaponState() { if(isWeapon && mPtr.getRefData().getHandle() == "player" && Settings::Manager::getBool("best attack", "Game")) + { + MWWorld::ContainerStoreIterator weapon = mPtr.getClass().getInventoryStore(mPtr).getSlot(MWWorld::InventoryStore::Slot_CarriedRight); mAttackType = getBestAttack(weapon->get()->mBase); + } else determineAttackType(); } @@ -1037,7 +1128,7 @@ bool CharacterController::updateWeaponState() attackStrength = std::min(1.f, 0.1f + std::rand() / float(RAND_MAX)); } - if(mAttackType != "shoot") + if(mWeaponType != WeapType_Crossbow && mWeaponType != WeapType_BowAndArrow) { MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); @@ -1228,17 +1319,21 @@ bool CharacterController::updateWeaponState() } } - MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() - && mWeaponType != WeapType_Spell && mWeaponType != WeapType_HandToHand) + if (mPtr.getClass().hasInventoryStore(mPtr)) + { + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); + MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() + && updateCarriedLeftVisible(mWeaponType)) - { - mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, - false, 1.0f, "start", "stop", 0.0f, (~(size_t)0)); - } - else if (mAnimation->isPlaying("torch")) - { - mAnimation->disable("torch"); + { + mAnimation->play("torch", Priority_Torch, MWRender::Animation::Group_LeftArm, + false, 1.0f, "start", "stop", 0.0f, (~(size_t)0), true); + } + else if (mAnimation->isPlaying("torch")) + { + mAnimation->disable("torch"); + } } return forcestateupdate; @@ -1250,7 +1345,7 @@ void CharacterController::update(float duration) const MWWorld::Class &cls = mPtr.getClass(); Ogre::Vector3 movement(0.0f); - updateVisibility(); + updateMagicEffects(); if(!cls.isActor()) { @@ -1279,7 +1374,7 @@ void CharacterController::update(float duration) //Force Jump Logic - bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); + bool isMoving = (std::abs(cls.getMovementSettings(mPtr).mPosition[0]) > .5 || std::abs(cls.getMovementSettings(mPtr).mPosition[1]) > .5); if(!inwater && !flying) { //Force Jump @@ -1403,12 +1498,12 @@ void CharacterController::update(float duration) forcestateupdate = (mJumpState != JumpState_InAir); mJumpState = JumpState_InAir; - // This is a guess. All that seems to be known is that "While the player is in the - // air, fJumpMoveBase and fJumpMoveMult governs air control". What does fJumpMoveMult do? static const float fJumpMoveBase = gmst.find("fJumpMoveBase")->getFloat(); - - vec.x *= fJumpMoveBase; - vec.y *= fJumpMoveBase; + static const float fJumpMoveMult = gmst.find("fJumpMoveMult")->getFloat(); + float factor = fJumpMoveBase + fJumpMoveMult * mPtr.getClass().getSkill(mPtr, ESM::Skill::Acrobatics)/100.f; + factor = std::min(1.f, factor); + vec.x *= factor; + vec.y *= factor; vec.z = 0.0f; } else if(vec.z > 0.0f && mJumpState == JumpState_None) @@ -1449,7 +1544,7 @@ void CharacterController::update(float duration) vec.z = 0.0f; float height = cls.getCreatureStats(mPtr).land(); - float healthLost = cls.getFallDamage(mPtr, height); + float healthLost = getFallDamage(mPtr, height); if (healthLost > 0.0f) { const float fatigueTerm = cls.getCreatureStats(mPtr).getFatigueTerm(); @@ -1531,7 +1626,9 @@ void CharacterController::update(float duration) clearAnimQueue(); if(mAnimQueue.empty()) + { idlestate = (inwater ? CharState_IdleSwim : (sneak ? CharState_IdleSneak : CharState_Idle)); + } else if(mAnimQueue.size() > 1) { if(mAnimation->isPlaying(mAnimQueue.front().first) == false) @@ -1545,12 +1642,13 @@ void CharacterController::update(float duration) } } - if(cls.hasInventoryStore(mPtr)) + if(cls.isBipedal(mPtr)) forcestateupdate = updateWeaponState() || forcestateupdate; else forcestateupdate = updateCreatureState() || forcestateupdate; - refreshCurrentAnims(idlestate, movestate, forcestateupdate); + if (!mSkipAnim) + refreshCurrentAnims(idlestate, movestate, forcestateupdate); if (inJump) mMovementAnimationControlled = false; @@ -1581,46 +1679,48 @@ void CharacterController::update(float duration) cls.getMovementSettings(mPtr).mPosition[0] = cls.getMovementSettings(mPtr).mPosition[1] = 0; // Can't reset jump state (mPosition[2]) here; we don't know for sure whether the PhysicSystem will actually handle it in this frame // due to the fixed minimum timestep used for the physics update. It will be reset in PhysicSystem::move once the jump is handled. + + if (!mSkipAnim) + updateHeadTracking(duration); } else if(cls.getCreatureStats(mPtr).isDead()) { world->queueMovement(mPtr, Ogre::Vector3(0.0f)); } - if(mAnimation && !mSkipAnim) - { - Ogre::Vector3 moved = mAnimation->runAnimation(duration); - if(duration > 0.0f) - moved /= duration; - else - moved = Ogre::Vector3(0.0f); - - // Ensure we're moving in generally the right direction... - if(mMovementSpeed > 0.f) - { - 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; - if((movement.y < 0.0f && movement.y < moved.y*2.0f) || - (movement.y > 0.0f && movement.y > moved.y*2.0f)) - moved.y = movement.y; - if((movement.z < 0.0f && movement.z < moved.z*2.0f) || - (movement.z > 0.0f && movement.z > moved.z*2.0f)) - moved.z = movement.z; - // but keep the original speed - float newLength = moved.length(); - if (newLength > 0) - moved *= (l / newLength); - } - - // Update movement - if(mMovementAnimationControlled && mPtr.getClass().isActor()) - world->queueMovement(mPtr, moved); - } + Ogre::Vector3 moved = mAnimation->runAnimation(mSkipAnim ? 0.f : duration); + if(duration > 0.0f) + moved /= duration; else + moved = Ogre::Vector3(0.0f); + + // Ensure we're moving in generally the right direction... + if(mMovementSpeed > 0.f) + { + 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; + if((movement.y < 0.0f && movement.y < moved.y*2.0f) || + (movement.y > 0.0f && movement.y > moved.y*2.0f)) + moved.y = movement.y; + if((movement.z < 0.0f && movement.z < moved.z*2.0f) || + (movement.z > 0.0f && movement.z > moved.z*2.0f)) + moved.z = movement.z; + // but keep the original speed + float newLength = moved.length(); + if (newLength > 0) + moved *= (l / newLength); + } + + if (mSkipAnim) mAnimation->updateEffects(duration); + + // Update movement + if(mMovementAnimationControlled && mPtr.getClass().isActor()) + world->queueMovement(mPtr, moved); + mSkipAnim = false; mAnimation->enableHeadAnimation(cls.isActor() && !cls.getCreatureStats(mPtr).isDead()); @@ -1689,6 +1789,8 @@ void CharacterController::forceStateUpdate() { playRandomDeath(); } + + mAnimation->runAnimation(0.f); } bool CharacterController::kill() @@ -1705,10 +1807,7 @@ bool CharacterController::kill() playRandomDeath(); - if(mAnimation) - { - mAnimation->disable(mCurrentIdle); - } + mAnimation->disable(mCurrentIdle); mIdleState = CharState_None; mCurrentIdle.clear(); @@ -1748,7 +1847,7 @@ void CharacterController::updateContinuousVfx() } } -void CharacterController::updateVisibility() +void CharacterController::updateMagicEffects() { if (!mPtr.getClass().isActor()) return; @@ -1765,9 +1864,11 @@ void CharacterController::updateVisibility() { alpha *= std::max(0.2f, (100.f - chameleon)/100.f); } - mAnimation->setAlpha(alpha); + bool vampire = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Vampirism).getMagnitude() > 0.0f; + mAnimation->setVampire(vampire); + float light = mPtr.getClass().getCreatureStats(mPtr).getMagicEffects().get(ESM::MagicEffect::Light).getMagnitude(); mAnimation->setLightEffect(light); } @@ -1776,15 +1877,73 @@ void CharacterController::determineAttackType() { float *move = mPtr.getClass().getMovementSettings(mPtr).mPosition; - if(mPtr.getClass().hasInventoryStore(mPtr)) + if (move[1] && !move[0]) // forward-backward + mAttackType = "thrust"; + else if (move[0] && !move[1]) //sideway + mAttackType = "slash"; + else + mAttackType = "chop"; +} + +bool CharacterController::isReadyToBlock() const +{ + return updateCarriedLeftVisible(mWeaponType); +} + +bool CharacterController::isKnockedOut() const +{ + return mHitState == CharState_KnockOut; +} + +void CharacterController::setHeadTrackTarget(const MWWorld::Ptr &target) +{ + mHeadTrackTarget = target; +} + +void CharacterController::updateHeadTracking(float duration) +{ + Ogre::Node* head = mAnimation->getNode("Bip01 Head"); + if (!head) + return; + Ogre::Radian zAngle (0.f); + Ogre::Radian xAngle (0.f); + if (!mHeadTrackTarget.isEmpty()) { - if (move[1] && !move[0]) // forward-backward - mAttackType = "thrust"; - else if (move[0] && !move[1]) //sideway - mAttackType = "slash"; - else - mAttackType = "chop"; + Ogre::Vector3 headPos = mPtr.getRefData().getBaseNode()->convertLocalToWorldPosition(head->_getDerivedPosition()); + Ogre::Vector3 targetPos (mHeadTrackTarget.getRefData().getPosition().pos); + if (MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(mHeadTrackTarget)) + { + Ogre::Node* targetHead = anim->getNode("Head"); + if (!targetHead) + targetHead = anim->getNode("Bip01 Head"); + if (targetHead) + targetPos = mHeadTrackTarget.getRefData().getBaseNode()->convertLocalToWorldPosition( + targetHead->_getDerivedPosition()); + } + + Ogre::Vector3 direction = targetPos - headPos; + direction.normalise(); + + const Ogre::Vector3 actorDirection = mPtr.getRefData().getBaseNode()->getOrientation().yAxis(); + + zAngle = Ogre::Math::ATan2(direction.x,direction.y) - + Ogre::Math::ATan2(actorDirection.x, actorDirection.y); + xAngle = -Ogre::Math::ASin(direction.z); + wrap(zAngle); + wrap(xAngle); + xAngle = Ogre::Degree(std::min(xAngle.valueDegrees(), 40.f)); + xAngle = Ogre::Degree(std::max(xAngle.valueDegrees(), -40.f)); + zAngle = Ogre::Degree(std::min(zAngle.valueDegrees(), 30.f)); + zAngle = Ogre::Degree(std::max(zAngle.valueDegrees(), -30.f)); + } + float factor = duration*5; + factor = std::min(factor, 1.f); + xAngle = (1.f-factor) * mAnimation->getHeadPitch() + factor * (-xAngle); + zAngle = (1.f-factor) * mAnimation->getHeadYaw() + factor * (-zAngle); + + mAnimation->setHeadPitch(xAngle); + mAnimation->setHeadYaw(zAngle); } } diff --git a/apps/openmw/mwmechanics/character.hpp b/apps/openmw/mwmechanics/character.hpp index 2a14c53b91..8a77494b97 100644 --- a/apps/openmw/mwmechanics/character.hpp +++ b/apps/openmw/mwmechanics/character.hpp @@ -6,7 +6,6 @@ #include #include "../mwworld/ptr.hpp" -#include "aistate.hpp" namespace MWWorld { @@ -140,9 +139,6 @@ class CharacterController MWWorld::Ptr mPtr; MWRender::Animation *mAnimation; - // - AiState mAiState; - typedef std::deque > AnimationQueue; AnimationQueue mAnimQueue; @@ -175,6 +171,8 @@ class CharacterController float mSecondsOfSwimming; float mSecondsOfRunning; + MWWorld::Ptr mHeadTrackTarget; + float mTurnAnimationThreshold; // how long to continue playing turning animation after actor stopped turning std::string mAttackType; // slash, chop or thrust @@ -188,9 +186,11 @@ class CharacterController bool updateCreatureState(); void updateIdleStormState(); + void updateHeadTracking(float duration); + void castSpell(const std::string& spellid); - void updateVisibility(); + void updateMagicEffects(); void playDeath(float startpoint, CharacterState death); void playRandomDeath(float startpoint = 0.0f); @@ -199,6 +199,8 @@ class CharacterController /// @param num if non-NULL, the chosen animation number will be written here std::string chooseRandomGroup (const std::string& prefix, int* num = NULL); + bool updateCarriedLeftVisible(WeaponType weaptype) const; + public: CharacterController(const MWWorld::Ptr &ptr, MWRender::Animation *anim); virtual ~CharacterController(); @@ -223,7 +225,11 @@ public: void forceStateUpdate(); - AiState& getAiState() { return mAiState; } + bool isReadyToBlock() const; + bool isKnockedOut() const; + + /// Make this character turn its head towards \a target. To turn off head tracking, pass an empty Ptr. + void setHeadTrackTarget(const MWWorld::Ptr& target); }; void getWeaponGroup(WeaponType weaptype, std::string &group); diff --git a/apps/openmw/mwmechanics/combat.cpp b/apps/openmw/mwmechanics/combat.cpp index 9225a57999..e22e9ec24f 100644 --- a/apps/openmw/mwmechanics/combat.cpp +++ b/apps/openmw/mwmechanics/combat.cpp @@ -29,7 +29,7 @@ Ogre::Radian signedAngle(Ogre::Vector3 v1, Ogre::Vector3 v2, Ogre::Vector3 norma ); } -void applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const Ogre::Vector3& hitPosition) +bool applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, const MWWorld::Ptr& object, const Ogre::Vector3& hitPosition) { std::string enchantmentName = !object.isEmpty() ? object.getClass().getEnchantment(object) : ""; if (!enchantmentName.empty()) @@ -41,8 +41,10 @@ void applyEnchantment (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, MWMechanics::CastSpell cast(attacker, victim); cast.mHitPosition = hitPosition; cast.cast(object); + return true; } } + return false; } } @@ -62,17 +64,10 @@ namespace MWMechanics || blockerStats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) return false; - // Don't block when in spellcasting state (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Spell) + if (!MWBase::Environment::get().getMechanicsManager()->isReadyToBlock(blocker)) return false; MWWorld::InventoryStore& inv = blocker.getClass().getInventoryStore(blocker); - - // Don't block when in hand-to-hand combat (shield is equipped, but not visible) - if (blockerStats.getDrawState() == DrawState_Weapon && - inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight) == inv.end()) - return false; - MWWorld::ContainerStoreIterator shield = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); if (shield == inv.end() || shield->getTypeName() != typeid(ESM::Armor).name()) return false; @@ -98,7 +93,11 @@ namespace MWMechanics blockerTerm *= gmst.find("fBlockStillBonus")->getFloat(); blockerTerm *= blockerStats.getFatigueTerm(); - float attackerSkill = attacker.getClass().getSkill(attacker, weapon.getClass().getEquipmentSkill(weapon)); + float attackerSkill = 0.f; + if (weapon.isEmpty()) + attackerSkill = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand); + else + 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(); @@ -127,7 +126,8 @@ namespace MWMechanics float normalizedEncumbrance = blocker.getClass().getNormalizedEncumbrance(blocker); normalizedEncumbrance = std::min(1.f, normalizedEncumbrance); float fatigueLoss = fFatigueBlockBase + normalizedEncumbrance * fFatigueBlockMult; - fatigueLoss += weapon.getClass().getWeight(weapon) * attackerStats.getAttackStrength() * fWeaponFatigueBlockMult; + if (!weapon.isEmpty()) + fatigueLoss += weapon.getClass().getWeight(weapon) * attackerStats.getAttackStrength() * fWeaponFatigueBlockMult; fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); blockerStats.setFatigue(fatigue); @@ -218,15 +218,16 @@ namespace MWMechanics damage *= gmst.find("fCombatKODamageMult")->getFloat(); // Apply "On hit" effect of the weapon - applyEnchantment(attacker, victim, weapon, hitPosition); + bool appliedEnchantment = applyEnchantment(attacker, victim, weapon, hitPosition); if (weapon != projectile) - applyEnchantment(attacker, victim, projectile, hitPosition); + appliedEnchantment = applyEnchantment(attacker, victim, projectile, hitPosition); if (damage > 0) MWBase::Environment::get().getWorld()->spawnBloodEffect(victim, hitPosition); - // Arrows shot at enemies have a chance to turn up in their inventory - if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr()) + // Non-enchanted arrows shot at enemies have a chance to turn up in their inventory + if (victim != MWBase::Environment::get().getWorld()->getPlayerPtr() + && !appliedEnchantment) { float fProjectileThrownStoreChance = gmst.find("fProjectileThrownStoreChance")->getFloat(); if ((::rand()/(RAND_MAX+1.0)) < fProjectileThrownStoreChance/100.f) @@ -334,4 +335,76 @@ namespace MWMechanics damage *= (float(weaphealth) / weapmaxhealth); } } + + void getHandToHandDamage(const MWWorld::Ptr &attacker, const MWWorld::Ptr &victim, float &damage, bool &healthdmg) + { + // Note: MCP contains an option to include Strength in hand-to-hand damage + // calculations. Some mods recommend using it, so we may want to include an + // option for it. + const MWWorld::ESMStore& store = MWBase::Environment::get().getWorld()->getStore(); + float minstrike = store.get().find("fMinHandToHandMult")->getFloat(); + float maxstrike = store.get().find("fMaxHandToHandMult")->getFloat(); + damage = attacker.getClass().getSkill(attacker, ESM::Skill::HandToHand); + damage *= minstrike + ((maxstrike-minstrike)*attacker.getClass().getCreatureStats(attacker).getAttackStrength()); + + MWMechanics::CreatureStats& otherstats = victim.getClass().getCreatureStats(victim); + healthdmg = (otherstats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0) + || otherstats.getKnockedDown(); + bool isWerewolf = (attacker.getClass().isNpc() && attacker.getClass().getNpcStats(attacker).isWerewolf()); + if(isWerewolf) + { + healthdmg = true; + // GLOB instead of GMST because it gets updated during a quest + damage *= MWBase::Environment::get().getWorld()->getGlobalFloat("werewolfclawmult"); + } + if(healthdmg) + damage *= store.get().find("fHandtoHandHealthPer")->getFloat(); + + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + if(isWerewolf) + { + const ESM::Sound *sound = store.get().searchRandom("WolfHit"); + if(sound) + sndMgr->playSound3D(victim, sound->mId, 1.0f, 1.0f); + } + else + sndMgr->playSound3D(victim, "Hand To Hand Hit", 1.0f, 1.0f); + } + + void applyFatigueLoss(const MWWorld::Ptr &attacker, const MWWorld::Ptr &weapon) + { + // somewhat of a guess, but using the weapon weight makes sense + const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); + const float fFatigueAttackBase = store.find("fFatigueAttackBase")->getFloat(); + const float fFatigueAttackMult = store.find("fFatigueAttackMult")->getFloat(); + const float fWeaponFatigueMult = store.find("fWeaponFatigueMult")->getFloat(); + CreatureStats& stats = attacker.getClass().getCreatureStats(attacker); + MWMechanics::DynamicStat fatigue = stats.getFatigue(); + const float normalizedEncumbrance = attacker.getClass().getNormalizedEncumbrance(attacker); + float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; + if (!weapon.isEmpty()) + fatigueLoss += weapon.getClass().getWeight(weapon) * stats.getAttackStrength() * fWeaponFatigueMult; + fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); + stats.setFatigue(fatigue); + } + + bool isEnvironmentCompatible(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim) + { + const MWWorld::Class& attackerClass = attacker.getClass(); + MWBase::World* world = MWBase::Environment::get().getWorld(); + + // If attacker is fish, victim must be in water + if (attackerClass.isPureWaterCreature(attacker)) + { + return world->isWading(victim); + } + + // If attacker can't swim, victim must not be in water + if (!attackerClass.canSwim(attacker)) + { + return !world->isSwimming(victim); + } + + return true; + } } diff --git a/apps/openmw/mwmechanics/combat.hpp b/apps/openmw/mwmechanics/combat.hpp index 0d3009510b..a48dcf72a4 100644 --- a/apps/openmw/mwmechanics/combat.hpp +++ b/apps/openmw/mwmechanics/combat.hpp @@ -32,6 +32,15 @@ void reduceWeaponCondition (float damage, bool hit, MWWorld::Ptr& weapon, const /// Adjust weapon damage based on its condition. A used weapon will be less effective. void adjustWeaponDamage (float& damage, const MWWorld::Ptr& weapon); +void getHandToHandDamage (const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim, float& damage, bool& healthdmg); + +/// Apply the fatigue loss incurred by attacking with the given weapon (weapon may be empty = hand-to-hand) +void applyFatigueLoss(const MWWorld::Ptr& attacker, const MWWorld::Ptr& weapon); + +/// Can attacker operate in victim's environment? +/// e.g. If attacker is a fish, is victim in water? Or, if attacker can't swim, is victim on land? +bool isEnvironmentCompatible(const MWWorld::Ptr& attacker, const MWWorld::Ptr& victim); + } #endif diff --git a/apps/openmw/mwmechanics/creaturestats.cpp b/apps/openmw/mwmechanics/creaturestats.cpp index 21fd2203fd..05ea9fb5e4 100644 --- a/apps/openmw/mwmechanics/creaturestats.cpp +++ b/apps/openmw/mwmechanics/creaturestats.cpp @@ -196,6 +196,8 @@ namespace MWMechanics if (index==0 && mDynamic[index].getCurrent()<1) { mDead = true; + + mDynamic[index].setModifier(0); mDynamic[index].setCurrent(0); if (MWBase::Environment::get().getWorld()->getGodModeState()) @@ -329,16 +331,6 @@ namespace MWMechanics mAttacked = attacked; } - bool CreatureStats::getCreatureTargetted() const - { - MWWorld::Ptr targetPtr; - if (mAiSequence.getCombatTarget(targetPtr)) - { - return targetPtr.getTypeName() == typeid(ESM::Creature).name(); - } - return false; - } - float CreatureStats::getEvasion() const { float evasion = (getAttribute(ESM::Attribute::Agility).getModified() / 5.0f) + @@ -359,6 +351,16 @@ namespace MWMechanics return mLastHitObject; } + void CreatureStats::setLastHitAttemptObject(const std::string& objectid) + { + mLastHitAttemptObject = objectid; + } + + const std::string &CreatureStats::getLastHitAttemptObject() const + { + return mLastHitAttemptObject; + } + void CreatureStats::addToFallHeight(float height) { mFallHeight += height; @@ -495,7 +497,10 @@ namespace MWMechanics state.mDead = mDead; state.mDied = mDied; state.mMurdered = mMurdered; - state.mFriendlyHits = mFriendlyHits; + // The vanilla engine does not store friendly hits in the save file. Since there's no other mechanism + // that ever resets the friendly hits (at least not to my knowledge) this should be regarded a feature + // rather than a bug. + //state.mFriendlyHits = mFriendlyHits; state.mTalkedTo = mTalkedTo; state.mAlarmed = mAlarmed; state.mAttacked = mAttacked; @@ -510,6 +515,7 @@ namespace MWMechanics state.mAttackStrength = mAttackStrength; state.mFallHeight = mFallHeight; // TODO: vertical velocity (move from PhysicActor to CreatureStats?) state.mLastHitObject = mLastHitObject; + state.mLastHitAttemptObject = mLastHitAttemptObject; state.mRecalcDynamicStats = mRecalcMagicka; state.mDrawState = mDrawState; state.mLevel = mLevel; @@ -543,7 +549,6 @@ namespace MWMechanics mDead = state.mDead; mDied = state.mDied; mMurdered = state.mMurdered; - mFriendlyHits = state.mFriendlyHits; mTalkedTo = state.mTalkedTo; mAlarmed = state.mAlarmed; mAttacked = state.mAttacked; @@ -558,6 +563,7 @@ namespace MWMechanics mAttackStrength = state.mAttackStrength; mFallHeight = state.mFallHeight; mLastHitObject = state.mLastHitObject; + mLastHitAttemptObject = state.mLastHitAttemptObject; mRecalcMagicka = state.mRecalcDynamicStats; mDrawState = DrawState_(state.mDrawState); mLevel = state.mLevel; @@ -636,7 +642,7 @@ namespace MWMechanics mDeathAnimation = index; } - std::map& CreatureStats::getSummonedCreatureMap() + std::map& CreatureStats::getSummonedCreatureMap() { return mSummonedCreatures; } diff --git a/apps/openmw/mwmechanics/creaturestats.hpp b/apps/openmw/mwmechanics/creaturestats.hpp index f830dd310c..145eb8a5bf 100644 --- a/apps/openmw/mwmechanics/creaturestats.hpp +++ b/apps/openmw/mwmechanics/creaturestats.hpp @@ -52,6 +52,7 @@ namespace MWMechanics float mFallHeight; std::string mLastHitObject; // The last object to hit this actor + std::string mLastHitAttemptObject; // The last object to attempt to hit this actor bool mRecalcMagicka; @@ -66,8 +67,11 @@ namespace MWMechanics // The index of the death animation that was played unsigned char mDeathAnimation; - // - std::map mSummonedCreatures; + public: + typedef std::pair SummonKey; // + private: + std::map mSummonedCreatures; // + // Contains ActorIds of summoned creatures with an expired lifetime that have not been deleted yet. // This may be necessary when the creature is in an inactive cell. std::vector mSummonGraveyard; @@ -198,8 +202,6 @@ namespace MWMechanics bool getAttacked() const; void setAttacked (bool attacked); - bool getCreatureTargetted() const; - float getEvasion() const; void setKnockedDown(bool value); @@ -217,8 +219,8 @@ namespace MWMechanics void setBlock(bool value); bool getBlock() const; - std::map& getSummonedCreatureMap(); - std::vector& getSummonedCreatureGraveyard(); + std::map& getSummonedCreatureMap(); // + std::vector& getSummonedCreatureGraveyard(); // ActorIds enum Flag { @@ -241,7 +243,9 @@ namespace MWMechanics bool getStance (Stance flag) const; void setLastHitObject(const std::string &objectid); + void setLastHitAttemptObject(const std::string &objectid); const std::string &getLastHitObject() const; + const std::string &getLastHitAttemptObject() const; // Note, this is just a cache to avoid checking the whole container store every frame. We don't need to store it in saves. // TODO: Put it somewhere else? diff --git a/apps/openmw/mwmechanics/enchanting.cpp b/apps/openmw/mwmechanics/enchanting.cpp index c134942b88..de5921a702 100644 --- a/apps/openmw/mwmechanics/enchanting.cpp +++ b/apps/openmw/mwmechanics/enchanting.cpp @@ -2,10 +2,12 @@ #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "creaturestats.hpp" #include "npcstats.hpp" +#include "spellcasting.hpp" namespace MWMechanics { @@ -55,7 +57,7 @@ namespace MWMechanics enchantment.mData.mCharge = getGemCharge(); enchantment.mData.mAutocalc = 0; enchantment.mData.mType = mCastStyle; - enchantment.mData.mCost = getEnchantPoints(); + enchantment.mData.mCost = getBaseCastCost(); store.remove(mSoulGemPtr, 1, player); @@ -65,7 +67,7 @@ namespace MWMechanics if(mSelfEnchanting) { - if(std::rand()/static_cast (RAND_MAX)*100 < getEnchantChance()) + if(getEnchantChance() (RAND_MAX)*100) return false; mEnchanter.getClass().skillUsageSucceeded (mEnchanter, ESM::Skill::Enchant, 2); @@ -156,7 +158,7 @@ namespace MWMechanics * * Formula on UESPWiki is not entirely correct. */ - float Enchanting::getEnchantPoints() const + int Enchanting::getEnchantPoints() const { if (mEffectList.mList.empty()) // No effects added, cost = 0 @@ -170,52 +172,50 @@ namespace MWMechanics for (std::vector::const_iterator it = mEffects.begin(); it != mEffects.end(); ++it) { float baseCost = (store.get().find(it->mEffectID))->mData.mBaseCost; - // To reflect vanilla behavior int magMin = (it->mMagnMin == 0) ? 1 : it->mMagnMin; int magMax = (it->mMagnMax == 0) ? 1 : it->mMagnMax; int area = (it->mArea == 0) ? 1 : it->mArea; - float magnitudeCost = 0; + float magnitudeCost = (magMin + magMax) * baseCost * 0.05; if (mCastStyle == ESM::Enchantment::ConstantEffect) { - magnitudeCost = (magMin + magMax) * baseCost * 2.5; + magnitudeCost *= store.get().find("fEnchantmentConstantDurationMult")->getFloat(); } else { - magnitudeCost = (magMin + magMax) * it->mDuration * baseCost * 0.025; - if(it->mRange == ESM::RT_Target) - magnitudeCost *= 1.5; + magnitudeCost *= it->mDuration; } - float areaCost = area * 0.025 * baseCost; - if (it->mRange == ESM::RT_Target) - areaCost *= 1.5; + float areaCost = area * 0.05 * baseCost; - enchantmentCost += (magnitudeCost + areaCost) * effectsLeftCnt; + const float fEffectCostMult = store.get().find("fEffectCostMult")->getFloat(); + + float cost = (magnitudeCost + areaCost) * fEffectCostMult; + if (it->mRange == ESM::RT_Target) + cost *= 1.5; + + enchantmentCost += cost * effectsLeftCnt; + enchantmentCost = std::max(1.f, enchantmentCost); --effectsLeftCnt; } - return enchantmentCost; + return static_cast(enchantmentCost); } - float Enchanting::getCastCost() const + int Enchanting::getBaseCastCost() const { if (mCastStyle == ESM::Enchantment::ConstantEffect) return 0; - const float enchantCost = getEnchantPoints(); + return getEnchantPoints(); + } + + int Enchanting::getEffectiveCastCost() const + { + int baseCost = getBaseCastCost(); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - MWMechanics::NpcStats &stats = player.getClass().getNpcStats(player); - int eSkill = stats.getSkill(ESM::Skill::Enchant).getModified(); - - /* - * Each point of enchant skill above/under 10 subtracts/adds - * one percent of enchantment cost while minimum is 1. - */ - const float castCost = enchantCost - (enchantCost / 100) * (eSkill - 10); - - return (castCost < 1) ? 1 : castCost; + return getEffectiveEnchantmentCastCost(baseCost, player); } @@ -240,7 +240,7 @@ namespace MWMechanics return soul->mData.mSoul; } - float Enchanting::getMaxEnchantValue() const + int Enchanting::getMaxEnchantValue() const { if (itemEmpty()) return 0; diff --git a/apps/openmw/mwmechanics/enchanting.hpp b/apps/openmw/mwmechanics/enchanting.hpp index 01ca1e0e1d..2c05d2d2e9 100644 --- a/apps/openmw/mwmechanics/enchanting.hpp +++ b/apps/openmw/mwmechanics/enchanting.hpp @@ -35,10 +35,11 @@ namespace MWMechanics bool create(); //Return true if created, false if failed. void nextCastStyle(); //Set enchant type to next possible type (for mOldItemPtr object) int getCastStyle() const; - float getEnchantPoints() const; - float getCastCost() const; + int getEnchantPoints() const; + int getBaseCastCost() const; // To be saved in the enchantment's record + int getEffectiveCastCost() const; // Effective cost taking player Enchant skill into account, used for preview purposes in the UI int getEnchantPrice() const; - float getMaxEnchantValue() const; + int getMaxEnchantValue() const; int getGemCharge() const; float getEnchantChance() const; bool soulEmpty() const; //Return true if empty diff --git a/apps/openmw/mwmechanics/levelledlist.hpp b/apps/openmw/mwmechanics/levelledlist.hpp index 5d9e291181..691996410c 100644 --- a/apps/openmw/mwmechanics/levelledlist.hpp +++ b/apps/openmw/mwmechanics/levelledlist.hpp @@ -2,6 +2,7 @@ #define OPENMW_MECHANICS_LEVELLEDLIST_H #include "../mwworld/ptr.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" #include "../mwbase/world.hpp" @@ -12,9 +13,9 @@ 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) + inline std::string getLevelledItem (const ESM::LevelledListBase* levItem, bool creature, unsigned char failChance=0) { - const std::vector& items = levItem->mList; + const std::vector& items = levItem->mList; const MWWorld::Ptr& player = MWBase::Environment::get().getWorld()->getPlayerPtr(); int playerLevel = player.getClass().getCreatureStats(player).getLevel(); @@ -27,7 +28,7 @@ namespace MWMechanics std::vector candidates; int highestLevel = 0; - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) { if (it->mLevel > highestLevel && it->mLevel <= playerLevel) highestLevel = it->mLevel; @@ -39,7 +40,7 @@ namespace MWMechanics allLevels = levItem->mFlags & ESM::CreatureLevList::AllLevels; std::pair highest = std::make_pair(-1, ""); - for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) + for (std::vector::const_iterator it = items.begin(); it != items.end(); ++it) { if (playerLevel >= it->mLevel && (allLevels || it->mLevel == highestLevel)) @@ -70,9 +71,9 @@ namespace MWMechanics else { if (ref.getPtr().getTypeName() == typeid(ESM::ItemLevList).name()) - return getLevelledItem(ref.getPtr().get()->mBase, failChance); + return getLevelledItem(ref.getPtr().get()->mBase, false, failChance); else - return getLevelledItem(ref.getPtr().get()->mBase, failChance); + return getLevelledItem(ref.getPtr().get()->mBase, true, failChance); } } diff --git a/apps/openmw/mwmechanics/magiceffects.hpp b/apps/openmw/mwmechanics/magiceffects.hpp index 0a8392dabd..c384d0857f 100644 --- a/apps/openmw/mwmechanics/magiceffects.hpp +++ b/apps/openmw/mwmechanics/magiceffects.hpp @@ -73,8 +73,8 @@ namespace MWMechanics struct EffectSourceVisitor { virtual void visit (MWMechanics::EffectKey key, - const std::string& sourceName, int casterActorId, - float magnitude, float remainingTime = -1) = 0; + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -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 9cafe9b3ce..4e4a1a8a69 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.cpp @@ -2,6 +2,8 @@ #include "mechanicsmanagerimp.hpp" #include "npcstats.hpp" +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" @@ -23,6 +25,33 @@ #include +namespace +{ + + float getFightDistanceBias(const MWWorld::Ptr& actor1, const MWWorld::Ptr& actor2) + { + Ogre::Vector3 pos1 (actor1.getRefData().getPosition().pos); + Ogre::Vector3 pos2 (actor2.getRefData().getPosition().pos); + + float d = pos1.distance(pos2); + + static const int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( + "iFightDistanceBase")->getInt(); + static const float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDistanceMultiplier")->getFloat(); + + return (iFightDistanceBase - fFightDistanceMultiplier * d); + } + + float getFightDispositionBias(float disposition) + { + static const float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( + "fFightDispMult")->getFloat(); + return ((50.f - disposition) * fFightDispMult); + } + +} + namespace MWMechanics { void MechanicsManager::buildPlayer() @@ -445,6 +474,7 @@ namespace MWMechanics void MechanicsManager::rest(bool sleep) { mActors.restoreDynamicStats (sleep); + mActors.fastForwardAi(); } int MechanicsManager::getHoursToRest() const @@ -544,8 +574,7 @@ namespace MWMechanics float reaction = 0; int rank = 0; - std::string npcFaction = ""; - if(!npcSkill.getFactionRanks().empty()) npcFaction = npcSkill.getFactionRanks().begin()->first; + std::string npcFaction = ptr.getClass().getPrimaryFaction(ptr); Misc::StringUtils::toLower(npcFaction); @@ -846,31 +875,31 @@ namespace MWMechanics mAI = true; } - bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim) + bool MechanicsManager::isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim) { - const std::string& owner = item.getCellRef().getOwner(); + const std::string& owner = cellref.getOwner(); bool isOwned = !owner.empty() && owner != "player"; - const std::string& faction = item.getCellRef().getFaction(); + const std::string& faction = cellref.getFaction(); bool isFactionOwned = false; if (!faction.empty() && ptr.getClass().isNpc()) { const std::map& factions = ptr.getClass().getNpcStats(ptr).getFactionRanks(); std::map::const_iterator found = factions.find(Misc::StringUtils::lowerCase(faction)); if (found == factions.end() - || found->second < item.getCellRef().getFactionRank()) + || found->second < cellref.getFactionRank()) isFactionOwned = true; } - const std::string& globalVariable = item.getCellRef().getGlobalVariable(); + const std::string& globalVariable = cellref.getGlobalVariable(); if (!globalVariable.empty() && MWBase::Environment::get().getWorld()->getGlobalInt(Misc::StringUtils::lowerCase(globalVariable)) == 1) { isOwned = false; isFactionOwned = false; } - if (!item.getCellRef().getOwner().empty()) - victim = MWBase::Environment::get().getWorld()->searchPtr(item.getCellRef().getOwner(), true); + if (!cellref.getOwner().empty()) + victim = MWBase::Environment::get().getWorld()->searchPtr(cellref.getOwner(), true); return (!isOwned && !isFactionOwned); } @@ -889,7 +918,7 @@ namespace MWMechanics } MWWorld::Ptr victim; - if (isAllowedToUse(ptr, bed, victim)) + if (isAllowedToUse(ptr, bed.getCellRef(), victim)) return false; if(commitCrime(ptr, victim, OT_SleepingInOwnedBed)) @@ -904,37 +933,119 @@ namespace MWMechanics void MechanicsManager::objectOpened(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item) { MWWorld::Ptr victim; - if (isAllowedToUse(ptr, item, victim)) + if (isAllowedToUse(ptr, item.getCellRef(), victim)) return; commitCrime(ptr, victim, OT_Trespassing); } - void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, int count) + std::vector > MechanicsManager::getStolenItemOwners(const std::string& itemid) { - MWWorld::Ptr victim; - if (isAllowedToUse(ptr, item, victim)) + std::vector > result; + StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); + if (it == mStolenItems.end()) + return result; + else + { + const OwnerMap& owners = it->second; + for (OwnerMap::const_iterator ownerIt = owners.begin(); ownerIt != owners.end(); ++ownerIt) + result.push_back(std::make_pair(ownerIt->first.first, ownerIt->second)); + return result; + } + } + + bool MechanicsManager::isItemStolenFrom(const std::string &itemid, const std::string &ownerid) + { + StolenItemsMap::const_iterator it = mStolenItems.find(Misc::StringUtils::lowerCase(itemid)); + if (it == mStolenItems.end()) + return false; + const OwnerMap& owners = it->second; + OwnerMap::const_iterator ownerFound = owners.find(std::make_pair(Misc::StringUtils::lowerCase(ownerid), false)); + return ownerFound != owners.end(); + } + + void MechanicsManager::confiscateStolenItems(const MWWorld::Ptr &player, const MWWorld::Ptr &targetContainer) + { + MWWorld::ContainerStore& store = player.getClass().getContainerStore(player); + for (MWWorld::ContainerStoreIterator it = store.begin(); it != store.end(); ++it) + { + StolenItemsMap::iterator stolenIt = mStolenItems.find(Misc::StringUtils::lowerCase(it->getClass().getId(*it))); + if (stolenIt == mStolenItems.end()) + continue; + OwnerMap& owners = stolenIt->second; + int itemCount = it->getRefData().getCount(); + for (OwnerMap::iterator ownerIt = owners.begin(); ownerIt != owners.end();) + { + int toRemove = std::min(itemCount, ownerIt->second); + itemCount -= toRemove; + ownerIt->second -= toRemove; + if (ownerIt->second == 0) + owners.erase(ownerIt++); + else + ++ownerIt; + } + + int toMove = it->getRefData().getCount() - itemCount; + + targetContainer.getClass().getContainerStore(targetContainer).add(*it, toMove, targetContainer); + store.remove(*it, toMove, player); + } + // TODO: unhardcode the locklevel + targetContainer.getClass().lock(targetContainer,50); + } + + void MechanicsManager::itemTaken(const MWWorld::Ptr &ptr, const MWWorld::Ptr &item, const MWWorld::Ptr& container, + int count) + { + if (ptr != MWBase::Environment::get().getWorld()->getPlayerPtr()) return; + + MWWorld::Ptr victim; + + const MWWorld::CellRef* ownerCellRef = &item.getCellRef(); + if (!container.isEmpty()) + { + // Inherit the owner of the container + ownerCellRef = &container.getCellRef(); + } + else + { + if (!item.getCellRef().hasContentFile()) + { + // this is a manually placed item, which means it was already stolen + return; + } + } + + if (isAllowedToUse(ptr, *ownerCellRef, victim)) + return; + + Owner owner; + owner.first = ownerCellRef->getOwner(); + owner.second = false; + if (owner.first.empty()) + { + owner.first = ownerCellRef->getFaction(); + owner.second = true; + } + Misc::StringUtils::toLower(owner.first); + mStolenItems[Misc::StringUtils::lowerCase(item.getClass().getId(item))][owner] += count; + commitCrime(ptr, victim, OT_Theft, item.getClass().getValue(item) * count); } - bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) + bool MechanicsManager::commitCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg, bool victimAware) { - // NOTE: int arg can be from itemTaken() so DON'T modify it, since it is - // passed to reportCrime later on in this function. - // NOTE: victim may be empty // Only player can commit crime - if (player.getRefData().getHandle() != "player") + if (player != MWBase::Environment::get().getWorld()->getPlayerPtr()) return false; - const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); - - // Find all the actors within the alarm radius std::vector neighbors; Ogre::Vector3 from = Ogre::Vector3(player.getRefData().getPosition().pos); + const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); float radius = esmStore.get().find("fAlarmRadius")->getFloat(); mActors.getObjectsInRange(from, radius, neighbors); @@ -943,11 +1054,8 @@ namespace MWMechanics if (!victim.isEmpty() && from.squaredDistance(Ogre::Vector3(victim.getRefData().getPosition().pos)) > radius*radius) neighbors.push_back(victim); - bool victimAware = false; - - // Find actors who directly witnessed the crime + // Did anyone see it? bool crimeSeen = false; - bool reported = false; for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) { if (*it == player) @@ -955,18 +1063,16 @@ namespace MWMechanics if (it->getClass().getCreatureStats(*it).isDead()) continue; - // Was the crime seen? - if ((MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) + if ((*it == victim && victimAware) + || (MWBase::Environment::get().getWorld()->getLOS(player, *it) && awarenessCheck(player, *it) ) // Murder crime can be reported even if no one saw it (hearing is enough, I guess). // TODO: Add mod support for stealth executions! || (type == OT_Murder && *it != victim)) { - if (*it == victim) - victimAware = true; - - // TODO: are there other messages? - if (type == OT_Theft) + if (type == OT_Theft || type == OT_Pickpocket) MWBase::Environment::get().getDialogueManager()->say(*it, "thief"); + else if (type == OT_Trespassing) + MWBase::Environment::get().getDialogueManager()->say(*it, "intruder"); // Crime reporting only applies to NPCs if (!it->getClass().isNpc()) @@ -977,20 +1083,13 @@ namespace MWMechanics crimeSeen = true; } - - // Will the witness report the crime? - if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) - { - reported = true; - } } - if (crimeSeen && reported) + if (crimeSeen) reportCrime(player, victim, type, arg); - else if (victimAware && !victim.isEmpty() && type == OT_Assault) - startCombat(victim, player); - - return reported; + else if (type == OT_Assault && !victim.isEmpty()) + startCombat(victim, player); // TODO: combat should be started with an "unaware" flag, which makes the victim flee? + return crimeSeen; } void MechanicsManager::reportCrime(const MWWorld::Ptr &player, const MWWorld::Ptr &victim, OffenseType type, int arg) @@ -1000,37 +1099,36 @@ namespace MWMechanics if (type == OT_Murder && !victim.isEmpty()) victim.getClass().getCreatureStats(victim).notifyMurder(); - // Bounty for each type of crime + // Bounty and disposition penalty for each type of crime + float disp = 0.f, dispVictim = 0.f; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) + { arg = store.find("iCrimeTresspass")->getInt(); + disp = dispVictim = store.find("iDispTresspass")->getInt(); + } else if (type == OT_Pickpocket) + { arg = store.find("iCrimePickPocket")->getInt(); + disp = dispVictim = store.find("fDispPickPocketMod")->getFloat(); + } else if (type == OT_Assault) + { arg = store.find("iCrimeAttack")->getInt(); + disp = store.find("iDispAttackMod")->getInt(); + dispVictim = store.find("fDispAttacking")->getFloat(); + } else if (type == OT_Murder) + { arg = store.find("iCrimeKilling")->getInt(); + disp = dispVictim = store.find("iDispKilling")->getInt(); + } else if (type == OT_Theft) { + disp = dispVictim = store.find("fDispStealing")->getFloat() * arg; arg *= store.find("fCrimeStealing")->getFloat(); arg = std::max(1, arg); // Minimum bounty of 1, in case items with zero value are stolen } - MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); - player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() - + arg); - - // 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 (player.getClass().getNpcStats(player).isSameFaction(victim.getClass().getNpcStats(victim))) - { - player.getClass().getNpcStats(player).expell(factionID); - } - } - // Make surrounding actors within alarm distance respond to the crime std::vector neighbors; @@ -1049,19 +1147,25 @@ namespace MWMechanics // What amount of provocation did this crime generate? // Controls whether witnesses will engage combat with the criminal. - int fight = 0; + int fight = 0, fightVictim = 0; if (type == OT_Trespassing || type == OT_SleepingInOwnedBed) - fight = esmStore.get().find("iFightTrespass")->getInt(); + fight = fightVictim = esmStore.get().find("iFightTrespass")->getInt(); else if (type == OT_Pickpocket) + { fight = esmStore.get().find("iFightPickpocket")->getInt(); - else if (type == OT_Assault) // Note: iFightAttack is for the victim, iFightAttacking for witnesses? - fight = esmStore.get().find("iFightAttack")->getInt(); + fightVictim = esmStore.get().find("iFightPickpocket")->getInt() * 4; // *4 according to research wiki + } + else if (type == OT_Assault) + { + fight = esmStore.get().find("iFightAttacking")->getInt(); + fightVictim = esmStore.get().find("iFightAttack")->getInt(); + } else if (type == OT_Murder) - fight = esmStore.get().find("iFightKilling")->getInt(); + fight = fightVictim = esmStore.get().find("iFightKilling")->getInt(); else if (type == OT_Theft) - fight = esmStore.get().find("fFightStealing")->getFloat(); + fight = fightVictim = esmStore.get().find("fFightStealing")->getFloat(); - const int iFightAttacking = esmStore.get().find("iFightAttacking")->getInt(); + bool reported = false; // Tell everyone (including the original reporter) in alarm range for (std::vector::iterator it = neighbors.begin(); it != neighbors.end(); ++it) @@ -1069,15 +1173,15 @@ namespace MWMechanics if ( *it == player || !it->getClass().isNpc() || it->getClass().getCreatureStats(*it).isDead()) continue; - int aggression = fight; - - // Note: iFightAttack is used for the victim, iFightAttacking for witnesses? - if (*it != victim && type == OT_Assault) - aggression = iFightAttacking; - if (it->getClass().getCreatureStats(*it).getAiSequence().isInCombat(victim)) continue; + // Will the witness report the crime? + if (it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase() >= 100) + { + reported = true; + } + if (it->getClass().isClass(*it, "guard")) { // Mark as Alarmed for dialogue @@ -1091,20 +1195,77 @@ namespace MWMechanics } else { - bool aggressive = MWBase::Environment::get().getMechanicsManager()->isAggressive(*it, player, aggression, true); - if (aggressive) + float dispTerm = (*it == victim) ? dispVictim : disp; + + float alarmTerm = 0.01 * it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Alarm).getBase(); + if (type == OT_Pickpocket && alarmTerm <= 0) + alarmTerm = 1.0; + + if (*it != victim) + dispTerm *= alarmTerm; + + float fightTerm = (*it == victim) ? fightVictim : fight; + fightTerm += getFightDispositionBias(dispTerm); + fightTerm += getFightDistanceBias(*it, player); + fightTerm *= alarmTerm; + + int observerFightRating = it->getClass().getCreatureStats(*it).getAiSetting(CreatureStats::AI_Fight).getBase(); + if (observerFightRating + fightTerm > 100) + fightTerm = 100 - observerFightRating; + fightTerm = std::max(0.f, fightTerm); + + if (observerFightRating + fightTerm >= 100) { startCombat(*it, player); + NpcStats& observerStats = it->getClass().getNpcStats(*it); + // Apply aggression value to the base Fight rating, so that the actor can continue fighting + // after a Calm spell wears off + observerStats.setAiSetting(CreatureStats::AI_Fight, observerFightRating + fightTerm); + + observerStats.setBaseDisposition(observerStats.getBaseDisposition()+dispTerm); + // Set the crime ID, which we will use to calm down participants // once the bounty has been paid. - it->getClass().getNpcStats(*it).setCrimeId(id); + observerStats.setCrimeId(id); // Mark as Alarmed for dialogue - it->getClass().getCreatureStats(*it).setAlarmed(true); + observerStats.setAlarmed(true); } } } + + if (reported) + { + MWBase::Environment::get().getWindowManager()->messageBox("#{sCrimeMessage}"); + player.getClass().getNpcStats(player).setBounty(player.getClass().getNpcStats(player).getBounty() + + arg); + + // If committing a crime against a faction member, expell from the faction + if (!victim.isEmpty() && victim.getClass().isNpc()) + { + std::string factionID = victim.getClass().getPrimaryFaction(victim); + + const std::map& playerRanks = player.getClass().getNpcStats(player).getFactionRanks(); + if (playerRanks.find(Misc::StringUtils::lowerCase(factionID)) != playerRanks.end()) + { + player.getClass().getNpcStats(player).expell(factionID); + } + } + + if (type == OT_Assault && !victim.isEmpty() + && !victim.getClass().getCreatureStats(victim).getAiSequence().isInCombat(player) + && victim.getClass().isNpc()) + { + // Attacker is in combat with us, but we are not in combat with the attacker yet. Time to fight back. + // Note: accidental or collateral damage attacks are ignored. + startCombat(victim, player); + + // Set the crime ID, which we will use to calm down participants + // once the bounty has been paid. + victim.getClass().getNpcStats(victim).setCrimeId(id); + } + } } bool MechanicsManager::actorAttacked(const MWWorld::Ptr &ptr, const MWWorld::Ptr &attacker) @@ -1155,7 +1316,7 @@ namespace MWMechanics bool MechanicsManager::awarenessCheck(const MWWorld::Ptr &ptr, const MWWorld::Ptr &observer) { - if (observer.getClass().getCreatureStats(observer).isDead()) + if (observer.getClass().getCreatureStats(observer).isDead() || !observer.getRefData().isEnabled()) return false; const MWWorld::Store& store = MWBase::Environment::get().getWorld()->getStore().get(); @@ -1232,7 +1393,7 @@ namespace MWMechanics // if guard starts combat with player, guards pursuing player should do the same if (ptr.getClass().isClass(ptr, "Guard")) { - for (Actors::PtrControllerMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter) + for (Actors::PtrActorMap::const_iterator iter = mActors.begin(); iter != mActors.end(); ++iter) { if (iter->first.getClass().isClass(iter->first, "Guard")) { @@ -1268,54 +1429,59 @@ namespace MWMechanics return mActors.getActorsFollowing(actor); } + std::list MechanicsManager::getActorsFollowingIndices(const MWWorld::Ptr& actor) + { + return mActors.getActorsFollowingIndices(actor); + } + std::list MechanicsManager::getActorsFighting(const MWWorld::Ptr& actor) { return mActors.getActorsFighting(actor); } int MechanicsManager::countSavedGameRecords() const { - return 1; // Death counter + return 1 // Death counter + +1; // Stolen items } void MechanicsManager::write(ESM::ESMWriter &writer, Loading::Listener &listener) const { mActors.write(writer, listener); + + ESM::StolenItems items; + items.mStolenItems = mStolenItems; + writer.startRecord(ESM::REC_STLN); + items.write(writer); + writer.endRecord(ESM::REC_STLN); } - void MechanicsManager::readRecord(ESM::ESMReader &reader, int32_t type) + void MechanicsManager::readRecord(ESM::ESMReader &reader, uint32_t type) { - mActors.readRecord(reader, type); + if (type == ESM::REC_STLN) + { + ESM::StolenItems items; + items.load(reader); + mStolenItems = items.mStolenItems; + } + else + mActors.readRecord(reader, type); } void MechanicsManager::clear() { mActors.clear(); + mStolenItems.clear(); } - bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target, int bias, bool ignoreDistance) + bool MechanicsManager::isAggressive(const MWWorld::Ptr &ptr, const MWWorld::Ptr &target) { - Ogre::Vector3 pos1 (ptr.getRefData().getPosition().pos); - Ogre::Vector3 pos2 (target.getRefData().getPosition().pos); - - float d = 0; - if (!ignoreDistance) - d = pos1.distance(pos2); - - static int iFightDistanceBase = MWBase::Environment::get().getWorld()->getStore().get().find( - "iFightDistanceBase")->getInt(); - static float fFightDistanceMultiplier = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDistanceMultiplier")->getFloat(); - static float fFightDispMult = MWBase::Environment::get().getWorld()->getStore().get().find( - "fFightDispMult")->getFloat(); - int disposition = 50; if (ptr.getClass().isNpc()) disposition = getDerivedDisposition(ptr); int fight = std::max(0.f, ptr.getClass().getCreatureStats(ptr).getAiSetting(CreatureStats::AI_Fight).getModified() - + (iFightDistanceBase - fFightDistanceMultiplier * d) - + ((50 - disposition) * fFightDispMult)) - + bias; + + getFightDistanceBias(ptr, target) + + getFightDispositionBias(disposition)); if (ptr.getClass().isNpc() && target.getClass().isNpc()) { @@ -1347,4 +1513,9 @@ namespace MWMechanics stats.resurrect(); } } + + bool MechanicsManager::isReadyToBlock(const MWWorld::Ptr &ptr) const + { + return mActors.isReadyToBlock(ptr); + } } diff --git a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp index 9f9e85c5af..d08334ae8c 100644 --- a/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp +++ b/apps/openmw/mwmechanics/mechanicsmanagerimp.hpp @@ -35,6 +35,11 @@ namespace MWMechanics Objects mObjects; Actors mActors; + typedef std::pair Owner; // < Owner id, bool isFaction > + typedef std::map OwnerMap; // < Owner, number of stolen items with this id from this owner > + typedef std::map StolenItemsMap; + StolenItemsMap mStolenItems; + public: void buildPlayer(); @@ -110,29 +115,26 @@ namespace MWMechanics virtual void startCombat (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target); /** - * @brief Commit a crime. If any actors witness the crime and report it, - * reportCrime will be called automatically. * @note victim may be empty * @param arg Depends on \a type, e.g. for Theft, the value of the item that was stolen. - * @return was the crime reported? + * @param victimAware Is the victim already aware of the crime? + * If this parameter is false, it will be determined by a line-of-sight and awareness check. + * @return was the crime seen? */ 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); + OffenseType type, int arg=0, bool victimAware=false); /// @return false if the attack was considered a "friendly hit" and forgiven virtual bool actorAttacked (const MWWorld::Ptr& victim, const MWWorld::Ptr& attacker); /// 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); + /// @param container The container the item is in; may be empty for an item in the world + virtual void itemTaken (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, const MWWorld::Ptr& container, + 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? Also returns fail when enemies are nearby virtual bool sleepInBed (const MWWorld::Ptr& ptr, const MWWorld::Ptr& bed); - /// @return is \a ptr allowed to take/use \a item or is it a crime? - virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::Ptr& item, MWWorld::Ptr& victim); - virtual void forceStateUpdate(const MWWorld::Ptr &ptr); virtual void playAnimationGroup(const MWWorld::Ptr& ptr, const std::string& groupName, int mode, int number); @@ -147,6 +149,7 @@ namespace MWMechanics virtual void getActorsInRange(const Ogre::Vector3 &position, float radius, std::vector &objects); virtual std::list getActorsFollowing(const MWWorld::Ptr& actor); + virtual std::list getActorsFollowingIndices(const MWWorld::Ptr& actor); virtual std::list getActorsFighting(const MWWorld::Ptr& actor); @@ -159,15 +162,31 @@ namespace MWMechanics virtual void write (ESM::ESMWriter& writer, Loading::Listener& listener) const; - virtual void readRecord (ESM::ESMReader& reader, int32_t type); + virtual void readRecord (ESM::ESMReader& reader, uint32_t type); virtual void clear(); - /// @param bias Can be used to add an additional aggression bias towards the target, - /// making it more likely for the function to return true. - virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target, int bias=0, bool ignoreDistance=false); + virtual bool isAggressive (const MWWorld::Ptr& ptr, const MWWorld::Ptr& target); virtual void keepPlayerAlive(); + + virtual bool isReadyToBlock (const MWWorld::Ptr& ptr) const; + + virtual void confiscateStolenItems (const MWWorld::Ptr& player, const MWWorld::Ptr& targetContainer); + + /// List the owners that the player has stolen this item from (the owner can be an NPC or a faction). + /// + virtual std::vector > getStolenItemOwners(const std::string& itemid); + + /// Has the player stolen this item from the given owner? + virtual bool isItemStolenFrom(const std::string& itemid, const std::string& ownerid); + + private: + void reportCrime (const MWWorld::Ptr& ptr, const MWWorld::Ptr& victim, + OffenseType type, int arg=0); + + /// @return is \a ptr allowed to take/use \a cellref or is it a crime? + virtual bool isAllowedToUse (const MWWorld::Ptr& ptr, const MWWorld::CellRef& cellref, MWWorld::Ptr& victim); }; } diff --git a/apps/openmw/mwmechanics/npcstats.cpp b/apps/openmw/mwmechanics/npcstats.cpp index 13fc14318c..58bf11cfff 100644 --- a/apps/openmw/mwmechanics/npcstats.cpp +++ b/apps/openmw/mwmechanics/npcstats.cpp @@ -31,9 +31,7 @@ MWMechanics::NpcStats::NpcStats() , mReputation(0) , mCrimeId(-1) , mWerewolfKills (0) -, mProfit(0) , mTimeToStartDrowning(20.0) -, mLastDrowningHit(0) { mSkillIncreases.resize (ESM::Attribute::Length, 0); } @@ -92,12 +90,6 @@ void MWMechanics::NpcStats::lowerRank(const std::string &faction) } } -void MWMechanics::NpcStats::setFactionRank(const std::string &faction, int rank) -{ - const std::string lower = Misc::StringUtils::lowerCase(faction); - mFactionRank[lower] = rank; -} - void MWMechanics::NpcStats::joinFaction(const std::string& faction) { const std::string lower = Misc::StringUtils::lowerCase(faction); @@ -128,42 +120,29 @@ void MWMechanics::NpcStats::clearExpelled(const std::string& factionID) mExpelled.erase(Misc::StringUtils::lowerCase(factionID)); } -bool MWMechanics::NpcStats::isSameFaction (const NpcStats& npcStats) const +bool MWMechanics::NpcStats::isInFaction (const std::string& faction) const { - for (std::map::const_iterator iter (mFactionRank.begin()); iter!=mFactionRank.end(); - ++iter) - if (npcStats.mFactionRank.find (iter->first)!=npcStats.mFactionRank.end()) - return true; - - return false; + return (mFactionRank.find(Misc::StringUtils::lowerCase(faction)) != mFactionRank.end()); } -float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& class_, int usageType, - int level, float extraFactor) const +int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const { - if (level<0) - level = static_cast (getSkill (skillIndex).getBase()); + std::map::const_iterator iter = mFactionReputation.find (Misc::StringUtils::lowerCase(faction)); - const ESM::Skill *skill = - MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); + if (iter==mFactionReputation.end()) + return 0; - float skillFactor = 1; + return iter->second; +} - if (usageType>=4) - throw std::runtime_error ("skill usage type out of range"); +void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, int value) +{ + mFactionReputation[Misc::StringUtils::lowerCase(faction)] = value; +} - if (usageType>=0) - { - skillFactor = skill->mData.mUseValue[usageType]; - - if (skillFactor<0) - throw std::runtime_error ("invalid skill gain factor"); - - if (skillFactor==0) - return 0; - } - - skillFactor *= extraFactor; +float MWMechanics::NpcStats::getSkillProgressRequirement (int skillIndex, const ESM::Class& class_) const +{ + float progressRequirement = 1 + getSkill (skillIndex).getBase(); const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); @@ -186,11 +165,15 @@ float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& cla break; } + progressRequirement *= typeFactor; + if (typeFactor<=0) throw std::runtime_error ("invalid skill type factor"); float specialisationFactor = 1; + const ESM::Skill *skill = + MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); if (skill->mData.mSpecialization==class_.mData.mSpecialization) { specialisationFactor = gmst.find ("fSpecialSkillBonus")->getFloat(); @@ -198,7 +181,9 @@ float MWMechanics::NpcStats::getSkillGain (int skillIndex, const ESM::Class& cla if (specialisationFactor<=0) throw std::runtime_error ("invalid skill specialisation factor"); } - return 1.0 / ((level+1) * (1.0/skillFactor) * typeFactor * specialisationFactor); + progressRequirement *= specialisationFactor; + + return progressRequirement; } void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, int usageType, float extraFactor) @@ -207,13 +192,26 @@ void MWMechanics::NpcStats::useSkill (int skillIndex, const ESM::Class& class_, if(mIsWerewolf) return; + const ESM::Skill *skill = + MWBase::Environment::get().getWorld()->getStore().get().find (skillIndex); + float skillGain = 1; + if (usageType>=4) + throw std::runtime_error ("skill usage type out of range"); + if (usageType>=0) + { + skillGain = skill->mData.mUseValue[usageType]; + if (skillGain<0) + throw std::runtime_error ("invalid skill gain factor"); + } + skillGain *= extraFactor; + MWMechanics::SkillValue& value = getSkill (skillIndex); - value.setProgress(value.getProgress() + getSkillGain (skillIndex, class_, usageType, -1, extraFactor)); + value.setProgress(value.getProgress() + skillGain); - if (value.getProgress()>=1) + if (int(value.getProgress())>=int(getSkillProgressRequirement(skillIndex, class_))) { - // skill leveled up + // skill levelled up increaseSkill(skillIndex, class_, false); } } @@ -257,21 +255,19 @@ void MWMechanics::NpcStats::increaseSkill(int skillIndex, const ESM::Class &clas /// \todo check if character is the player, if levelling is ever implemented for NPCs MWBase::Environment::get().getSoundManager ()->playSound ("skillraise", 1, 1); - std::vector noButtons; - std::stringstream message; message << boost::format(MWBase::Environment::get().getWindowManager ()->getGameSettingString ("sNotifyMessage39", "")) % std::string("#{" + ESM::Skill::sSkillNameIds[skillIndex] + "}") % static_cast (base); - MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), noButtons, MWGui::ShowInDialogueMode_Never); + MWBase::Environment::get().getWindowManager ()->messageBox(message.str(), MWGui::ShowInDialogueMode_Never); if (mLevelProgress >= gmst.find("iLevelUpTotal")->getInt()) { // levelup is possible now - MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", noButtons, MWGui::ShowInDialogueMode_Never); + MWBase::Environment::get().getWindowManager ()->messageBox ("#{sLevelUpMsg}", MWGui::ShowInDialogueMode_Never); } - getSkill (skillIndex).setBase (base); + getSkill(skillIndex).setBase (base); if (!preserveProgress) getSkill(skillIndex).setProgress(0); } @@ -283,13 +279,15 @@ int MWMechanics::NpcStats::getLevelProgress () const void MWMechanics::NpcStats::levelUp() { - mLevelProgress -= 10; - for (int i=0; i &gmst = MWBase::Environment::get().getWorld()->getStore().get(); + mLevelProgress -= gmst.find("iLevelUpTotal")->getInt(); + mLevelProgress = std::max(0, mLevelProgress); // might be necessary when levelup was invoked via console + + for (int i=0; igetStore().get().find("iWereWolfBounty")->getInt(); - else - return mBounty; + return mBounty; } void MWMechanics::NpcStats::setBounty (int bounty) { - if (!mIsWerewolf) - mBounty = bounty; -} - -int MWMechanics::NpcStats::getFactionReputation (const std::string& faction) const -{ - std::map::const_iterator iter = mFactionReputation.find (faction); - - if (iter==mFactionReputation.end()) - return 0; - - return iter->second; -} - -void MWMechanics::NpcStats::setFactionReputation (const std::string& faction, int value) -{ - mFactionReputation[faction] = value; + mBounty = bounty; } int MWMechanics::NpcStats::getReputation() const @@ -466,16 +445,6 @@ void MWMechanics::NpcStats::addWerewolfKill() ++mWerewolfKills; } -int MWMechanics::NpcStats::getProfit() const -{ - return mProfit; -} - -void MWMechanics::NpcStats::modifyProfit(int diff) -{ - mProfit += diff; -} - float MWMechanics::NpcStats::getTimeToStartDrowning() const { return mTimeToStartDrowning; @@ -519,7 +488,6 @@ void MWMechanics::NpcStats::writeState (ESM::NpcStats& state) const state.mReputation = mReputation; state.mWerewolfKills = mWerewolfKills; - state.mProfit = mProfit; state.mLevelProgress = mLevelProgress; for (int i=0; ifirst] = iter->second.mRank; if (iter->second.mReputation) - mFactionReputation[iter->first] = iter->second.mReputation; + mFactionReputation[Misc::StringUtils::lowerCase(iter->first)] = iter->second.mReputation; } mDisposition = state.mDisposition; @@ -567,7 +534,6 @@ void MWMechanics::NpcStats::readState (const ESM::NpcStats& state) mBounty = state.mBounty; mReputation = state.mReputation; mWerewolfKills = state.mWerewolfKills; - mProfit = state.mProfit; mLevelProgress = state.mLevelProgress; for (int i=0; i #include -#include "stat.hpp" - #include "creaturestats.hpp" namespace ESM @@ -22,41 +20,31 @@ namespace MWMechanics class NpcStats : public CreatureStats { - /// 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 mFactionRank; - int mDisposition; - SkillValue mSkill[ESM::Skill::Length]; + SkillValue mSkill[ESM::Skill::Length]; // SkillValue.mProgress used by the player only SkillValue mWerewolfSkill[ESM::Skill::Length]; - int mBounty; - std::set mExpelled; - std::map mFactionReputation; int mReputation; int mCrimeId; + + // ----- used by the player only, maybe should be moved at some point ------- + int mBounty; int mWerewolfKills; - int mProfit; - + /// Used for the player only; NPCs have maximum one faction defined in their NPC record + std::map mFactionRank; + std::set mExpelled; + std::map mFactionReputation; int mLevelProgress; // 0-10 - std::vector mSkillIncreases; // number of skill increases for each attribute - std::set mUsedIds; + // --------------------------------------------------------------------------- /// Countdown to getting damage while underwater float mTimeToStartDrowning; - /// time since last hit from drowning - float mLastDrowningHit; public: NpcStats(); - /// for mercenary companions. starts out as 0, and changes when items are added or removed through the UI. - int getProfit() const; - void modifyProfit(int diff); - int getBaseDisposition() const; void setBaseDisposition(int disposition); @@ -76,23 +64,15 @@ namespace MWMechanics void lowerRank(const std::string& faction); /// Join this faction, setting the initial rank to 0. void joinFaction(const std::string& faction); - /// Warning: this function performs no check whether the rank exists, - /// and should be used in initial actor setup only. - void setFactionRank(const std::string& faction, int rank); const std::set& 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? + bool isInFaction (const std::string& faction) const; - float getSkillGain (int skillIndex, const ESM::Class& class_, int usageType = -1, - int level = -1, float extraFactor=1.f) const; - ///< \param usageType: Usage specific factor, specified in the respective skill record; - /// -1: use a factor of 1.0 instead. - /// \param level Level to base calculation on; -1: use current level. + float getSkillProgressRequirement (int skillIndex, const ESM::Class& class_) const; void useSkill (int skillIndex, const ESM::Class& class_, int usageType = -1, float extraFactor=1.f); ///< Increase skill by usage. @@ -110,8 +90,10 @@ namespace MWMechanics /// Called at character creation. void flagAsUsed (const std::string& id); + ///< @note Id must be lower-case bool hasBeenUsed (const std::string& id) const; + ///< @note Id must be lower-case int getBounty() const; diff --git a/apps/openmw/mwmechanics/obstacle.cpp b/apps/openmw/mwmechanics/obstacle.cpp index 55ebfeab54..6bf81e8611 100644 --- a/apps/openmw/mwmechanics/obstacle.cpp +++ b/apps/openmw/mwmechanics/obstacle.cpp @@ -115,8 +115,8 @@ namespace MWMechanics if(mDistSameSpot == -1) mDistSameSpot = DIST_SAME_SPOT * (cls.getSpeed(actor) / 150); - bool samePosition = (abs(pos.pos[0] - mPrevX) < mDistSameSpot) && - (abs(pos.pos[1] - mPrevY) < mDistSameSpot); + bool samePosition = (std::abs(pos.pos[0] - mPrevX) < mDistSameSpot) && + (std::abs(pos.pos[1] - mPrevY) < mDistSameSpot); // update position mPrevX = pos.pos[0]; mPrevY = pos.pos[1]; diff --git a/apps/openmw/mwmechanics/obstacle.hpp b/apps/openmw/mwmechanics/obstacle.hpp index 76ab9d029b..e0ae9203d7 100644 --- a/apps/openmw/mwmechanics/obstacle.hpp +++ b/apps/openmw/mwmechanics/obstacle.hpp @@ -1,10 +1,6 @@ #ifndef OPENMW_MECHANICS_OBSTACLE_H #define OPENMW_MECHANICS_OBSTACLE_H -//#include "../mwbase/world.hpp" -//#include "../mwworld/class.hpp" -#include "../mwworld/cellstore.hpp" - namespace MWWorld { class Ptr; diff --git a/apps/openmw/mwmechanics/pathfinding.cpp b/apps/openmw/mwmechanics/pathfinding.cpp index f1279c415e..0634725a81 100644 --- a/apps/openmw/mwmechanics/pathfinding.cpp +++ b/apps/openmw/mwmechanics/pathfinding.cpp @@ -282,28 +282,13 @@ namespace MWMechanics return Ogre::Math::ATan2(directionX,directionY).valueDegrees(); } - bool PathFinder::checkWaypoint(float x, float y, float z) + bool PathFinder::checkPathCompleted(float x, float y, float z, float tolerance) { if(mPath.empty()) return true; ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) - { - mPath.pop_front(); - if(mPath.empty()) mIsPathConstructed = false; - return true; - } - return false; - } - - bool PathFinder::checkPathCompleted(float x, float y, float z) - { - if(mPath.empty()) - return true; - - ESM::Pathgrid::Point nextPoint = *mPath.begin(); - if(sqrDistanceZCorrected(nextPoint, x, y, z) < 64*64) + if(sqrDistanceZCorrected(nextPoint, x, y, z) < tolerance*tolerance) { mPath.pop_front(); if(mPath.empty()) diff --git a/apps/openmw/mwmechanics/pathfinding.hpp b/apps/openmw/mwmechanics/pathfinding.hpp index 482808dacd..7ba2d22ba6 100644 --- a/apps/openmw/mwmechanics/pathfinding.hpp +++ b/apps/openmw/mwmechanics/pathfinding.hpp @@ -39,11 +39,8 @@ namespace MWMechanics void buildPath(const ESM::Pathgrid::Point &startPoint, const ESM::Pathgrid::Point &endPoint, 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 + bool checkPathCompleted(float x, float y, float z, float tolerance=32.f); + ///< \Returns true if we are within \a tolerance units of the last path point. float getZAngleToNext(float x, float y) const; diff --git a/apps/openmw/mwmechanics/pathgrid.cpp b/apps/openmw/mwmechanics/pathgrid.cpp index 4983a4a4f2..9e50af2b8c 100644 --- a/apps/openmw/mwmechanics/pathgrid.cpp +++ b/apps/openmw/mwmechanics/pathgrid.cpp @@ -4,6 +4,7 @@ #include "../mwbase/environment.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" namespace { @@ -95,7 +96,7 @@ namespace MWMechanics * +----------------> * high cost */ - bool PathgridGraph::load(const ESM::Cell* cell) + bool PathgridGraph::load(const MWWorld::CellStore *cell) { if(!cell) return false; @@ -103,10 +104,9 @@ namespace MWMechanics if(mIsGraphConstructed) return true; - mCell = cell; - mIsExterior = cell->isExterior(); - mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell); - + mCell = cell->getCell(); + mIsExterior = cell->getCell()->isExterior(); + mPathgrid = MWBase::Environment::get().getWorld()->getStore().get().search(*cell->getCell()); if(!mPathgrid) return false; diff --git a/apps/openmw/mwmechanics/pathgrid.hpp b/apps/openmw/mwmechanics/pathgrid.hpp index 5d01dca009..2742957a68 100644 --- a/apps/openmw/mwmechanics/pathgrid.hpp +++ b/apps/openmw/mwmechanics/pathgrid.hpp @@ -21,7 +21,7 @@ namespace MWMechanics public: PathgridGraph(); - bool load(const ESM::Cell *cell); + bool load(const MWWorld::CellStore *cell); // returns true if end point is strongly connected (i.e. reachable // from start point) both start and end are pathgrid point indexes diff --git a/apps/openmw/mwmechanics/spellcasting.cpp b/apps/openmw/mwmechanics/spellcasting.cpp index d33bb6ad15..49887e560f 100644 --- a/apps/openmw/mwmechanics/spellcasting.cpp +++ b/apps/openmw/mwmechanics/spellcasting.cpp @@ -15,11 +15,13 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwrender/animation.hpp" #include "magiceffects.hpp" #include "npcstats.hpp" +#include "summoning.hpp" namespace { @@ -434,7 +436,7 @@ namespace MWMechanics float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; magnitude *= magnitudeMult; - bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration); + bool hasDuration = !(magicEffect->mData.mFlags & ESM::MagicEffect::NoDuration) && effectIt->mDuration > 0; if (target.getClass().isActor() && hasDuration) { ActiveSpells::ActiveEffect effect; @@ -469,6 +471,18 @@ namespace MWMechanics else applyInstantEffect(target, caster, EffectKey(*effectIt), magnitude); + // Re-casting a summon effect will remove the creature from previous castings of that effect. + if (isSummoningEffect(effectIt->mEffectID) && !target.isEmpty() && target.getClass().isActor()) + { + CreatureStats& targetStats = target.getClass().getCreatureStats(target); + std::map::iterator found = targetStats.getSummonedCreatureMap().find(std::make_pair(effectIt->mEffectID, mId)); + if (found != targetStats.getSummonedCreatureMap().end()) + { + cleanupSummonedCreature(targetStats, found->second); + targetStats.getSummonedCreatureMap().erase(found); + } + } + // 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? @@ -514,7 +528,7 @@ namespace MWMechanics } if (!exploded) - MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, mId, mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(mHitPosition, effects, caster, range, mId, mSourceName); if (!reflectedEffects.mList.empty()) inflict(caster, target, reflectedEffects, range, true, exploded); @@ -578,6 +592,30 @@ namespace MWMechanics value.restore(magnitude); target.getClass().getCreatureStats(target).setAttribute(attribute, value); } + else if (effectId == ESM::MagicEffect::DamageHealth) + { + applyDynamicStatsEffect(0, target, magnitude * -1); + } + else if (effectId == ESM::MagicEffect::RestoreHealth) + { + applyDynamicStatsEffect(0, target, magnitude); + } + else if (effectId == ESM::MagicEffect::DamageFatigue) + { + applyDynamicStatsEffect(2, target, magnitude * -1); + } + else if (effectId == ESM::MagicEffect::RestoreFatigue) + { + applyDynamicStatsEffect(2, target, magnitude); + } + else if (effectId == ESM::MagicEffect::DamageMagicka) + { + applyDynamicStatsEffect(1, target, magnitude * -1); + } + else if (effectId == ESM::MagicEffect::RestoreMagicka) + { + applyDynamicStatsEffect(1, target, magnitude); + } else if (effectId == ESM::MagicEffect::DamageSkill || effectId == ESM::MagicEffect::RestoreSkill) { if (target.getTypeName() != typeid(ESM::NPC).name()) @@ -639,6 +677,13 @@ namespace MWMechanics } } } + + void CastSpell::applyDynamicStatsEffect(int attribute, const MWWorld::Ptr& target, float magnitude) + { + DynamicStat value = target.getClass().getCreatureStats(target).getDynamic(attribute); + value.modify(magnitude); + target.getClass().getCreatureStats(target).setDynamic(attribute, value); + } bool CastSpell::cast(const std::string &id) { @@ -673,9 +718,7 @@ namespace MWMechanics // Check if there's enough charge left if (enchantment->mData.mType == ESM::Enchantment::WhenUsed || enchantment->mData.mType == ESM::Enchantment::WhenStrikes) { - const float enchantCost = enchantment->mData.mCost; - int eSkill = mCaster.getClass().getSkill(mCaster, ESM::Skill::Enchant); - const int castCost = std::max(1.f, enchantCost - (enchantCost / 100) * (eSkill - 10)); + const int castCost = getEffectiveEnchantmentCastCost(enchantment->mData.mCost, mCaster); if (item.getCellRef().getEnchantmentCharge() == -1) item.getCellRef().setEnchantmentCharge(enchantment->mData.mCharge); @@ -687,12 +730,11 @@ namespace MWMechanics // Failure sound int school = 0; - for (std::vector::const_iterator effectIt (enchantment->mEffects.mList.begin()); - effectIt!=enchantment->mEffects.mList.end(); ++effectIt) + if (!enchantment->mEffects.mList.empty()) { - const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectIt->mEffectID); + short effectId = enchantment->mEffects.mList.front().mEffectID; + const ESM::MagicEffect* magicEffect = MWBase::Environment::get().getWorld()->getStore().get().find(effectId); school = magicEffect->mData.mSchool; - break; } static const std::string schools[] = { "alteration", "conjuration", "destruction", "illusion", "mysticism", "restoration" @@ -900,4 +942,25 @@ namespace MWMechanics return true; } + + int getEffectiveEnchantmentCastCost(float castCost, const MWWorld::Ptr &actor) + { + /* + * Each point of enchant skill above/under 10 subtracts/adds + * one percent of enchantment cost while minimum is 1. + */ + int eSkill = actor.getClass().getSkill(actor, ESM::Skill::Enchant); + const float result = castCost - (castCost / 100) * (eSkill - 10); + + return static_cast((result < 1) ? 1 : result); + } + + bool isSummoningEffect(int effectId) + { + return ((effectId >= ESM::MagicEffect::SummonScamp + && effectId <= ESM::MagicEffect::SummonStormAtronach) + || effectId == ESM::MagicEffect::SummonCenturionSphere + || (effectId >= ESM::MagicEffect::SummonFabricant + && effectId <= ESM::MagicEffect::SummonCreature05)); + } } diff --git a/apps/openmw/mwmechanics/spellcasting.hpp b/apps/openmw/mwmechanics/spellcasting.hpp index 395ae043b4..2d550e0856 100644 --- a/apps/openmw/mwmechanics/spellcasting.hpp +++ b/apps/openmw/mwmechanics/spellcasting.hpp @@ -22,6 +22,8 @@ namespace MWMechanics ESM::Skill::SkillEnum spellSchoolToSkill(int school); + bool isSummoningEffect(int effectId); + /** * @param spell spell to cast * @param actor calculate spell success chance for this actor (depends on actor's skills) @@ -58,6 +60,8 @@ namespace MWMechanics float getEffectMultiplier(short effectId, const MWWorld::Ptr& actor, const MWWorld::Ptr& caster, const ESM::Spell* spell = NULL, const MagicEffects* effects = NULL); + int getEffectiveEnchantmentCastCost (float castCost, const MWWorld::Ptr& actor); + class CastSpell { private: @@ -93,6 +97,8 @@ namespace MWMechanics /// @note \a caster can be any type of object, or even an empty object. void applyInstantEffect (const MWWorld::Ptr& target, const MWWorld::Ptr& caster, const MWMechanics::EffectKey& effect, float magnitude); + + void applyDynamicStatsEffect (int attribute, const MWWorld::Ptr& target, float magnitude); }; } diff --git a/apps/openmw/mwmechanics/spells.cpp b/apps/openmw/mwmechanics/spells.cpp index a1b73bc47c..dcbd3f09f3 100644 --- a/apps/openmw/mwmechanics/spells.cpp +++ b/apps/openmw/mwmechanics/spells.cpp @@ -25,12 +25,10 @@ namespace MWMechanics return mSpells.end(); } - void Spells::add (const std::string& spellId) + void Spells::add (const ESM::Spell* spell) { - if (mSpells.find (spellId)==mSpells.end()) + if (mSpells.find (spell->mId)==mSpells.end()) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); - std::map random; // Determine the random magnitudes (unless this is a castable spell, in which case @@ -50,13 +48,19 @@ namespace MWMechanics corprus.mWorsenings = 0; corprus.mNextWorsening = MWBase::Environment::get().getWorld()->getTimeStamp() + CorprusStats::sWorseningPeriod; - mCorprusSpells[spellId] = corprus; + mCorprusSpells[spell->mId] = corprus; } - mSpells.insert (std::make_pair (Misc::StringUtils::lowerCase(spellId), random)); + mSpells.insert (std::make_pair (spell->mId, random)); } } + void Spells::add (const std::string& spellId) + { + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().find(spellId); + add(spell); + } + void Spells::remove (const std::string& spellId) { std::string lower = Misc::StringUtils::lowerCase(spellId); @@ -245,7 +249,7 @@ namespace MWMechanics random = it->second.at(i); float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * random; - visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, -1, magnitude); + visitor.visit(MWMechanics::EffectKey(*effectIt), spell->mName, spell->mId, -1, magnitude); } } } @@ -308,21 +312,17 @@ namespace MWMechanics void Spells::readState(const ESM::SpellState &state) { - mSpells = state.mSpells; - mSelectedSpell = state.mSelectedSpell; - - // Discard spells that are no longer available due to changed content files - for (TContainer::iterator iter = mSpells.begin(); iter!=mSpells.end();) + for (TContainer::const_iterator it = state.mSpells.begin(); it != state.mSpells.end(); ++it) { - const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(iter->first); - if (!spell) + // Discard spells that are no longer available due to changed content files + const ESM::Spell* spell = MWBase::Environment::get().getWorld()->getStore().get().search(it->first); + if (spell) { - if (iter->first == mSelectedSpell) - mSelectedSpell = ""; - mSpells.erase(iter++); + mSpells[it->first] = it->second; + + if (it->first == state.mSelectedSpell) + mSelectedSpell = it->first; } - else - ++iter; } // No need to discard spells here (doesn't really matter if non existent ids are kept) diff --git a/apps/openmw/mwmechanics/spells.hpp b/apps/openmw/mwmechanics/spells.hpp index ab799ffe12..064b2c1e5d 100644 --- a/apps/openmw/mwmechanics/spells.hpp +++ b/apps/openmw/mwmechanics/spells.hpp @@ -79,6 +79,9 @@ namespace MWMechanics void add (const std::string& spell); ///< Adding a spell that is already listed in *this is a no-op. + void add (const ESM::Spell* spell); + ///< Adding a spell that is already listed in *this is a no-op. + void remove (const std::string& spell); ///< If the spell to be removed is the selected spell, the selected spell will be changed to /// no spell (empty string). diff --git a/apps/openmw/mwmechanics/summoning.cpp b/apps/openmw/mwmechanics/summoning.cpp new file mode 100644 index 0000000000..ec9bd0ea01 --- /dev/null +++ b/apps/openmw/mwmechanics/summoning.cpp @@ -0,0 +1,196 @@ +#include "summoning.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "../mwmechanics/spellcasting.hpp" + +#include "../mwworld/esmstore.hpp" +#include "../mwworld/class.hpp" +#include "../mwworld/manualref.hpp" +#include "../mwworld/inventorystore.hpp" + +#include "../mwrender/animation.hpp" + +#include "creaturestats.hpp" +#include "aifollow.hpp" + + +namespace MWMechanics +{ + + void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(creatureActorId); + if (!ptr.isEmpty()) + { + // TODO: Show death animation before deleting? We shouldn't allow looting the corpse while the animation + // plays though, which is a rather lame exploit in vanilla. + MWBase::Environment::get().getWorld()->deleteObject(ptr); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + } + else + { + // We didn't find the creature. It's probably in an inactive cell. + // Add to graveyard so we can delete it when the cell becomes active. + std::vector& graveyard = casterStats.getSummonedCreatureGraveyard(); + graveyard.push_back(creatureActorId); + } + } + + UpdateSummonedCreatures::UpdateSummonedCreatures(const MWWorld::Ptr &actor) + : mActor(actor) + { + + } + + void UpdateSummonedCreatures::visit(EffectKey key, const std::string &sourceName, const std::string &sourceId, int casterActorId, float magnitude, float remainingTime, float totalTime) + { + if (isSummoningEffect(key.mId) && magnitude > 0) + { + mActiveEffects.insert(std::make_pair(key.mId, sourceId)); + } + } + + void UpdateSummonedCreatures::finish() + { + static std::map summonMap; + if (summonMap.empty()) + { + 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"; + } + + MWMechanics::CreatureStats& creatureStats = mActor.getClass().getCreatureStats(mActor); + + // Update summon effects + std::map& creatureMap = creatureStats.getSummonedCreatureMap(); + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + bool found = mActiveEffects.find(it->first) != mActiveEffects.end(); + if (!found) + { + // Effect has ended + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + continue; + } + ++it; + } + + for (std::set >::iterator it = mActiveEffects.begin(); it != mActiveEffects.end(); ++it) + { + bool found = creatureMap.find(std::make_pair(it->first, it->second)) != creatureMap.end(); + if (!found) + { + ESM::Position ipos = mActor.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; + + const std::string& creatureGmst = summonMap[it->first]; + std::string creatureID = + MWBase::Environment::get().getWorld()->getStore().get().find(creatureGmst)->getString(); + + if (!creatureID.empty()) + { + MWWorld::CellStore* store = mActor.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), creatureID, 1); + ref.getPtr().getCellRef().setPosition(ipos); + + MWMechanics::CreatureStats& summonedCreatureStats = ref.getPtr().getClass().getCreatureStats(ref.getPtr()); + + // Make the summoned creature follow its master and help in fights + AiFollow package(mActor.getCellRef().getRefId()); + summonedCreatureStats.getAiSequence().stack(package, ref.getPtr()); + int creatureActorId = summonedCreatureStats.getActorId(); + + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + + MWRender::Animation* anim = MWBase::Environment::get().getWorld()->getAnimation(placed); + if (anim) + { + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_Start"); + if (fx) + anim->addEffect("meshes\\" + fx->mModel, -1, false); + } + + creatureMap.insert(std::make_pair(*it, creatureActorId)); + } + } + } + + for (std::map::iterator it = creatureMap.begin(); it != creatureMap.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->second); + if (!ptr.isEmpty() && ptr.getClass().getCreatureStats(ptr).isDead()) + { + // Purge the magic effect so a new creature can be summoned if desired + creatureStats.getActiveSpells().purgeEffect(it->first.first, it->first.second); + if (mActor.getClass().hasInventoryStore(ptr)) + mActor.getClass().getInventoryStore(mActor).purgeEffect(it->first.first, it->first.second); + + cleanupSummonedCreature(creatureStats, it->second); + creatureMap.erase(it++); + } + else + ++it; + } + + std::vector& graveyard = creatureStats.getSummonedCreatureGraveyard(); + for (std::vector::iterator it = graveyard.begin(); it != graveyard.end(); ) + { + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->searchPtrViaActorId(*it); + if (!ptr.isEmpty()) + { + it = graveyard.erase(it); + + const ESM::Static* fx = MWBase::Environment::get().getWorld()->getStore().get() + .search("VFX_Summon_End"); + if (fx) + MWBase::Environment::get().getWorld()->spawnEffect("meshes\\" + fx->mModel, + "", Ogre::Vector3(ptr.getRefData().getPosition().pos)); + + MWBase::Environment::get().getWorld()->deleteObject(ptr); + } + else + ++it; + } + } + +} diff --git a/apps/openmw/mwmechanics/summoning.hpp b/apps/openmw/mwmechanics/summoning.hpp new file mode 100644 index 0000000000..b8fe377838 --- /dev/null +++ b/apps/openmw/mwmechanics/summoning.hpp @@ -0,0 +1,35 @@ +#ifndef OPENMW_MECHANICS_SUMMONING_H +#define OPENMW_MECHANICS_SUMMONING_H + +#include + +#include "magiceffects.hpp" +#include "../mwworld/ptr.hpp" + +namespace MWMechanics +{ + + class CreatureStats; + + struct UpdateSummonedCreatures : public EffectSourceVisitor + { + UpdateSummonedCreatures(const MWWorld::Ptr& actor); + + virtual void visit (MWMechanics::EffectKey key, + const std::string& sourceName, const std::string& sourceId, int casterActorId, + float magnitude, float remainingTime = -1, float totalTime = -1); + + /// To call after all effect sources have been visited + void finish(); + + private: + MWWorld::Ptr mActor; + + std::set > mActiveEffects; + }; + + void cleanupSummonedCreature (MWMechanics::CreatureStats& casterStats, int creatureActorId); + +} + +#endif diff --git a/apps/openmw/mwrender/activatoranimation.cpp b/apps/openmw/mwrender/activatoranimation.cpp index 540876a3ec..1ef68f619c 100644 --- a/apps/openmw/mwrender/activatoranimation.cpp +++ b/apps/openmw/mwrender/activatoranimation.cpp @@ -1,11 +1,10 @@ #include "activatoranimation.hpp" +#include +#include + #include -#include "../mwbase/world.hpp" - -#include "../mwworld/class.hpp" - #include "renderconst.hpp" namespace MWRender @@ -15,11 +14,9 @@ ActivatorAnimation::~ActivatorAnimation() { } -ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr) +ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr, const std::string& model) : Animation(ptr, ptr.getRefData().getBaseNode()) { - const std::string& model = mPtr.getClass().getModel(mPtr); - if(!model.empty()) { setObjectRoot(model, false); @@ -27,6 +24,28 @@ ActivatorAnimation::ActivatorAnimation(const MWWorld::Ptr &ptr) addAnimSource(model); } + else + { + // No model given. Create an object root anyway, so that lights can be added to it if needed. + mObjectRoot = NifOgre::ObjectScenePtr (new NifOgre::ObjectScene(mInsert->getCreator())); + } +} + +void ActivatorAnimation::addLight(const ESM::Light *light) +{ + addExtraLight(mInsert->getCreator(), mObjectRoot, light); +} + +void ActivatorAnimation::removeParticles() +{ + for (unsigned int i=0; imParticles.size(); ++i) + { + // Don't destroyParticleSystem, the ParticleSystemController is still holding a pointer to it. + // Don't setVisible, this could conflict with a VisController. + // The following will remove all spawned particles, then set the speed factor to zero so that no new ones will be spawned. + mObjectRoot->mParticles[i]->setSpeedFactor(0.f); + mObjectRoot->mParticles[i]->clear(); + } } } diff --git a/apps/openmw/mwrender/activatoranimation.hpp b/apps/openmw/mwrender/activatoranimation.hpp index eb3e5815e7..a234defe7a 100644 --- a/apps/openmw/mwrender/activatoranimation.hpp +++ b/apps/openmw/mwrender/activatoranimation.hpp @@ -13,8 +13,11 @@ namespace MWRender class ActivatorAnimation : public Animation { public: - ActivatorAnimation(const MWWorld::Ptr& ptr); + ActivatorAnimation(const MWWorld::Ptr& ptr, const std::string &model); virtual ~ActivatorAnimation(); + + void addLight(const ESM::Light *light); + void removeParticles(); }; } diff --git a/apps/openmw/mwrender/actors.cpp b/apps/openmw/mwrender/actors.cpp index b7e9f5730c..3da6c40c83 100644 --- a/apps/openmw/mwrender/actors.cpp +++ b/apps/openmw/mwrender/actors.cpp @@ -71,22 +71,31 @@ void Actors::insertNPC(const MWWorld::Ptr& ptr) mAllActors[ptr] = anim; mRendering->addWaterRippleEmitter (ptr); } -void Actors::insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields) +void Actors::insertCreature (const MWWorld::Ptr& ptr, const std::string &model, bool weaponsShields) { insertBegin(ptr); Animation* anim = NULL; if (weaponsShields) - anim = new CreatureWeaponAnimation(ptr); + anim = new CreatureWeaponAnimation(ptr, model); else - anim = new CreatureAnimation(ptr); + anim = new CreatureAnimation(ptr, model); delete mAllActors[ptr]; mAllActors[ptr] = anim; mRendering->addWaterRippleEmitter (ptr); } -void Actors::insertActivator (const MWWorld::Ptr& ptr) +void Actors::insertActivator (const MWWorld::Ptr& ptr, const std::string &model, bool addLight) { insertBegin(ptr); - ActivatorAnimation* anim = new ActivatorAnimation(ptr); + ActivatorAnimation* anim = new ActivatorAnimation(ptr, model); + + if(ptr.getTypeName() == typeid(ESM::Light).name()) + { + if (addLight) + anim->addLight(ptr.get()->mBase); + else + anim->removeParticles(); + } + delete mAllActors[ptr]; mAllActors[ptr] = anim; } @@ -189,4 +198,18 @@ void Actors::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) mRendering->updateWaterRippleEmitterPtr (old, cur); } +void Actors::enableLights() +{ + PtrAnimationMap::const_iterator it = mAllActors.begin(); + for(;it != mAllActors.end();++it) + it->second->enableLights(true); +} + +void Actors::disableLights() +{ + PtrAnimationMap::const_iterator it = mAllActors.begin(); + for(;it != mAllActors.end();++it) + it->second->enableLights(false); +} + } diff --git a/apps/openmw/mwrender/actors.hpp b/apps/openmw/mwrender/actors.hpp index d5d6c52bb0..4f6c1bec28 100644 --- a/apps/openmw/mwrender/actors.hpp +++ b/apps/openmw/mwrender/actors.hpp @@ -40,11 +40,14 @@ namespace MWRender void setRootNode(Ogre::SceneNode* root); void insertNPC(const MWWorld::Ptr& ptr); - void insertCreature (const MWWorld::Ptr& ptr, bool weaponsShields); - void insertActivator (const MWWorld::Ptr& ptr); - bool deleteObject (const MWWorld::Ptr& ptr); + void insertCreature (const MWWorld::Ptr& ptr, const std::string& model, bool weaponsShields); + void insertActivator (const MWWorld::Ptr& ptr, const std::string& model, bool addLight=false); + bool deleteObject (const MWWorld::Ptr& ptr); ///< \return found? + void enableLights(); + void disableLights(); + void removeCell(MWWorld::CellStore* store); void update (Ogre::Camera* camera); diff --git a/apps/openmw/mwrender/animation.cpp b/apps/openmw/mwrender/animation.cpp index ecaaba0b9d..50afc5afdd 100644 --- a/apps/openmw/mwrender/animation.cpp +++ b/apps/openmw/mwrender/animation.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -86,6 +87,12 @@ Animation::~Animation() mAnimSources.clear(); } +std::string Animation::getObjectRootName() const +{ + if (mSkelBase) + return mSkelBase->getMesh()->getName(); + return std::string(); +} void Animation::setObjectRoot(const std::string &model, bool baseonly) { @@ -97,22 +104,8 @@ void Animation::setObjectRoot(const std::string &model, bool baseonly) if(model.empty()) return; - std::string mdlname = Misc::StringUtils::lowerCase(model); - std::string::size_type p = mdlname.rfind('\\'); - if(p == std::string::npos) - p = mdlname.rfind('/'); - if(p != std::string::npos) - mdlname.insert(mdlname.begin()+p+1, 'x'); - else - mdlname.insert(mdlname.begin(), 'x'); - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mdlname)) - { - mdlname = model; - Misc::StringUtils::toLower(mdlname); - } - - mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, mdlname) : - NifOgre::Loader::createObjectBase(mInsert, mdlname)); + mObjectRoot = (!baseonly ? NifOgre::Loader::createObjects(mInsert, model) : + NifOgre::Loader::createObjectBase(mInsert, model)); if(mObjectRoot->mSkelBase) { @@ -169,6 +162,9 @@ struct AddGlow instance->setProperty("env_map", sh::makeProperty(new sh::BooleanValue(true))); instance->setProperty("env_map_color", sh::makeProperty(new sh::Vector3(mColor->x, mColor->y, mColor->z))); + // Workaround for crash in Ogre (https://bitbucket.org/sinbad/ogre/pull-request/447/fix-shadows-crash-for-textureunitstates/diff) + // Remove when the fix is merged + instance->getMaterial()->setShadowCasterMaterial("openmw_shadowcaster_noalpha"); } }; @@ -252,14 +248,8 @@ void Animation::addAnimSource(const std::string &model) if(!mSkelBase) return; - std::string kfname = Misc::StringUtils::lowerCase(model); - std::string::size_type p = kfname.rfind('\\'); - if(p == std::string::npos) - p = kfname.rfind('/'); - if(p != std::string::npos) - kfname.insert(kfname.begin()+p+1, 'x'); - else - kfname.insert(kfname.begin(), 'x'); + std::string kfname = model; + Misc::StringUtils::toLower(kfname); if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) kfname.replace(kfname.size()-4, 4, ".kf"); @@ -336,7 +326,7 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScene { const MWWorld::Fallback *fallback = MWBase::Environment::get().getWorld()->getFallback(); - const int clr = light->mData.mColor; + const unsigned int clr = light->mData.mColor; Ogre::ColourValue color(((clr >> 0) & 0xFF) / 255.0f, ((clr >> 8) & 0xFF) / 255.0f, ((clr >> 16) & 0xFF) / 255.0f); @@ -414,7 +404,7 @@ void Animation::addExtraLight(Ogre::SceneManager *sceneMgr, NifOgre::ObjectScene } -Ogre::Node *Animation::getNode(const std::string &name) +Ogre::Node* Animation::getNode(const std::string &name) { if(mSkelBase) { @@ -425,6 +415,16 @@ Ogre::Node *Animation::getNode(const std::string &name) return NULL; } +Ogre::Node* Animation::getNode(int handle) +{ + if (mSkelBase) + { + Ogre::SkeletonInstance *skel = mSkelBase->getSkeleton(); + return skel->getBone(handle); + } + return NULL; +} + NifOgre::TextKeyMap::const_iterator Animation::findGroupStart(const NifOgre::TextKeyMap &keys, const std::string &groupname) { NifOgre::TextKeyMap::const_iterator iter(keys.begin()); @@ -613,7 +613,7 @@ void Animation::updatePosition(float oldtime, float newtime, Ogre::Vector3 &posi mAccumRoot->setPosition(-off); } -bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint) +bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, float startpoint, bool loopfallback) { // Look for text keys in reverse. This normally wouldn't matter, but for some reason undeadwolf_2.nif has two // separate walkforward keys, and the last one is supposed to be used. @@ -654,8 +654,16 @@ bool Animation::reset(AnimState &state, const NifOgre::TextKeyMap &keys, const s return false; state.mStartTime = startkey->first; - state.mLoopStartTime = startkey->first; - state.mLoopStopTime = stopkey->first; + if (loopfallback) + { + state.mLoopStartTime = startkey->first; + state.mLoopStopTime = stopkey->first; + } + else + { + state.mLoopStartTime = startkey->first; + state.mLoopStopTime = std::numeric_limits::max(); + } state.mStopTime = stopkey->first; state.mTime = state.mStartTime + ((state.mStopTime - state.mStartTime) * startpoint); @@ -850,7 +858,7 @@ void Animation::stopLooping(const std::string& groupname) } } -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) +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, bool loopfallback) { if(!mSkelBase || mAnimSources.empty()) return; @@ -886,7 +894,7 @@ void Animation::play(const std::string &groupname, int priority, int groups, boo for(;iter != mAnimSources.rend();++iter) { const NifOgre::TextKeyMap &textkeys = (*iter)->mTextKeys; - if(reset(state, textkeys, groupname, start, stop, startpoint)) + if(reset(state, textkeys, groupname, start, stop, startpoint, loopfallback)) { state.mSource = *iter; state.mSpeedMult = speedmult; @@ -980,7 +988,11 @@ void Animation::resetActiveGroups() AnimStateMap::const_iterator state = mStates.find(mAnimationTimePtr[0]->getAnimName()); if(state == mStates.end()) + { + if (mAccumRoot && mNonAccumRoot) + mAccumRoot->setPosition(-mNonAccumRoot->getPosition()*mAccumulate); return; + } const Ogre::SharedPtr &animsrc = state->second.mSource; const std::vector >&ctrls = animsrc->mControllers[0]; @@ -994,6 +1006,9 @@ void Animation::resetActiveGroups() break; } } + + if (mAccumRoot && mNonAccumCtrl) + mAccumRoot->setPosition(-mNonAccumCtrl->getTranslation(state->second.mTime)*mAccumulate); } @@ -1131,9 +1146,6 @@ Ogre::Vector3 Animation::runAnimation(float duration) if(!state.mPlaying && state.mAutoDisable) { - if(mNonAccumCtrl && stateiter->first == mAnimationTimePtr[0]->getAnimName()) - mAccumRoot->setPosition(0.f,0.f,0.f); - mStates.erase(stateiter++); resetActiveGroups(); @@ -1259,10 +1271,9 @@ void Animation::addEffect(const std::string &model, int effectId, bool loop, con if (bonename.empty()) params.mObjects = NifOgre::Loader::createObjects(mInsert, model); else - params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + params.mObjects = NifOgre::Loader::createObjects(mSkelBase, bonename, "", mInsert, model); - // TODO: turn off shadow casting - setRenderProperties(params.mObjects, RV_Misc, + setRenderProperties(params.mObjects, RV_Effects, RQG_Main, RQG_Alpha, 0.f, false, NULL); params.mLoop = loop; @@ -1496,23 +1507,6 @@ ObjectAnimation::ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &mod } } -void ObjectAnimation::addLight(const ESM::Light *light) -{ - addExtraLight(mInsert->getCreator(), mObjectRoot, light); -} - -void ObjectAnimation::removeParticles() -{ - for (unsigned int i=0; imParticles.size(); ++i) - { - // Don't destroyParticleSystem, the ParticleSystemController is still holding a pointer to it. - // Don't setVisible, this could conflict with a VisController. - // The following will remove all spawned particles, then set the speed factor to zero so that no new ones will be spawned. - mObjectRoot->mParticles[i]->setSpeedFactor(0.f); - mObjectRoot->mParticles[i]->clear(); - } -} - class FindEntityTransparency { public: diff --git a/apps/openmw/mwrender/animation.hpp b/apps/openmw/mwrender/animation.hpp index a8a9ee11e6..dab8cfebb7 100644 --- a/apps/openmw/mwrender/animation.hpp +++ b/apps/openmw/mwrender/animation.hpp @@ -173,7 +173,7 @@ protected: */ bool reset(AnimState &state, const NifOgre::TextKeyMap &keys, const std::string &groupname, const std::string &start, const std::string &stop, - float startpoint); + float startpoint, bool loopfallback); void handleTextKey(AnimState &state, const std::string &groupname, const NifOgre::TextKeyMap::const_iterator &key, const NifOgre::TextKeyMap& map); @@ -206,6 +206,9 @@ public: Ogre::uint8 transqueue, Ogre::Real dist=0.0f, bool enchantedGlow=false, Ogre::Vector3* glowColor=NULL); + /// Returns the name of the .nif file that makes up this animation's base skeleton. + /// If there is no skeleton, returns "". + std::string getObjectRootName() const; Animation(const MWWorld::Ptr &ptr, Ogre::SceneNode *node); virtual ~Animation(); @@ -228,6 +231,7 @@ public: virtual void preRender (Ogre::Camera* camera); virtual void setAlpha(float alpha) {} + virtual void setVampire(bool vampire) {} public: void updatePtr(const MWWorld::Ptr &ptr); @@ -254,11 +258,14 @@ public: * at the start marker, 1 starts at the stop marker. * \param loops How many times to loop the animation. This will use the * "loop start" and "loop stop" markers if they exist, - * otherwise it will use "start" and "stop". + * otherwise it may fall back to "start" and "stop", but only if + * the \a loopFallback parameter is true. + * \param loopFallback Allow looping an animation that has no loop keys, i.e. fall back to use + * the "start" and "stop" keys for looping? */ void play(const std::string &groupname, int priority, int groups, bool autodisable, float speedmult, const std::string &start, const std::string &stop, - float startpoint, size_t loops); + float startpoint, size_t loops, bool loopfallback=false); /** If the given animation group is currently playing, set its remaining loop count to '0'. */ @@ -305,6 +312,10 @@ public: /// 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 void setHeadPitch(Ogre::Radian factor) {} + virtual void setHeadYaw(Ogre::Radian factor) {} + virtual Ogre::Radian getHeadPitch() const { return Ogre::Radian(0.f); } + virtual Ogre::Radian getHeadYaw() const { return Ogre::Radian(0.f); } virtual Ogre::Vector3 runAnimation(float duration); @@ -326,6 +337,7 @@ public: Ogre::AxisAlignedBox getWorldBounds(); Ogre::Node *getNode(const std::string &name); + Ogre::Node *getNode(int handle); // Attaches the given object to a bone on this object's base skeleton. If the bone doesn't // exist, the object isn't attached and NULL is returned. The returned TagPoint is only @@ -338,9 +350,6 @@ class ObjectAnimation : public Animation { public: ObjectAnimation(const MWWorld::Ptr& ptr, const std::string &model); - void addLight(const ESM::Light *light); - void removeParticles(); - bool canBatch() const; void fillBatch(Ogre::StaticGeometry *sg); }; diff --git a/apps/openmw/mwrender/camera.cpp b/apps/openmw/mwrender/camera.cpp index 1850df904b..c7a27dfe8f 100644 --- a/apps/openmw/mwrender/camera.cpp +++ b/apps/openmw/mwrender/camera.cpp @@ -7,7 +7,6 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" -#include "../mwbase/soundmanager.hpp" #include "../mwworld/ptr.hpp" #include "../mwworld/refdata.hpp" @@ -120,15 +119,6 @@ namespace MWRender setPosition(Ogre::Vector3(x,y,z)); } - void Camera::updateListener() - { - Ogre::Vector3 pos = mCamera->getRealPosition(); - Ogre::Vector3 dir = mCamera->getRealDirection(); - Ogre::Vector3 up = mCamera->getRealUp(); - - MWBase::Environment::get().getSoundManager()->setListenerPosDir(pos, dir, up); - } - void Camera::update(float duration, bool paused) { if (mAnimation->upperBodyReady()) @@ -148,7 +138,6 @@ namespace MWRender } } - updateListener(); if (paused) return; diff --git a/apps/openmw/mwrender/camera.hpp b/apps/openmw/mwrender/camera.hpp index c542dc96ce..691a80862b 100644 --- a/apps/openmw/mwrender/camera.hpp +++ b/apps/openmw/mwrender/camera.hpp @@ -50,9 +50,6 @@ namespace MWRender bool mVanityToggleQueued; bool mViewModeToggleQueued; - /// Updates sound manager listener data - void updateListener(); - void setPosition(const Ogre::Vector3& position); void setPosition(float x, float y, float z); diff --git a/apps/openmw/mwrender/characterpreview.cpp b/apps/openmw/mwrender/characterpreview.cpp index 92d0bcd557..756c79ad83 100644 --- a/apps/openmw/mwrender/characterpreview.cpp +++ b/apps/openmw/mwrender/characterpreview.cpp @@ -72,14 +72,15 @@ namespace MWRender l->setDirection (Ogre::Vector3(0.3, -0.7, 0.3)); l->setDiffuseColour (Ogre::ColourValue(1,1,1)); - mSceneMgr->setAmbientLight (Ogre::ColourValue(0.5, 0.5, 0.5)); + mSceneMgr->setAmbientLight (Ogre::ColourValue(0.25, 0.25, 0.25)); mCamera = mSceneMgr->createCamera (mName); + mCamera->setFOVy(Ogre::Degree(12.3)); mCamera->setAspectRatio (float(mSizeX) / float(mSizeY)); Ogre::SceneNode* renderRoot = mSceneMgr->getRootSceneNode()->createChildSceneNode("renderRoot"); - //we do this with mwRoot in renderingManager, do it here too. + // leftover of old coordinate system. TODO: remove this and adjust positions/orientations to match renderRoot->pitch(Ogre::Degree(-90)); mNode = renderRoot->createChildSceneNode(); @@ -91,7 +92,7 @@ namespace MWRender mCamera->setPosition(mPosition * scale); mCamera->lookAt(mLookAt * scale); - mCamera->setNearClipDistance (0.01); + mCamera->setNearClipDistance (1); mCamera->setFarClipDistance (1000); mTexture = Ogre::TextureManager::getSingleton().createManual(mName, @@ -159,7 +160,7 @@ namespace MWRender InventoryPreview::InventoryPreview(MWWorld::Ptr character) - : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 65, -180), Ogre::Vector3(0,65,0)) + : CharacterPreview(character, 512, 1024, "CharacterPreview", Ogre::Vector3(0, 71, -700), Ogre::Vector3(0,71,0)) , mSelectionBuffer(NULL) , mSizeX(0) , mSizeY(0) @@ -195,6 +196,7 @@ namespace MWRender MWWorld::InventoryStore &inv = mCharacter.getClass().getInventoryStore(mCharacter); MWWorld::ContainerStoreIterator iter = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); std::string groupname; + bool showCarriedLeft = true; if(iter == inv.end()) groupname = "inventoryhandtohand"; else @@ -224,20 +226,24 @@ namespace MWRender groupname = "inventoryweapontwowide"; else groupname = "inventoryhandtohand"; - } + + showCarriedLeft = (iter->getClass().canBeEquipped(*iter, mCharacter).first != 2); + } else groupname = "inventoryhandtohand"; } + mAnimation->showCarriedLeft(showCarriedLeft); + mCurrentAnimGroup = groupname; mAnimation->play(mCurrentAnimGroup, 1, Animation::Group_All, false, 1.0f, "start", "stop", 0.0f, 0); MWWorld::ContainerStoreIterator torch = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedLeft); - if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name()) + if(torch != inv.end() && torch->getTypeName() == typeid(ESM::Light).name() && showCarriedLeft) { if(!mAnimation->getInfo("torch")) mAnimation->play("torch", 2, MWRender::Animation::Group_LeftArm, false, - 1.0f, "start", "stop", 0.0f, ~0ul); + 1.0f, "start", "stop", 0.0f, ~0ul, true); } else if(mAnimation->getInfo("torch")) mAnimation->disable("torch"); @@ -283,9 +289,10 @@ namespace MWRender RaceSelectionPreview::RaceSelectionPreview() : CharacterPreview(MWBase::Environment::get().getWorld()->getPlayerPtr(), - 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 6, -35), Ogre::Vector3(0,125,0)) + 512, 512, "CharacterHeadPreview", Ogre::Vector3(0, 8, -125), Ogre::Vector3(0,127,0)) , mBase (*mCharacter.get()->mBase) , mRef(&mBase) + , mPitch(Ogre::Degree(6)) { mCharacter = MWWorld::Ptr(&mRef, NULL); } @@ -293,7 +300,9 @@ namespace MWRender void RaceSelectionPreview::update(float angle) { mAnimation->runAnimation(0.0f); - mNode->roll(Ogre::Radian(angle), Ogre::SceneNode::TS_LOCAL); + + mNode->setOrientation(Ogre::Quaternion(Ogre::Radian(angle), Ogre::Vector3::UNIT_Z) + * Ogre::Quaternion(mPitch, Ogre::Vector3::UNIT_X)); updateCamera(); } @@ -312,7 +321,8 @@ namespace MWRender mBase = proto; mBase.mId = "player"; rebuild(); - update(0); + mAnimation->runAnimation(0.0f); + updateCamera(); } void RaceSelectionPreview::onSetup () @@ -325,7 +335,10 @@ namespace MWRender void RaceSelectionPreview::updateCamera() { Ogre::Vector3 scale = mNode->getScale(); - Ogre::Vector3 headOffset = mAnimation->getNode("Bip01 Head")->_getDerivedPosition(); + Ogre::Node* headNode = mAnimation->getNode("Bip01 Head"); + if (!headNode) + return; + Ogre::Vector3 headOffset = headNode->_getDerivedPosition(); headOffset = mNode->convertLocalToWorldPosition(headOffset); mCamera->setPosition(headOffset + mPosition * scale); diff --git a/apps/openmw/mwrender/characterpreview.hpp b/apps/openmw/mwrender/characterpreview.hpp index 711de0d154..80dbe18b4a 100644 --- a/apps/openmw/mwrender/characterpreview.hpp +++ b/apps/openmw/mwrender/characterpreview.hpp @@ -120,6 +120,10 @@ namespace MWRender } void setPrototype(const ESM::NPC &proto); + + private: + + Ogre::Radian mPitch; }; } diff --git a/apps/openmw/mwrender/creatureanimation.cpp b/apps/openmw/mwrender/creatureanimation.cpp index fef9fa644f..2bdf8a499a 100644 --- a/apps/openmw/mwrender/creatureanimation.cpp +++ b/apps/openmw/mwrender/creatureanimation.cpp @@ -16,39 +16,37 @@ namespace MWRender { -CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr) +CreatureAnimation::CreatureAnimation(const MWWorld::Ptr &ptr, const std::string& model) : Animation(ptr, ptr.getRefData().getBaseNode()) { MWWorld::LiveCellRef *ref = mPtr.get(); - 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::Bipedal)) - addAnimSource("meshes\\base_anim.nif"); + addAnimSource("meshes\\xbase_anim.nif"); addAnimSource(model); } } -CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr) +CreatureWeaponAnimation::CreatureWeaponAnimation(const MWWorld::Ptr &ptr, const std::string& model) : Animation(ptr, ptr.getRefData().getBaseNode()) , mShowWeapons(false) , mShowCarriedLeft(false) { MWWorld::LiveCellRef *ref = mPtr.get(); - 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::Bipedal)) - addAnimSource("meshes\\base_anim.nif"); + addAnimSource("meshes\\xbase_anim.nif"); addAnimSource(model); mPtr.getClass().getInventoryStore(mPtr).setListener(this, mPtr); @@ -106,7 +104,7 @@ void CreatureWeaponAnimation::updatePart(NifOgre::ObjectScenePtr& scene, int slo else bonename = "Shield Bone"; - scene = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, item.getClass().getModel(item)); + scene = NifOgre::Loader::createObjects(mSkelBase, bonename, bonename, mInsert, item.getClass().getModel(item)); Ogre::Vector3 glowColor = getEnchantmentColor(item); setRenderProperties(scene, RV_Actors, RQG_Main, RQG_Alpha, 0, diff --git a/apps/openmw/mwrender/creatureanimation.hpp b/apps/openmw/mwrender/creatureanimation.hpp index d6cd8a5179..6201c7af4a 100644 --- a/apps/openmw/mwrender/creatureanimation.hpp +++ b/apps/openmw/mwrender/creatureanimation.hpp @@ -15,7 +15,7 @@ namespace MWRender class CreatureAnimation : public Animation { public: - CreatureAnimation(const MWWorld::Ptr& ptr); + CreatureAnimation(const MWWorld::Ptr& ptr, const std::string &model); virtual ~CreatureAnimation() {} }; @@ -25,7 +25,7 @@ namespace MWRender class CreatureWeaponAnimation : public Animation, public WeaponAnimation, public MWWorld::InventoryStoreListener { public: - CreatureWeaponAnimation(const MWWorld::Ptr& ptr); + CreatureWeaponAnimation(const MWWorld::Ptr& ptr, const std::string &model); virtual ~CreatureWeaponAnimation() {} virtual void equipmentChanged() { updateParts(); } diff --git a/apps/openmw/mwrender/debugging.cpp b/apps/openmw/mwrender/debugging.cpp index 4f5536ca32..972c1b6dd0 100644 --- a/apps/openmw/mwrender/debugging.cpp +++ b/apps/openmw/mwrender/debugging.cpp @@ -231,8 +231,9 @@ void Debugging::togglePathgrid() void Debugging::enableCellPathgrid(MWWorld::CellStore *store) { + MWBase::World* world = MWBase::Environment::get().getWorld(); const ESM::Pathgrid *pathgrid = - MWBase::Environment::get().getWorld()->getStore().get().search(*store->getCell()); + world->getStore().get().search(*store->getCell()); if (!pathgrid) return; Vector3 cellPathGridPos(0, 0, 0); diff --git a/apps/openmw/mwrender/effectmanager.cpp b/apps/openmw/mwrender/effectmanager.cpp index a48dea8d57..503a0223e8 100644 --- a/apps/openmw/mwrender/effectmanager.cpp +++ b/apps/openmw/mwrender/effectmanager.cpp @@ -25,8 +25,7 @@ void EffectManager::addEffect(const std::string &model, std::string textureOverr NifOgre::ObjectScenePtr scene = NifOgre::Loader::createObjects(sceneNode, model); - // TODO: turn off shadow casting - MWRender::Animation::setRenderProperties(scene, RV_Misc, + MWRender::Animation::setRenderProperties(scene, RV_Effects, RQG_Main, RQG_Alpha, 0.f, false, NULL); for(size_t i = 0;i < scene->mControllers.size();i++) diff --git a/apps/openmw/mwrender/globalmap.cpp b/apps/openmw/mwrender/globalmap.cpp index fd8b919367..de78748b57 100644 --- a/apps/openmw/mwrender/globalmap.cpp +++ b/apps/openmw/mwrender/globalmap.cpp @@ -11,6 +11,7 @@ #include #include +#include #include @@ -66,109 +67,83 @@ namespace MWRender loadingListener->setProgressRange((mMaxX-mMinX+1) * (mMaxY-mMinY+1)); loadingListener->setProgress(0); - const Ogre::ColourValue waterShallowColour(0.15, 0.2, 0.19); - const Ogre::ColourValue waterDeepColour(0.1, 0.14, 0.13); - const Ogre::ColourValue groundColour(0.254, 0.19, 0.13); - const Ogre::ColourValue mountainColour(0.05, 0.05, 0.05); - const Ogre::ColourValue hillColour(0.16, 0.12, 0.08); + std::vector data (mWidth * mHeight * 3); - //if (!boost::filesystem::exists(mCacheDir + "/GlobalMap.png")) - if (1) + for (int x = mMinX; x <= mMaxX; ++x) { - std::vector data (mWidth * mHeight * 3); - - for (int x = mMinX; x <= mMaxX; ++x) + for (int y = mMinY; y <= mMaxY; ++y) { - for (int y = mMinY; y <= mMaxY; ++y) + ESM::Land* land = esmStore.get().search (x,y); + + if (land) { - ESM::Land* land = esmStore.get().search (x,y); + int mask = ESM::Land::DATA_WNAM; + if (!land->isDataLoaded(mask)) + land->loadData(mask); + } - if (land) + for (int cellY=0; cellYisDataLoaded(mask)) - land->loadData(mask); - } + int vertexX = float(cellX)/float(mCellSize) * 9; + int vertexY = float(cellY)/float(mCellSize) * 9; - for (int cellY=0; cellYmDataTypes & ESM::Land::DATA_WNAM) + y = (land->mLandData->mWnam[vertexY * 9 + vertexX] << 4) / 2048.f; + else + y = (SCHAR_MIN << 4) / 2048.f; + if (y < 0) { - int vertexX = float(cellX)/float(mCellSize) * ESM::Land::LAND_SIZE; - int vertexY = float(cellY)/float(mCellSize) * ESM::Land::LAND_SIZE; - - - int texelX = (x-mMinX) * mCellSize + cellX; - int texelY = (mHeight-1) - ((y-mMinY) * mCellSize + cellY); - - unsigned char r,g,b; - - if (land) - { - const float landHeight = land->mLandData->mHeights[vertexY * ESM::Land::LAND_SIZE + vertexX]; - - if (landHeight >= 0) - { - const float hillHeight = 2500.f; - if (landHeight >= hillHeight) - { - const float mountainHeight = 15000.f; - float factor = std::min(1.f, float(landHeight-hillHeight)/mountainHeight); - r = (hillColour.r * (1-factor) + mountainColour.r * factor) * 255; - g = (hillColour.g * (1-factor) + mountainColour.g * factor) * 255; - b = (hillColour.b * (1-factor) + mountainColour.b * factor) * 255; - } - else - { - float factor = std::min(1.f, float(landHeight)/hillHeight); - r = (groundColour.r * (1-factor) + hillColour.r * factor) * 255; - g = (groundColour.g * (1-factor) + hillColour.g * factor) * 255; - b = (groundColour.b * (1-factor) + hillColour.b * factor) * 255; - } - } - else - { - if (landHeight >= -100) - { - float factor = std::min(1.f, -1*landHeight/100.f); - r = (((waterShallowColour+groundColour)/2).r * (1-factor) + waterShallowColour.r * factor) * 255; - g = (((waterShallowColour+groundColour)/2).g * (1-factor) + waterShallowColour.g * factor) * 255; - b = (((waterShallowColour+groundColour)/2).b * (1-factor) + waterShallowColour.b * factor) * 255; - } - else - { - float factor = std::min(1.f, -1*(landHeight-100)/1000.f); - r = (waterShallowColour.r * (1-factor) + waterDeepColour.r * factor) * 255; - g = (waterShallowColour.g * (1-factor) + waterDeepColour.g * factor) * 255; - b = (waterShallowColour.b * (1-factor) + waterDeepColour.b * factor) * 255; - } - } - - } + r = (14 * y + 38); + g = 20 * y + 56; + b = 18 * y + 51; + } + else if (y < 0.3f) + { + if (y < 0.1f) + y *= 8.f; else { - r = waterDeepColour.r * 255; - g = waterDeepColour.g * 255; - b = waterDeepColour.b * 255; + y -= 0.1; + y += 0.8; } - - data[texelY * mWidth * 3 + texelX * 3] = r; - data[texelY * mWidth * 3 + texelX * 3+1] = g; - data[texelY * mWidth * 3 + texelX * 3+2] = b; + r = 66 - 32 * y; + g = 48 - 23 * y; + b = 33 - 16 * y; } + else + { + y -= 0.3f; + y *= 1.428f; + r = 34 - 29 * y; + g = 25 - 20 * y; + b = 17 - 12 * y; + } + + data[texelY * mWidth * 3 + texelX * 3] = r; + data[texelY * mWidth * 3 + texelX * 3+1] = g; + data[texelY * mWidth * 3 + texelX * 3+2] = b; } - loadingListener->increaseProgress(1); } + loadingListener->increaseProgress(); + if (land) + land->unloadData(); } - - Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); - - tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, - Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC); - tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8); } - else - tex = Ogre::TextureManager::getSingleton ().getByName ("GlobalMap.png"); + + Ogre::DataStreamPtr stream(new Ogre::MemoryDataStream(&data[0], data.size())); + + tex = Ogre::TextureManager::getSingleton ().createManual ("GlobalMap.png", Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, + Ogre::TEX_TYPE_2D, mWidth, mHeight, 0, Ogre::PF_B8G8R8, Ogre::TU_STATIC); + tex->loadRawData(stream, mWidth, mHeight, Ogre::PF_B8G8R8); tex->load(); @@ -209,8 +184,10 @@ namespace MWRender if (!localMapTexture.isNull()) { + int mapWidth = localMapTexture->getWidth(); + int mapHeight = localMapTexture->getHeight(); mOverlayTexture->load(); - mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,512,512), + mOverlayTexture->getBuffer()->blit(localMapTexture->getBuffer(), Ogre::Image::Box(0,0,mapWidth,mapHeight), Ogre::Image::Box(originX,originY,originX+mCellSize,originY+mCellSize)); Ogre::Image backup; @@ -218,7 +195,7 @@ namespace MWRender data.resize(mCellSize*mCellSize*4, 0); backup.loadDynamicImage(&data[0], mCellSize, mCellSize, Ogre::PF_A8B8G8R8); - localMapTexture->getBuffer()->blitToMemory(Ogre::Image::Box(0,0,512,512), backup.getPixelBox()); + localMapTexture->getBuffer()->blitToMemory(Ogre::Image::Box(0,0,mapWidth,mapHeight), backup.getPixelBox()); for (int x=0; x(resource); + Ogre::Texture* tex = static_cast(resource); Ogre::ConstImagePtrList list; list.push_back(&mOverlayImage); tex->_loadImages(list); @@ -266,9 +243,9 @@ namespace MWRender { const ESM::GlobalMap::Bounds& bounds = map.mBounds; - if (bounds.mMaxX-bounds.mMinX <= 0) + if (bounds.mMaxX-bounds.mMinX < 0) return; - if (bounds.mMaxY-bounds.mMinY <= 0) + if (bounds.mMaxY-bounds.mMinY < 0) return; if (bounds.mMinX > bounds.mMaxX diff --git a/apps/openmw/mwrender/localmap.cpp b/apps/openmw/mwrender/localmap.cpp index f4388eec54..638a086239 100644 --- a/apps/openmw/mwrender/localmap.cpp +++ b/apps/openmw/mwrender/localmap.cpp @@ -24,8 +24,10 @@ using namespace MWRender; using namespace Ogre; -LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManager* rendering) : - mInterior(false) +LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManager* rendering) + : mInterior(false) + , mAngle(0.f) + , mMapResolution(Settings::Manager::getInt("local map resolution", "Map")) { mRendering = rend; mRenderingManager = rendering; @@ -49,7 +51,7 @@ LocalMap::LocalMap(OEngine::Render::OgreRenderer* rend, MWRender::RenderingManag "localmap/rtt", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - sMapResolution, sMapResolution, + mMapResolution, mMapResolution, 0, PF_R8G8B8, TU_RENDERTARGET); @@ -198,7 +200,7 @@ void LocalMap::requestMap(MWWorld::CellStore* cell, // Get the cell's NorthMarker rotation. This is used to rotate the entire map. const Vector2& north = MWBase::Environment::get().getWorld()->getNorthVector(cell); - Radian angle = Ogre::Math::ATan2 (north.x, north.y) + Ogre::Degree(2); + Radian angle = Ogre::Math::ATan2 (north.x, north.y); mAngle = angle.valueRadians(); // Rotate the cell and merge the rotated corners to the bounding box @@ -412,7 +414,7 @@ void LocalMap::render(const float x, const float y, texture, ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, TEX_TYPE_2D, - sMapResolution, sMapResolution, + mMapResolution, mMapResolution, 0, PF_R8G8B8); tex->getBuffer()->blit(mRenderTexture->getBuffer()); @@ -492,7 +494,7 @@ void LocalMap::loadResource(Ogre::Resource* resource) std::vector& buffer = mBuffers[resourceName]; - Ogre::Texture* tex = dynamic_cast(resource); + Ogre::Texture* tex = static_cast(resource); tex->createInternalResources(); memcpy(tex->getBuffer()->lock(HardwareBuffer::HBL_DISCARD), &buffer[0], sFogOfWarResolution*sFogOfWarResolution*4); tex->getBuffer()->unlock(); diff --git a/apps/openmw/mwrender/localmap.hpp b/apps/openmw/mwrender/localmap.hpp index 4c60cbb11a..7cfa388146 100644 --- a/apps/openmw/mwrender/localmap.hpp +++ b/apps/openmw/mwrender/localmap.hpp @@ -87,7 +87,7 @@ namespace MWRender OEngine::Render::OgreRenderer* mRendering; MWRender::RenderingManager* mRenderingManager; - static const int sMapResolution = 512; + int mMapResolution; // the dynamic texture is a bottleneck, so don't set this too high static const int sFogOfWarResolution = 32; diff --git a/apps/openmw/mwrender/npcanimation.cpp b/apps/openmw/mwrender/npcanimation.cpp index f2bc3df95f..0926498c0a 100644 --- a/apps/openmw/mwrender/npcanimation.cpp +++ b/apps/openmw/mwrender/npcanimation.cpp @@ -12,6 +12,8 @@ #include +#include + #include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" @@ -56,8 +58,13 @@ std::string getVampireHead(const std::string& race, bool female) } } - assert(sVampireMapping[thisCombination]); - return "meshes\\" + sVampireMapping[thisCombination]->mModel; + if (sVampireMapping.find(thisCombination) == sVampireMapping.end()) + sVampireMapping[thisCombination] = NULL; + + const ESM::BodyPart* bodyPart = sVampireMapping[thisCombination]; + if (!bodyPart) + return std::string(); + return "meshes\\" + bodyPart->mModel; } bool isSkinned (NifOgre::ObjectScenePtr scene) @@ -155,7 +162,7 @@ static NpcAnimation::PartBoneMap createPartListMap() { NpcAnimation::PartBoneMap result; result.insert(std::make_pair(ESM::PRT_Head, "Head")); - result.insert(std::make_pair(ESM::PRT_Hair, "Head")); + result.insert(std::make_pair(ESM::PRT_Hair, "Head")); // note it uses "Head" as attach bone, but "Hair" as filter result.insert(std::make_pair(ESM::PRT_Neck, "Neck")); result.insert(std::make_pair(ESM::PRT_Cuirass, "Chest")); result.insert(std::make_pair(ESM::PRT_Groin, "Groin")); @@ -202,7 +209,9 @@ NpcAnimation::NpcAnimation(const MWWorld::Ptr& ptr, Ogre::SceneNode* node, int v mFirstPersonOffset(0.f, 0.f, 0.f), mAlpha(1.f), mNpcType(Type_Normal), - mSoundsDisabled(disableSounds) + mSoundsDisabled(disableSounds), + mHeadPitch(0.f), + mHeadYaw(0.f) { mNpc = mPtr.get()->mBase; @@ -254,12 +263,27 @@ void NpcAnimation::updateNpcBase() } else { - if (isVampire) + mHeadModel = ""; + if (isVampire) // FIXME: fall back to regular head when getVampireHead fails? mHeadModel = getVampireHead(mNpc->mRace, mNpc->mFlags & ESM::NPC::Female); - else - mHeadModel = "meshes\\" + store.get().find(mNpc->mHead)->mModel; + else if (!mNpc->mHead.empty()) + { + const ESM::BodyPart* bp = store.get().search(mNpc->mHead); + if (bp) + mHeadModel = "meshes\\" + bp->mModel; + else + std::cerr << "Failed to load body part '" << mNpc->mHead << "'" << std::endl; + } - mHairModel = "meshes\\" + store.get().find(mNpc->mHair)->mModel; + mHairModel = ""; + if (!mNpc->mHair.empty()) + { + const ESM::BodyPart* bp = store.get().search(mNpc->mHair); + if (bp) + mHairModel = "meshes\\" + bp->mModel; + else + std::cerr << "Failed to load body part '" << mNpc->mHair << "'" << std::endl; + } } bool isBeast = (race->mData.mFlags & ESM::Race::Beast) != 0; @@ -270,6 +294,7 @@ void NpcAnimation::updateNpcBase() (!isWerewolf ? !isBeast ? "meshes\\base_anim.1st.nif" : "meshes\\base_animkna.1st.nif" : "meshes\\wolf\\skin.1st.nif"); + smodel = Misc::ResourceHelpers::correctActorModelPath(smodel); setObjectRoot(smodel, true); if(mViewMode != VM_FirstPerson) @@ -278,11 +303,11 @@ void NpcAnimation::updateNpcBase() if(!isWerewolf) { if(Misc::StringUtils::lowerCase(mNpc->mRace).find("argonian") != std::string::npos) - addAnimSource("meshes\\argonian_swimkna.nif"); + addAnimSource("meshes\\xargonian_swimkna.nif"); else if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\base_anim_female.nif"); + addAnimSource("meshes\\xbase_anim_female.nif"); if(mNpc->mModel.length() > 0) - addAnimSource("meshes\\"+mNpc->mModel); + addAnimSource("meshes\\x"+mNpc->mModel); } } else @@ -294,11 +319,11 @@ void NpcAnimation::updateNpcBase() /* A bit counter-intuitive, but unlike third-person anims, it seems * beast races get both base_anim.1st.nif and base_animkna.1st.nif. */ - addAnimSource("meshes\\base_anim.1st.nif"); + addAnimSource("meshes\\xbase_anim.1st.nif"); if(isBeast) - addAnimSource("meshes\\base_animkna.1st.nif"); + addAnimSource("meshes\\xbase_animkna.1st.nif"); if(!mNpc->isMale() && !isBeast) - addAnimSource("meshes\\base_anim_female.1st.nif"); + addAnimSource("meshes\\xbase_anim_female.1st.nif"); } } @@ -349,6 +374,8 @@ void NpcAnimation::updateParts() }; static const size_t slotlistsize = sizeof(slotlist)/sizeof(slotlist[0]); + bool wasArrowAttached = (mAmmunition.get()); + MWWorld::InventoryStore& inv = mPtr.getClass().getInventoryStore(mPtr); for(size_t i = 0;i < slotlistsize && mViewMode != VM_HeadOnly;i++) { @@ -399,9 +426,9 @@ void NpcAnimation::updateParts() if(mViewMode != VM_FirstPerson) { - if(mPartPriorities[ESM::PRT_Head] < 1) + if(mPartPriorities[ESM::PRT_Head] < 1 && !mHeadModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Head, -1,1, mHeadModel); - if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1) + if(mPartPriorities[ESM::PRT_Hair] < 1 && mPartPriorities[ESM::PRT_Head] <= 1 && !mHairModel.empty()) addOrReplaceIndividualPart(ESM::PRT_Hair, -1,1, mHairModel); } if(mViewMode == VM_HeadOnly) @@ -543,6 +570,9 @@ void NpcAnimation::updateParts() "meshes\\"+bodypart->mModel); } } + + if (wasArrowAttached) + attachArrow(); } void NpcAnimation::addFirstPersonOffset(const Ogre::Vector3 &offset) @@ -562,9 +592,9 @@ public: } }; -NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, bool enchantedGlow, Ogre::Vector3* glowColor) +NifOgre::ObjectScenePtr NpcAnimation::insertBoundedPart(const std::string &model, int group, const std::string &bonename, const std::string &bonefilter, bool enchantedGlow, Ogre::Vector3* glowColor) { - NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, mInsert, model); + NifOgre::ObjectScenePtr objects = NifOgre::Loader::createObjects(mSkelBase, bonename, bonefilter, mInsert, model); setRenderProperties(objects, (mViewMode == VM_FirstPerson) ? RV_FirstPerson : mVisibilityFlags, RQG_Main, RQG_Alpha, 0, enchantedGlow, glowColor); @@ -611,6 +641,10 @@ Ogre::Vector3 NpcAnimation::runAnimation(float timepassed) { // In third person mode we may still need pitch for ranged weapon targeting pitchSkeleton(mPtr.getRefData().getPosition().rot[0], baseinst); + + Ogre::Node* node = baseinst->getBone("Bip01 Head"); + if (node) + node->rotate(Ogre::Quaternion(mHeadYaw, Ogre::Vector3::UNIT_Z) * Ogre::Quaternion(mHeadPitch, Ogre::Vector3::UNIT_X), Ogre::Node::TS_WORLD); } mFirstPersonOffset = 0.f; // reset the X, Y, Z offset for the next frame. @@ -674,7 +708,10 @@ bool NpcAnimation::addOrReplaceIndividualPart(ESM::PartReferenceType type, int g mPartPriorities[type] = priority; try { - mObjectParts[type] = insertBoundedPart(mesh, group, sPartList.at(type), enchantedGlow, glowColor); + const std::string& bonename = sPartList.at(type); + // PRT_Hair seems to be the only type that breaks consistency and uses a filter that's different from the attachment bone + const std::string bonefilter = (type == ESM::PRT_Hair) ? "hair" : bonename; + mObjectParts[type] = insertBoundedPart(mesh, group, bonename, bonefilter, enchantedGlow, glowColor); } catch (std::exception& e) { @@ -774,6 +811,8 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormData.mPart == ESM::BodyPart::MP_Upperarm)) bodypart = NULL; } + else if (!bodypart) + std::cerr << "Failed to find body part '" << part->mFemale << "'" << std::endl; } if(!bodypart && !part->mMale.empty()) { @@ -787,6 +826,8 @@ void NpcAnimation::addPartGroup(int group, int priority, const std::vectormData.mPart == ESM::BodyPart::MP_Upperarm)) bodypart = NULL; } + else if (!bodypart) + std::cerr << "Failed to find body part '" << part->mMale << "'" << std::endl; } if(bodypart) @@ -973,4 +1014,37 @@ void NpcAnimation::equipmentChanged() updateParts(); } +void NpcAnimation::setVampire(bool vampire) +{ + if (mNpcType == Type_Werewolf) // we can't have werewolf vampires, can we + return; + if ((mNpcType == Type_Vampire) != vampire) + { + if (mPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) + MWBase::Environment::get().getWorld()->reattachPlayerCamera(); + else + rebuild(); + } +} + +void NpcAnimation::setHeadPitch(Ogre::Radian pitch) +{ + mHeadPitch = pitch; +} + +void NpcAnimation::setHeadYaw(Ogre::Radian yaw) +{ + mHeadYaw = yaw; +} + +Ogre::Radian NpcAnimation::getHeadPitch() const +{ + return mHeadPitch; +} + +Ogre::Radian NpcAnimation::getHeadYaw() const +{ + return mHeadYaw; +} + } diff --git a/apps/openmw/mwrender/npcanimation.hpp b/apps/openmw/mwrender/npcanimation.hpp index ee62fce9cd..90b1c269bf 100644 --- a/apps/openmw/mwrender/npcanimation.hpp +++ b/apps/openmw/mwrender/npcanimation.hpp @@ -100,9 +100,13 @@ private: float mAlpha; bool mSoundsDisabled; + Ogre::Radian mHeadYaw; + Ogre::Radian mHeadPitch; + void updateNpcBase(); NifOgre::ObjectScenePtr insertBoundedPart(const std::string &model, int group, const std::string &bonename, + const std::string &bonefilter, bool enchantedGlow, Ogre::Vector3* glowColor=NULL); void removeIndividualPart(ESM::PartReferenceType type); @@ -142,8 +146,13 @@ public: /// to indicate the facing orientation of the character. virtual void setPitchFactor(float factor) { mPitchFactor = factor; } + virtual void setHeadPitch(Ogre::Radian pitch); + virtual void setHeadYaw(Ogre::Radian yaw); + virtual Ogre::Radian getHeadPitch() const; + virtual Ogre::Radian getHeadYaw() const; + virtual void showWeapons(bool showWeapon); - virtual void showCarriedLeft(bool showa); + virtual void showCarriedLeft(bool show); virtual void attachArrow(); virtual void releaseArrow(); @@ -168,6 +177,8 @@ public: /// Make the NPC only partially visible virtual void setAlpha(float alpha); + virtual void setVampire(bool vampire); + /// Prepare this animation for being rendered with \a camera (rotates billboard nodes) virtual void preRender (Ogre::Camera* camera); }; diff --git a/apps/openmw/mwrender/objects.cpp b/apps/openmw/mwrender/objects.cpp index 96decbb360..9650830191 100644 --- a/apps/openmw/mwrender/objects.cpp +++ b/apps/openmw/mwrender/objects.cpp @@ -73,20 +73,12 @@ void Objects::insertBegin(const MWWorld::Ptr& ptr) ptr.getRefData().setBaseNode(insert); } -void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch, bool addLight) +void Objects::insertModel(const MWWorld::Ptr &ptr, const std::string &mesh, bool batch) { insertBegin(ptr); std::auto_ptr anim(new ObjectAnimation(ptr, mesh)); - if(ptr.getTypeName() == typeid(ESM::Light).name()) - { - if (addLight) - anim->addLight(ptr.get()->mBase); - else - anim->removeParticles(); - } - if (!mesh.empty()) { Ogre::AxisAlignedBox bounds = anim->getWorldBounds(); @@ -245,20 +237,6 @@ Ogre::AxisAlignedBox Objects::getDimensions(MWWorld::CellStore* cell) return mBounds[cell]; } -void Objects::enableLights() -{ - PtrAnimationMap::const_iterator it = mObjects.begin(); - for(;it != mObjects.end();++it) - it->second->enableLights(true); -} - -void Objects::disableLights() -{ - PtrAnimationMap::const_iterator it = mObjects.begin(); - for(;it != mObjects.end();++it) - it->second->enableLights(false); -} - void Objects::update(float dt, Ogre::Camera* camera) { PtrAnimationMap::const_iterator it = mObjects.begin(); diff --git a/apps/openmw/mwrender/objects.hpp b/apps/openmw/mwrender/objects.hpp index 7f740dbab0..adfe5ca26b 100644 --- a/apps/openmw/mwrender/objects.hpp +++ b/apps/openmw/mwrender/objects.hpp @@ -41,13 +41,10 @@ public: , mRootNode(NULL) {} ~Objects(){} - void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false, bool addLight=false); + void insertModel(const MWWorld::Ptr& ptr, const std::string &model, bool batch=false); ObjectAnimation* getAnimation(const MWWorld::Ptr &ptr); - void enableLights(); - void disableLights(); - void update (float dt, Ogre::Camera* camera); ///< per-frame update diff --git a/apps/openmw/mwrender/refraction.cpp b/apps/openmw/mwrender/refraction.cpp index 164380866a..6cc49089ae 100644 --- a/apps/openmw/mwrender/refraction.cpp +++ b/apps/openmw/mwrender/refraction.cpp @@ -33,7 +33,7 @@ namespace MWRender Ogre::Viewport* vp = mRenderTarget->addViewport(mCamera); vp->setOverlaysEnabled(false); vp->setShadowsEnabled(false); - vp->setVisibilityMask(RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Sky + RV_FirstPerson); + vp->setVisibilityMask(RV_Refraction); vp->setMaterialScheme("water_refraction"); vp->setBackgroundColour (Ogre::ColourValue(0.090195, 0.115685, 0.12745)); mRenderTarget->setAutoUpdated(true); diff --git a/apps/openmw/mwrender/renderconst.hpp b/apps/openmw/mwrender/renderconst.hpp index 44599ebee2..cfd84cb32e 100644 --- a/apps/openmw/mwrender/renderconst.hpp +++ b/apps/openmw/mwrender/renderconst.hpp @@ -21,6 +21,7 @@ enum RenderQueueGroups RQG_UnderWater = Ogre::RENDER_QUEUE_4, RQG_Water = RQG_Alpha, + RQG_Ripples = RQG_Water+1, // Sky late (sun & sun flare) RQG_SkiesLate = Ogre::RENDER_QUEUE_SKIES_LATE @@ -30,39 +31,44 @@ enum RenderQueueGroups enum VisibilityFlags { // Terrain - RV_Terrain = 1, + RV_Terrain = (1<<0), // Statics (e.g. trees, houses) - RV_Statics = 2, + RV_Statics = (1<<1), // Small statics - RV_StaticsSmall = 4, + RV_StaticsSmall = (1<<2), // Water - RV_Water = 8, + RV_Water = (1<<3), // Actors (npcs, creatures) - RV_Actors = 16, + RV_Actors = (1<<4), // Misc objects (containers, dynamic objects) - RV_Misc = 32, + RV_Misc = (1<<5), - RV_Sky = 64, + // VFX, don't appear on map and don't cast shadows + RV_Effects = (1<<6), + + RV_Sky = (1<<7), // not visible in reflection - RV_NoReflection = 128, + RV_NoReflection = (1<<8), - RV_OcclusionQuery = 256, + RV_OcclusionQuery = (1<<9), - RV_Debug = 512, + RV_Debug = (1<<10), // overlays, we only want these on the main render target - RV_Overlay = 1024, + RV_Overlay = (1<<11), // First person meshes do not cast shadows - RV_FirstPerson = 2048, + RV_FirstPerson = (1<<12), - RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water + RV_Map = RV_Terrain + RV_Statics + RV_StaticsSmall + RV_Misc + RV_Water, + + RV_Refraction = RV_Actors + RV_Misc + RV_Statics + RV_StaticsSmall + RV_Terrain + RV_Effects + RV_Sky + RV_FirstPerson }; } diff --git a/apps/openmw/mwrender/renderingmanager.cpp b/apps/openmw/mwrender/renderingmanager.cpp index ed25e70a69..b83a078ba7 100644 --- a/apps/openmw/mwrender/renderingmanager.cpp +++ b/apps/openmw/mwrender/renderingmanager.cpp @@ -175,7 +175,7 @@ RenderingManager::RenderingManager(OEngine::Render::OgreRenderer& _rend, const b mDebugging = new Debugging(mRootNode, engine); mLocalMap = new MWRender::LocalMap(&mRendering, this); - mWater = new MWRender::Water(mRendering.getCamera(), this); + mWater = new MWRender::Water(mRendering.getCamera(), this, mFallback); setMenuTransparency(Settings::Manager::getFloat("menu transparency", "GUI")); } @@ -256,10 +256,10 @@ void RenderingManager::cellAdded (MWWorld::CellStore *store) mDebugging->cellAdded(store); } -void RenderingManager::addObject (const MWWorld::Ptr& ptr){ +void RenderingManager::addObject (const MWWorld::Ptr& ptr, const std::string& model){ const MWWorld::Class& class_ = ptr.getClass(); - class_.insertObjectRendering(ptr, *this); + class_.insertObjectRendering(ptr, model, *this); } void RenderingManager::removeObject (const MWWorld::Ptr& ptr) @@ -297,6 +297,8 @@ void RenderingManager::rotateObject(const MWWorld::Ptr &ptr) void RenderingManager::updateObjectCell(const MWWorld::Ptr &old, const MWWorld::Ptr &cur) { + if (!old.getRefData().getBaseNode()) + return; Ogre::SceneNode *child = mRendering.getScene()->getSceneNode(old.getRefData().getHandle()); @@ -409,6 +411,7 @@ void RenderingManager::update (float duration, bool paused) mSkyManager->setGlare(mOcclusionQuery->getSunVisibility()); + mWater->changeCell(player.getCell()->getCell()); mWater->updateUnderwater(world->isUnderwater(player.getCell(), cam)); @@ -629,12 +632,12 @@ void RenderingManager::sunDisable(bool real) } } -void RenderingManager::setSunDirection(const Ogre::Vector3& direction, bool is_moon) +void RenderingManager::setSunDirection(const Ogre::Vector3& direction, bool is_night) { // direction * -1 (because 'direction' is camera to sun vector and not sun to camera), if (mSun) mSun->setDirection(Vector3(-direction.x, -direction.y, -direction.z)); - mSkyManager->setSunDirection(direction, is_moon); + mSkyManager->setSunDirection(direction, is_night); } void RenderingManager::setGlare(bool glare) @@ -677,19 +680,20 @@ void RenderingManager::writeFog(MWWorld::CellStore* cell) void RenderingManager::disableLights(bool sun) { - mObjects->disableLights(); + mActors->disableLights(); sunDisable(sun); } void RenderingManager::enableLights(bool sun) { - mObjects->enableLights(); + mActors->enableLights(); sunEnable(sun); } void RenderingManager::notifyWorldSpaceChanged() { mEffectManager->clear(); + mWater->clearRipples(); } Ogre::Vector4 RenderingManager::boundingBoxToScreen(Ogre::AxisAlignedBox bounds) @@ -753,8 +757,14 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec || it->second == "resolution y" || it->second == "fullscreen")) changeRes = true; + else if (it->first == "Video" && it->second == "window border") + changeRes = true; else if (it->second == "field of view" && it->first == "General") mRendering.setFov(Settings::Manager::getFloat("field of view", "General")); + else if (it->second == "gamma" && it->first == "General") + { + mRendering.setWindowGammaContrast(Settings::Manager::getFloat("gamma", "General"), Settings::Manager::getFloat("contrast", "General")); + } else if ((it->second == "texture filtering" && it->first == "General") || (it->second == "anisotropy" && it->first == "General")) { @@ -810,6 +820,7 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec unsigned int x = Settings::Manager::getInt("resolution x", "Video"); unsigned int y = Settings::Manager::getInt("resolution y", "Video"); bool fullscreen = Settings::Manager::getBool("fullscreen", "Video"); + bool windowBorder = Settings::Manager::getBool("window border", "Video"); SDL_Window* window = mRendering.getSDLWindow(); @@ -828,7 +839,10 @@ void RenderingManager::processChangedSettings(const Settings::CategorySettingVec SDL_SetWindowFullscreen(window, fullscreen); } else + { SDL_SetWindowSize(window, x, y); + SDL_SetWindowBordered(window, windowBorder ? SDL_TRUE : SDL_FALSE); + } } mWater->processChangedSettings(settings); @@ -1026,10 +1040,10 @@ void RenderingManager::enableTerrain(bool enable) if (!mTerrain) { if (Settings::Manager::getBool("distant land", "Terrain")) - mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::DefaultWorld(mRendering.getScene(), new MWRender::TerrainStorage(true), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY, 1, 64); else - mTerrain = new Terrain::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(), RV_Terrain, + mTerrain = new Terrain::TerrainGrid(mRendering.getScene(), new MWRender::TerrainStorage(false), RV_Terrain, Settings::Manager::getBool("shader", "Terrain"), Terrain::Align_XY); mTerrain->applyMaterials(Settings::Manager::getBool("enabled", "Shadows"), Settings::Manager::getBool("split", "Shadows")); diff --git a/apps/openmw/mwrender/renderingmanager.hpp b/apps/openmw/mwrender/renderingmanager.hpp index c3eedce7b5..9f029c1b91 100644 --- a/apps/openmw/mwrender/renderingmanager.hpp +++ b/apps/openmw/mwrender/renderingmanager.hpp @@ -111,7 +111,7 @@ public: /// Write current fog of war for this cell to the CellStore void writeFog (MWWorld::CellStore* store); - void addObject (const MWWorld::Ptr& ptr); + void addObject (const MWWorld::Ptr& ptr, const std::string& model); void removeObject (const MWWorld::Ptr& ptr); void moveObject (const MWWorld::Ptr& ptr, const Ogre::Vector3& position); @@ -141,7 +141,7 @@ public: void setAmbientColour(const Ogre::ColourValue& colour); void setSunColour(const Ogre::ColourValue& colour); - void setSunDirection(const Ogre::Vector3& direction, bool is_moon); + void setSunDirection(const Ogre::Vector3& direction, bool is_night); void sunEnable(bool real); ///< @param real whether or not to really disable the sunlight (otherwise just set diffuse to 0) void sunDisable(bool real); diff --git a/apps/openmw/mwrender/ripplesimulation.cpp b/apps/openmw/mwrender/ripplesimulation.cpp index 64b5e48c38..84794162f3 100644 --- a/apps/openmw/mwrender/ripplesimulation.cpp +++ b/apps/openmw/mwrender/ripplesimulation.cpp @@ -1,162 +1,72 @@ #include "ripplesimulation.hpp" -#include -#include -#include -#include -#include +#include + #include -#include -#include +#include +#include +#include #include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/fallback.hpp" + +#include "renderconst.hpp" + namespace MWRender { - -RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager) - : mMainSceneMgr(mainSceneManager), - mTime(0), - mCurrentFrameOffset(0,0), - mPreviousFrameOffset(0,0), - mRippleCenter(0,0), - mTextureSize(512), - mRippleAreaLength(1000), - mImpulseSize(20), - mTexelOffset(0,0), - mFirstUpdate(true), - mRectangle(NULL), - mImpulse(NULL) +RippleSimulation::RippleSimulation(Ogre::SceneManager* mainSceneManager, const MWWorld::Fallback* fallback) + : mSceneMgr(mainSceneManager) + , mParticleSystem(NULL) + , mSceneNode(NULL) { - Ogre::AxisAlignedBox aabInf; - aabInf.setInfinite(); + mRippleLifeTime = fallback->getFallbackFloat("Water_RippleLifetime"); + mRippleRotSpeed = fallback->getFallbackFloat("Water_RippleRotSpeed"); - mSceneMgr = Ogre::Root::getSingleton().createSceneManager(Ogre::ST_GENERIC); + // Unknown: + // fallback=Water_RippleScale,0.15, 6.5 + // fallback=Water_RippleAlphas,0.7, 0.1, 0.01 - mCamera = mSceneMgr->createCamera("RippleCamera"); + // Instantiate from ripples.particle file + mParticleSystem = mSceneMgr->createParticleSystem("openmw/Ripples", "openmw/Ripples"); - mRectangle = new Ogre::Rectangle2D(true); - mRectangle->setBoundingBox(aabInf); - mRectangle->setCorners(-1.0, 1.0, 1.0, -1.0, false); - Ogre::SceneNode* node = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - node->attachObject(mRectangle); + mParticleSystem->setRenderQueueGroup(RQG_Ripples); + mParticleSystem->setVisibilityFlags(RV_Effects); - mImpulse = new Ogre::Rectangle2D(true); - mImpulse->setCorners(-0.1, 0.1, 0.1, -0.1, false); - mImpulse->setBoundingBox(aabInf); - mImpulse->setMaterial("AddImpulse"); - Ogre::SceneNode* impulseNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); - impulseNode->attachObject(mImpulse); + int rippleFrameCount = fallback->getFallbackInt("Water_RippleFrameCount"); + std::string tex = fallback->getFallbackString("Water_RippleTexture"); - //float w=0.05; - for (int i=0; i<4; ++i) - { - Ogre::TexturePtr texture; - if (i != 3) - texture = Ogre::TextureManager::getSingleton().createManual("RippleHeight" + Ogre::StringConverter::toString(i), - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); - else - texture = Ogre::TextureManager::getSingleton().createManual("RippleNormal", - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, Ogre::TEX_TYPE_2D, mTextureSize, mTextureSize, 1, 0, Ogre::PF_R8G8B8, Ogre::TU_RENDERTARGET); + sh::MaterialInstance* mat = sh::Factory::getInstance().getMaterialInstance("openmw/Ripple"); + mat->setProperty("anim_texture2", sh::makeProperty(new sh::StringValue(std::string("textures\\water\\") + tex + ".dds " + + Ogre::StringConverter::toString(rippleFrameCount) + + " " + + Ogre::StringConverter::toString(0.3)))); + // seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better + mParticleSystem->_update(0.f); - Ogre::RenderTexture* rt = texture->getBuffer()->getRenderTarget(); - rt->removeAllViewports(); - rt->addViewport(mCamera); - rt->setAutoUpdated(false); - rt->getViewport(0)->setClearEveryFrame(false); - - // debug overlay - /* - Ogre::Rectangle2D* debugOverlay = new Ogre::Rectangle2D(true); - debugOverlay->setCorners(w*2-1, 0.9, (w+0.18)*2-1, 0.4, false); - w += 0.2; - debugOverlay->setBoundingBox(aabInf); - Ogre::SceneNode* debugNode = mMainSceneMgr->getRootSceneNode()->createChildSceneNode(); - debugNode->attachObject(debugOverlay); - - Ogre::MaterialPtr debugMaterial = Ogre::MaterialManager::getSingleton().create("RippleDebug" + Ogre::StringConverter::toString(i), - Ogre::ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME); - - if (i != 3) - debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleHeight" + Ogre::StringConverter::toString(i)); - else - debugMaterial->getTechnique(0)->getPass(0)->createTextureUnitState("RippleNormal"); - debugMaterial->getTechnique(0)->getPass(0)->setLightingEnabled(false); - - debugOverlay->setMaterial("RippleDebug" + Ogre::StringConverter::toString(i)); - */ - - mRenderTargets[i] = rt; - mTextures[i] = texture; - } - - sh::Factory::getInstance().setSharedParameter("rippleTextureSize", sh::makeProperty( - new sh::Vector4(1.0/512, 1.0/512, 512, 512))); - sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty( - new sh::Vector3(0, 0, 0))); - sh::Factory::getInstance().setSharedParameter("rippleAreaLength", sh::makeProperty( - new sh::FloatValue(mRippleAreaLength))); - + mSceneNode = mSceneMgr->getRootSceneNode()->createChildSceneNode(); + mSceneNode->attachObject(mParticleSystem); } RippleSimulation::~RippleSimulation() { - delete mRectangle; - delete mImpulse; + if (mParticleSystem) + mSceneMgr->destroyParticleSystem(mParticleSystem); + mParticleSystem = NULL; - Ogre::Root::getSingleton().destroySceneManager(mSceneMgr); + if (mSceneNode) + mSceneMgr->destroySceneNode(mSceneNode); + mSceneNode = NULL; } void RippleSimulation::update(float dt, Ogre::Vector2 position) { - // try to keep 20 fps - mTime += dt; - - while (mTime >= 1/20.0 || mFirstUpdate) - { - mPreviousFrameOffset = mCurrentFrameOffset; - - mCurrentFrameOffset = position - mRippleCenter; - // add texel offsets from previous frame. - mCurrentFrameOffset += mTexelOffset; - - mTexelOffset = Ogre::Vector2(std::fmod(mCurrentFrameOffset.x, 1.0f/mTextureSize), - std::fmod(mCurrentFrameOffset.y, 1.0f/mTextureSize)); - - // now subtract new offset in order to snap to texels - mCurrentFrameOffset -= mTexelOffset; - - // texture coordinate space - mCurrentFrameOffset /= mRippleAreaLength; - - mRippleCenter = position; - - addImpulses(); - waterSimulation(); - heightMapToNormalMap(); - - swapHeightMaps(); - if (!mFirstUpdate) - mTime -= 1/20.0; - else - mFirstUpdate = false; - } - - sh::Factory::getInstance().setSharedParameter("rippleCenter", sh::makeProperty( - new sh::Vector3(mRippleCenter.x + mTexelOffset.x, mRippleCenter.y + mTexelOffset.y, 0))); -} - -void RippleSimulation::addImpulses() -{ - mRectangle->setVisible(false); - mImpulse->setVisible(true); - - /// \todo it should be more efficient to render all emitters at once + bool newParticle = false; for (std::vector::iterator it=mEmitters.begin(); it !=mEmitters.end(); ++it) { if (it->mPtr == MWBase::Environment::get().getWorld ()->getPlayerPtr()) @@ -165,69 +75,50 @@ void RippleSimulation::addImpulses() // for non-player actors this is done in updateObjectCell it->mPtr = MWBase::Environment::get().getWorld ()->getPlayerPtr(); } - const float* _currentPos = it->mPtr.getRefData().getPosition().pos; - Ogre::Vector3 currentPos (_currentPos[0], _currentPos[1], _currentPos[2]); - - if ( (currentPos - it->mLastEmitPosition).length() > 2 - && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), currentPos)) + Ogre::Vector3 currentPos (it->mPtr.getRefData().getPosition().pos); + currentPos.z = 0; + if ( (currentPos - it->mLastEmitPosition).length() > 10 + // Only emit when close to the water surface, not above it and not too deep in the water + && MWBase::Environment::get().getWorld ()->isUnderwater (it->mPtr.getCell(), + Ogre::Vector3(it->mPtr.getRefData().getPosition().pos)) + && !MWBase::Environment::get().getWorld()->isSubmerged(it->mPtr)) { it->mLastEmitPosition = currentPos; - Ogre::Vector2 pos (currentPos.x, currentPos.y); - pos -= mRippleCenter; - pos /= mRippleAreaLength; - float size = mImpulseSize / mRippleAreaLength; - mImpulse->setCorners(pos.x-size, pos.y+size, pos.x+size, pos.y-size, false); - - // don't render if we are offscreen - if (pos.x - size >= 1.0 || pos.y+size <= -1.0 || pos.x+size <= -1.0 || pos.y-size >= 1.0) - continue; - mRenderTargets[1]->update(); + newParticle = true; + Ogre::Particle* created = mParticleSystem->createParticle(); + if (!created) + break; // TODO: cleanup the oldest particle to make room +#if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3& position = created->mPosition; + Ogre::Vector3& direction = created->mDirection; + Ogre::ColourValue& colour = created->mColour; + float& totalTimeToLive = created->mTotalTimeToLive; + float& timeToLive = created->mTimeToLive; + Ogre::Radian& rotSpeed = created->mRotationSpeed; + Ogre::Radian& rotation = created->mRotation; +#else + Ogre::Vector3& position = created->position; + Ogre::Vector3& direction = created->direction; + Ogre::ColourValue& colour = created->colour; + float& totalTimeToLive = created->totalTimeToLive; + float& timeToLive = created->timeToLive; + Ogre::Radian& rotSpeed = created->rotationSpeed; + Ogre::Radian& rotation = created->rotation; +#endif + timeToLive = totalTimeToLive = mRippleLifeTime; + colour = Ogre::ColourValue(0.f, 0.f, 0.f, 0.7); // Water_RippleAlphas.x? + direction = Ogre::Vector3(0,0,0); + position = currentPos; + position.z = 0; // Z is set by the Scene Node + rotSpeed = mRippleRotSpeed; + rotation = Ogre::Radian(Ogre::Math::RangeRandom(-Ogre::Math::PI, Ogre::Math::PI)); + created->setDimensions(50,50); } } - mImpulse->setVisible(false); - mRectangle->setVisible(true); -} - -void RippleSimulation::waterSimulation() -{ - mRectangle->setMaterial("HeightmapSimulation"); - - sh::Factory::getInstance().setTextureAlias("Heightmap0", mTextures[0]->getName()); - sh::Factory::getInstance().setTextureAlias("Heightmap1", mTextures[1]->getName()); - - sh::Factory::getInstance().setSharedParameter("currentFrameOffset", sh::makeProperty( - new sh::Vector3(mCurrentFrameOffset.x, mCurrentFrameOffset.y, 0))); - sh::Factory::getInstance().setSharedParameter("previousFrameOffset", sh::makeProperty( - new sh::Vector3(mPreviousFrameOffset.x, mPreviousFrameOffset.y, 0))); - - mRenderTargets[2]->update(); -} - -void RippleSimulation::heightMapToNormalMap() -{ - mRectangle->setMaterial("HeightToNormalMap"); - - sh::Factory::getInstance().setTextureAlias("Heightmap2", mTextures[2]->getName()); - - mRenderTargets[TEX_NORMAL]->update(); -} - -void RippleSimulation::swapHeightMaps() -{ - // 0 -> 1 -> 2 to 2 -> 0 ->1 - Ogre::RenderTexture* tmp = mRenderTargets[0]; - Ogre::TexturePtr tmp2 = mTextures[0]; - - mRenderTargets[0] = mRenderTargets[1]; - mTextures[0] = mTextures[1]; - - mRenderTargets[1] = mRenderTargets[2]; - mTextures[1] = mTextures[2]; - - mRenderTargets[2] = tmp; - mTextures[2] = tmp2; + if (newParticle) // now apparently needs another update, otherwise it won't render in the first frame after a particle is created. TODO: patch Ogre to handle this better + mParticleSystem->_update(0.f); } void RippleSimulation::addEmitter(const MWWorld::Ptr& ptr, float scale, float force) @@ -264,5 +155,15 @@ void RippleSimulation::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld: } } +void RippleSimulation::setWaterHeight(float height) +{ + mSceneNode->setPosition(0,0,height); +} + +void RippleSimulation::clear() +{ + mParticleSystem->clear(); +} + } diff --git a/apps/openmw/mwrender/ripplesimulation.hpp b/apps/openmw/mwrender/ripplesimulation.hpp index e203212cf0..4551476a4c 100644 --- a/apps/openmw/mwrender/ripplesimulation.hpp +++ b/apps/openmw/mwrender/ripplesimulation.hpp @@ -1,19 +1,19 @@ #ifndef RIPPLE_SIMULATION_H #define RIPPLE_SIMULATION_H -#include -#include -#include #include #include "../mwworld/ptr.hpp" namespace Ogre { - class RenderTexture; - class Camera; class SceneManager; - class Rectangle2D; + class ParticleSystem; +} + +namespace MWWorld +{ + class Fallback; } namespace MWRender @@ -30,9 +30,11 @@ struct Emitter class RippleSimulation { public: - RippleSimulation(Ogre::SceneManager* mainSceneManager); + RippleSimulation(Ogre::SceneManager* mainSceneManager, const MWWorld::Fallback* fallback); ~RippleSimulation(); + /// @param dt Time since the last frame + /// @param position Position of the player void update(float dt, Ogre::Vector2 position); /// adds an emitter, position will be tracked automatically @@ -40,47 +42,21 @@ public: void removeEmitter (const MWWorld::Ptr& ptr); void updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr); + /// Change the height of the water surface, thus moving all ripples with it + void setWaterHeight(float height); + + /// Remove all active ripples + void clear(); + private: - RippleSimulation(const RippleSimulation&); - RippleSimulation& operator=(const RippleSimulation&); + Ogre::SceneManager* mSceneMgr; + Ogre::ParticleSystem* mParticleSystem; + Ogre::SceneNode* mSceneNode; std::vector mEmitters; - Ogre::RenderTexture* mRenderTargets[4]; - Ogre::TexturePtr mTextures[4]; - - int mTextureSize; - float mRippleAreaLength; - float mImpulseSize; - - bool mFirstUpdate; - - Ogre::Camera* mCamera; - - // own scenemanager to render our simulation - Ogre::SceneManager* mSceneMgr; - Ogre::Rectangle2D* mRectangle; - - // scenemanager to create the debug overlays on - Ogre::SceneManager* mMainSceneMgr; - - static const int TEX_NORMAL = 3; - - Ogre::Rectangle2D* mImpulse; - - void addImpulses(); - void heightMapToNormalMap(); - void waterSimulation(); - void swapHeightMaps(); - - float mTime; - - Ogre::Vector2 mRippleCenter; - - Ogre::Vector2 mTexelOffset; - - Ogre::Vector2 mCurrentFrameOffset; - Ogre::Vector2 mPreviousFrameOffset; + float mRippleLifeTime; + float mRippleRotSpeed; }; } diff --git a/apps/openmw/mwrender/sky.cpp b/apps/openmw/mwrender/sky.cpp index 1841021279..f6287de5ef 100644 --- a/apps/openmw/mwrender/sky.cpp +++ b/apps/openmw/mwrender/sky.cpp @@ -8,6 +8,7 @@ #include #include #include +#include #include #include #include @@ -74,6 +75,7 @@ BillboardObject::BillboardObject( const String& textureName, const Vector3& position, SceneNode* rootNode, const std::string& material) +: mVisibility(1.0f) { SceneManager* sceneMgr = rootNode->getCreator(); @@ -103,11 +105,6 @@ BillboardObject::BillboardObject( const String& textureName, bodyCount++; } -BillboardObject::BillboardObject() -: mNode(NULL), mMaterial(NULL), mEntity(NULL) -{ -} - void BillboardObject::requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration) { } @@ -186,9 +183,12 @@ Moon::Moon( const String& textureName, SceneNode* rootNode, const std::string& material) : BillboardObject(textureName, initialSize, position, rootNode, material) + , mType(Type_Masser) { setVisibility(1.0); + mMaterial->setProperty("alphatexture", sh::makeProperty(new sh::StringValue(textureName + "_alpha"))); + mPhase = Moon::Phase_Full; } @@ -217,9 +217,15 @@ void Moon::setPhase(const Moon::Phase& phase) textureName += ".dds"; if (mType == Moon::Type_Secunda) + { sh::Factory::getInstance ().setTextureAlias ("secunda_texture", textureName); + sh::Factory::getInstance ().setTextureAlias ("secunda_texture_alpha", "textures\\tx_mooncircle_full_s.dds"); + } else + { sh::Factory::getInstance ().setTextureAlias ("masser_texture", textureName); + sh::Factory::getInstance ().setTextureAlias ("masser_texture_alpha", "textures\\tx_mooncircle_full_m.dds"); + } mPhase = phase; } @@ -434,7 +440,10 @@ void SkyManager::updateRain(float dt) Ogre::Vector3 pos = it->first->getPosition(); pos.z -= mRainSpeed * dt; it->first->setPosition(pos); - if (pos.z < -minHeight) + if (pos.z < -minHeight + // Here we might want to add a "splash" effect later + || MWBase::Environment::get().getWorld()->isUnderwater( + MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), it->first->_getDerivedPosition())) { it->second.setNull(); mSceneMgr->destroySceneNode(it->first); @@ -461,6 +470,12 @@ void SkyManager::updateRain(float dt) // Create a separate node to control the offset, since a node with setInheritOrientation(false) will still // consider the orientation of the parent node for its position, just not for its orientation float startHeight = 700; + Ogre::Vector3 worldPos = mParticleNode->_getDerivedPosition(); + worldPos += Ogre::Vector3(xOffs, yOffs, startHeight); + if (MWBase::Environment::get().getWorld()->isUnderwater( + MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), worldPos)) + return; + Ogre::SceneNode* offsetNode = mParticleNode->createChildSceneNode(Ogre::Vector3(xOffs,yOffs,startHeight)); // Spawn a new rain object for each instance. @@ -493,6 +508,30 @@ void SkyManager::update(float duration) for (unsigned int i=0; imControllers.size(); ++i) mParticle->mControllers[i].update(); + for (unsigned int i=0; imParticles.size(); ++i) + { + Ogre::ParticleSystem* psys = mParticle->mParticles[i]; + Ogre::ParticleIterator pi = psys->_getIterator(); + while (!pi.end()) + { + Ogre::Particle *p = pi.getNext(); + #if OGRE_VERSION >= (1 << 16 | 10 << 8 | 0) + Ogre::Vector3 pos = p->mPosition; + Ogre::Real& timeToLive = p->mTimeToLive; + #else + Ogre::Vector3 pos = p->position; + Ogre::Real& timeToLive = p->timeToLive; + #endif + + if (psys->getKeepParticlesInLocalSpace() && psys->getParentNode()) + pos = psys->getParentNode()->convertLocalToWorldPosition(pos); + + if (MWBase::Environment::get().getWorld()->isUnderwater( + MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(), pos)) + timeToLive = 0; + } + } + if (mIsStorm) mParticleNode->setOrientation(Ogre::Vector3::UNIT_Y.getRotationTo(mStormDirection)); } @@ -728,14 +767,14 @@ void SkyManager::setStormDirection(const Vector3 &direction) mStormDirection = direction; } -void SkyManager::setSunDirection(const Vector3& direction, bool is_moon) +void SkyManager::setSunDirection(const Vector3& direction, bool is_night) { if (!mCreated) return; mSun->setPosition(direction); mSunGlare->setPosition(direction); float height = direction.z; - float fade = is_moon ? 0.0 : (( height > 0.5) ? 1.0 : height * 2); + float fade = is_night ? 0.0 : (( height > 0.5) ? 1.0 : height * 2); sh::Factory::getInstance ().setSharedParameter ("waterSunFade_sunHeight", sh::makeProperty(new sh::Vector2(fade, height))); } diff --git a/apps/openmw/mwrender/sky.hpp b/apps/openmw/mwrender/sky.hpp index 0544f17eff..6950dbab34 100644 --- a/apps/openmw/mwrender/sky.hpp +++ b/apps/openmw/mwrender/sky.hpp @@ -39,7 +39,6 @@ namespace MWRender Ogre::SceneNode* rootNode, const std::string& material ); - BillboardObject(); void requestedConfiguration (sh::MaterialInstance* m, const std::string& configuration); void createdConfiguration (sh::MaterialInstance* m, const std::string& configuration); @@ -154,7 +153,7 @@ namespace MWRender void setStormDirection(const Ogre::Vector3& direction); - void setSunDirection(const Ogre::Vector3& direction, bool is_moon); + void setSunDirection(const Ogre::Vector3& direction, bool is_night); void setMasserDirection(const Ogre::Vector3& direction); diff --git a/apps/openmw/mwrender/terrainstorage.cpp b/apps/openmw/mwrender/terrainstorage.cpp index cbd9e24443..e74b6af124 100644 --- a/apps/openmw/mwrender/terrainstorage.cpp +++ b/apps/openmw/mwrender/terrainstorage.cpp @@ -9,6 +9,22 @@ namespace MWRender { + TerrainStorage::TerrainStorage(bool preload) + { + if (preload) + { + const MWWorld::ESMStore &esmStore = + MWBase::Environment::get().getWorld()->getStore(); + + MWWorld::Store::iterator it = esmStore.get().begin(); + for (; it != esmStore.get().end(); ++it) + { + ESM::Land* land = const_cast(&*it); // TODO: fix store interface + land->loadData(ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX); + } + } + } + void TerrainStorage::getBounds(float& minX, float& maxX, float& minY, float& maxY) { minX = 0, minY = 0, maxX = 0, maxY = 0; @@ -39,6 +55,12 @@ namespace MWRender const MWWorld::ESMStore &esmStore = MWBase::Environment::get().getWorld()->getStore(); ESM::Land* land = esmStore.get().search(cellX, cellY); + if (!land) + return NULL; + + const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; + if (!land->isDataLoaded(flags)) + land->loadData(flags); return land; } diff --git a/apps/openmw/mwrender/terrainstorage.hpp b/apps/openmw/mwrender/terrainstorage.hpp index 30a2a61ac3..e6f4a04ad3 100644 --- a/apps/openmw/mwrender/terrainstorage.hpp +++ b/apps/openmw/mwrender/terrainstorage.hpp @@ -14,6 +14,10 @@ namespace MWRender virtual const ESM::LandTexture* getLandTexture(int index, short plugin); public: + ///@param preload Preload all Land records at startup? If using the multithreaded terrain component, this + /// should be set to "true" in order to avoid race conditions. + TerrainStorage(bool preload); + /// Get bounds of the whole terrain in cell units virtual void getBounds(float& minX, float& maxX, float& minY, float& maxY); }; diff --git a/apps/openmw/mwrender/water.cpp b/apps/openmw/mwrender/water.cpp index fd790b363a..456fc47e97 100644 --- a/apps/openmw/mwrender/water.cpp +++ b/apps/openmw/mwrender/water.cpp @@ -20,9 +20,6 @@ #include #include -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - using namespace Ogre; namespace MWRender @@ -187,7 +184,7 @@ void PlaneReflection::setVisibilityMask (int flags) // -------------------------------------------------------------------------------------------------------------------------------- -Water::Water (Ogre::Camera *camera, RenderingManager* rend) : +Water::Water (Ogre::Camera *camera, RenderingManager* rend, const MWWorld::Fallback* fallback) : mCamera (camera), mSceneMgr (camera->getSceneManager()), mIsUnderwater(false), mVisibilityFlags(0), mActive(1), mToggled(1), @@ -198,7 +195,7 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend) : mSimulation(NULL), mPlayer(0,0) { - mSimulation = new RippleSimulation(mSceneMgr); + mSimulation = new RippleSimulation(mSceneMgr, fallback); mSky = rend->getSkyManager(); @@ -210,10 +207,10 @@ Water::Water (Ogre::Camera *camera, RenderingManager* rend) : mWaterPlane = Plane(Vector3::UNIT_Z, 0); - int waterScale = 300; + int waterScale = 30; MeshManager::getSingleton().createPlane("water", ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME, mWaterPlane, - CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 10, 10, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y); + CELL_SIZE*5*waterScale, CELL_SIZE*5*waterScale, 40, 40, true, 1, 3*waterScale,3*waterScale, Vector3::UNIT_Y); mWater = mSceneMgr->createEntity("water"); mWater->setVisibilityFlags(RV_Water); @@ -305,11 +302,7 @@ Water::~Water() void Water::changeCell(const ESM::Cell* cell) { - mTop = cell->mWater; - - setHeight(mTop); - - if(!(cell->mData.mFlags & cell->Interior)) + if(cell->isExterior()) mWaterNode->setPosition(getSceneNodeCoordinates(cell->mData.mX, cell->mData.mY)); } @@ -317,6 +310,8 @@ void Water::setHeight(const float height) { mTop = height; + mSimulation->setWaterHeight(height); + mWaterPlane = Plane(Vector3::UNIT_Z, -height); if (mReflection) @@ -386,9 +381,13 @@ void Water::update(float dt, Ogre::Vector3 player) void Water::frameStarted(float dt) { + if (!mActive) + return; + + mSimulation->update(dt, mPlayer); + if (mReflection) { - mSimulation->update(dt, mPlayer); mReflection->update(); } } @@ -424,6 +423,7 @@ void Water::applyVisibilityMask() mVisibilityFlags = RV_Terrain * Settings::Manager::getBool("reflect terrain", "Water") + (RV_Statics + RV_StaticsSmall + RV_Misc) * Settings::Manager::getBool("reflect statics", "Water") + RV_Actors * Settings::Manager::getBool("reflect actors", "Water") + + RV_Effects + RV_Sky; if (mReflection) @@ -500,4 +500,9 @@ void Water::updateEmitterPtr (const MWWorld::Ptr& old, const MWWorld::Ptr& ptr) mSimulation->updateEmitterPtr(old, ptr); } +void Water::clearRipples() +{ + mSimulation->clear(); +} + } // namespace diff --git a/apps/openmw/mwrender/water.hpp b/apps/openmw/mwrender/water.hpp index 6a7b05a3aa..10d0a06ffb 100644 --- a/apps/openmw/mwrender/water.hpp +++ b/apps/openmw/mwrender/water.hpp @@ -30,6 +30,11 @@ namespace Ogre struct RenderTargetEvent; } +namespace MWWorld +{ + class Fallback; +} + namespace MWRender { class SkyManager; @@ -145,9 +150,11 @@ namespace MWRender { Ogre::Vector2 mPlayer; public: - Water (Ogre::Camera *camera, RenderingManager* rend); + Water (Ogre::Camera *camera, RenderingManager* rend, const MWWorld::Fallback* fallback); ~Water(); + void clearRipples(); + void setActive(bool active); bool toggle(); diff --git a/apps/openmw/mwrender/weaponanimation.cpp b/apps/openmw/mwrender/weaponanimation.cpp index c59a93feba..d16afe3ce2 100644 --- a/apps/openmw/mwrender/weaponanimation.cpp +++ b/apps/openmw/mwrender/weaponanimation.cpp @@ -7,12 +7,14 @@ #include "../mwbase/world.hpp" #include "../mwbase/environment.hpp" +#include "../mwbase/soundmanager.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/esmstore.hpp" #include "../mwmechanics/creaturestats.hpp" +#include "../mwmechanics/combat.hpp" #include "animation.hpp" @@ -44,19 +46,37 @@ void WeaponAnimation::attachArrow(MWWorld::Ptr actor) { MWWorld::InventoryStore& inv = actor.getClass().getInventoryStore(actor); MWWorld::ContainerStoreIterator weaponSlot = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); - if (weaponSlot != inv.end() && weaponSlot->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) + if (weaponSlot == inv.end()) + return; + if (weaponSlot->getTypeName() != typeid(ESM::Weapon).name()) + return; + int weaponType = weaponSlot->get()->mBase->mData.mType; + if (weaponType == ESM::Weapon::MarksmanThrown) + { + std::string soundid = weaponSlot->getClass().getUpSoundId(*weaponSlot); + if(!soundid.empty()) + { + MWBase::SoundManager *sndMgr = MWBase::Environment::get().getSoundManager(); + sndMgr->playSound3D(actor, soundid, 1.0f, 1.0f); + } showWeapon(true); - else + } + else if (weaponType == ESM::Weapon::MarksmanBow || weaponType == ESM::Weapon::MarksmanCrossbow) { NifOgre::ObjectScenePtr weapon = getWeapon(); + if (!weapon.get()) + return; MWWorld::ContainerStoreIterator ammo = inv.getSlot(MWWorld::InventoryStore::Slot_Ammunition); if (ammo == inv.end()) return; std::string model = ammo->getClass().getModel(*ammo); - assert(weapon->mSkelBase && "Need a skeleton to attach the arrow to"); - mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, "ArrowBone", weapon->mSkelBase->getParentSceneNode(), model); + if (!weapon->mSkelBase) + throw std::runtime_error("Need a skeleton to attach the arrow to"); + + const std::string bonename = "ArrowBone"; + mAmmunition = NifOgre::Loader::createObjects(weapon->mSkelBase, bonename, bonename, weapon->mSkelBase->getParentSceneNode(), model); configureAddedObject(mAmmunition, *ammo, MWWorld::InventoryStore::Slot_Ammunition); } } @@ -67,6 +87,8 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) MWWorld::ContainerStoreIterator weapon = inv.getSlot(MWWorld::InventoryStore::Slot_CarriedRight); if (weapon == inv.end()) return; + if (weapon->getTypeName() != typeid(ESM::Weapon).name()) + return; // The orientation of the launched projectile. Always the same as the actor orientation, even if the ArrowBone's orientation dictates otherwise. Ogre::Quaternion orient = Ogre::Quaternion(Ogre::Radian(actor.getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * @@ -75,19 +97,7 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) const MWWorld::Store &gmst = MWBase::Environment::get().getWorld()->getStore().get(); - // 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::CreatureStats& attackerStats = actor.getClass().getCreatureStats(actor); - MWMechanics::DynamicStat fatigue = attackerStats.getFatigue(); - const float normalizedEncumbrance = actor.getClass().getNormalizedEncumbrance(actor); - float fatigueLoss = fFatigueAttackBase + normalizedEncumbrance * fFatigueAttackMult; - if (!weapon->isEmpty()) - fatigueLoss += weapon->getClass().getWeight(*weapon) * attackerStats.getAttackStrength() * fWeaponFatigueMult; - fatigue.setCurrent(fatigue.getCurrent() - fatigueLoss); - attackerStats.setFatigue(fatigue); + MWMechanics::applyFatigueLoss(actor, *weapon); if (weapon->get()->mBase->mData.mType == ESM::Weapon::MarksmanThrown) { @@ -123,6 +133,9 @@ void WeaponAnimation::releaseArrow(MWWorld::Ptr actor) if (ammo == inv.end()) return; + if (!mAmmunition.get()) + return; + Ogre::Vector3 launchPos(0,0,0); if (mAmmunition->mSkelBase) { diff --git a/apps/openmw/mwrender/weaponanimation.hpp b/apps/openmw/mwrender/weaponanimation.hpp index cbe910c716..e1ccd94656 100644 --- a/apps/openmw/mwrender/weaponanimation.hpp +++ b/apps/openmw/mwrender/weaponanimation.hpp @@ -35,8 +35,11 @@ namespace MWRender WeaponAnimation() : mPitchFactor(0) {} virtual ~WeaponAnimation() {} - virtual void attachArrow(MWWorld::Ptr actor); - virtual void releaseArrow(MWWorld::Ptr actor); + /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. + void attachArrow(MWWorld::Ptr actor); + + /// @note If no weapon (or an invalid weapon) is equipped, this function is a no-op. + void releaseArrow(MWWorld::Ptr actor); protected: NifOgre::ObjectScenePtr mAmmunition; diff --git a/apps/openmw/mwscript/animationextensions.cpp b/apps/openmw/mwscript/animationextensions.cpp index 613cf7d24e..c43cdf5650 100644 --- a/apps/openmw/mwscript/animationextensions.cpp +++ b/apps/openmw/mwscript/animationextensions.cpp @@ -2,6 +2,7 @@ #include "animationextensions.hpp" #include +#include #include #include @@ -10,6 +11,7 @@ #include #include +#include "../mwbase/environment.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "interpretercontext.hpp" diff --git a/apps/openmw/mwscript/cellextensions.cpp b/apps/openmw/mwscript/cellextensions.cpp index a568b79438..43d213c5af 100644 --- a/apps/openmw/mwscript/cellextensions.cpp +++ b/apps/openmw/mwscript/cellextensions.cpp @@ -117,18 +117,9 @@ namespace MWScript runtime.push(0); return; } - const ESM::Cell *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell()->getCell(); + const MWWorld::CellStore *cell = MWBase::Environment::get().getWorld()->getPlayerPtr().getCell(); - std::string current = cell->mName; - - if (!(cell->mData.mFlags & ESM::Cell::Interior) && current.empty() - && !cell->mRegion.empty()) - { - const ESM::Region *region = - MWBase::Environment::get().getWorld()->getStore().get().find (cell->mRegion); - - current = region->mName; - } + std::string current = MWBase::Environment::get().getWorld()->getCellName(cell); Misc::StringUtils::toLower(current); bool match = current.length()>=name.length() && diff --git a/apps/openmw/mwscript/compilercontext.hpp b/apps/openmw/mwscript/compilercontext.hpp index 95719ab692..010926f451 100644 --- a/apps/openmw/mwscript/compilercontext.hpp +++ b/apps/openmw/mwscript/compilercontext.hpp @@ -12,7 +12,7 @@ namespace MWScript enum Type { Type_Full, // global, local, targetted - Type_Dialgoue, + Type_Dialogue, Type_Console }; diff --git a/apps/openmw/mwscript/containerextensions.cpp b/apps/openmw/mwscript/containerextensions.cpp index 96e30e3966..86329191e3 100644 --- a/apps/openmw/mwscript/containerextensions.cpp +++ b/apps/openmw/mwscript/containerextensions.cpp @@ -14,10 +14,13 @@ #include #include +#include + #include #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/containerstore.hpp" @@ -71,8 +74,7 @@ namespace MWScript msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage61}"); msgBox = boost::str(boost::format(msgBox) % count % itemName); } - std::vector noButtons; - MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only); + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } }; @@ -143,8 +145,7 @@ namespace MWScript msgBox = MyGUI::LanguageManager::getInstance().replaceTags("#{sNotifyMessage62}"); msgBox = boost::str (boost::format(msgBox) % itemName); } - std::vector noButtons; - MWBase::Environment::get().getWindowManager()->messageBox(msgBox, noButtons, MWGui::ShowInDialogueMode_Only); + MWBase::Environment::get().getWindowManager()->messageBox(msgBox, MWGui::ShowInDialogueMode_Only); } } }; diff --git a/apps/openmw/mwscript/controlextensions.cpp b/apps/openmw/mwscript/controlextensions.cpp index fb6b73be63..fd7fe47371 100644 --- a/apps/openmw/mwscript/controlextensions.cpp +++ b/apps/openmw/mwscript/controlextensions.cpp @@ -10,6 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/inputmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/ptr.hpp" diff --git a/apps/openmw/mwscript/dialogueextensions.cpp b/apps/openmw/mwscript/dialogueextensions.cpp index 563a9dde3a..8b68052647 100644 --- a/apps/openmw/mwscript/dialogueextensions.cpp +++ b/apps/openmw/mwscript/dialogueextensions.cpp @@ -11,6 +11,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/journal.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwmechanics/npcstats.hpp" @@ -196,7 +197,7 @@ namespace MWScript MWWorld::Ptr player = MWBase::Environment::get().getWorld ()->getPlayerPtr(); - runtime.push (ptr.getClass().getNpcStats (ptr).isSameFaction (player.getClass().getNpcStats (player))); + player.getClass().getNpcStats (player).isInFaction(ptr.getClass().getPrimaryFaction(ptr)); } }; @@ -236,6 +237,25 @@ namespace MWScript } }; + class OpSetFactionReaction : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + std::string faction1 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + std::string faction2 = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + int newValue = runtime[0].mInteger; + runtime.pop(); + + MWBase::Environment::get().getDialogueManager()->setFactionReaction(faction1, faction2, newValue); + } + }; + template class OpClearInfoActor : public Interpreter::Opcode0 { @@ -268,6 +288,7 @@ namespace MWScript interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFaction, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeSameFactionExplicit, new OpSameFaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeModFactionReaction, new OpModFactionReaction); + interpreter.installSegment5 (Compiler::Dialogue::opcodeSetFactionReaction, new OpSetFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeGetFactionReaction, new OpGetFactionReaction); interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActor, new OpClearInfoActor); interpreter.installSegment5 (Compiler::Dialogue::opcodeClearInfoActorExplicit, new OpClearInfoActor); diff --git a/apps/openmw/mwscript/docs/vmformat.txt b/apps/openmw/mwscript/docs/vmformat.txt index b80c84d674..93720aef62 100644 --- a/apps/openmw/mwscript/docs/vmformat.txt +++ b/apps/openmw/mwscript/docs/vmformat.txt @@ -433,5 +433,17 @@ op 0x20002c4-0x20002db: ModMagicEffect op 0x20002dc-0x20002f3: ModMagicEffect, explicit op 0x20002f4: ResetActors op 0x20002f5: ToggleWorld +op 0x20002f6: PCForce1stPerson +op 0x20002f7: PCForce3rdPerson +op 0x20002f8: PCGet3rdPerson +op 0x20002f9: HitAttemptOnMe +op 0x20002fa: HitAttemptOnMe, explicit +op 0x20002fb: AddToLevCreature +op 0x20002fc: RemoveFromLevCreature +op 0x20002fd: AddToLevItem +op 0x20002fe: RemoveFromLevItem +op 0x20002ff: SetFactionReaction +op 0x2000300: EnableLevelupMenu +op 0x2000301: ToggleScripts -opcodes 0x20002f6-0x3ffffff unused +opcodes 0x2000302-0x3ffffff unused diff --git a/apps/openmw/mwscript/globalscripts.cpp b/apps/openmw/mwscript/globalscripts.cpp index b409a304c9..92fd51d87d 100644 --- a/apps/openmw/mwscript/globalscripts.cpp +++ b/apps/openmw/mwscript/globalscripts.cpp @@ -2,6 +2,7 @@ #include "globalscripts.hpp" #include +#include #include #include @@ -142,11 +143,10 @@ namespace MWScript writer.startRecord (ESM::REC_GSCR); script.save (writer); writer.endRecord (ESM::REC_GSCR); - progress.increaseProgress(); } } - bool GlobalScripts::readRecord (ESM::ESMReader& reader, int32_t type) + bool GlobalScripts::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_GSCR) { diff --git a/apps/openmw/mwscript/globalscripts.hpp b/apps/openmw/mwscript/globalscripts.hpp index 55c2e93217..9f009db98f 100644 --- a/apps/openmw/mwscript/globalscripts.hpp +++ b/apps/openmw/mwscript/globalscripts.hpp @@ -62,7 +62,7 @@ namespace MWScript void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, int32_t type); + bool readRecord (ESM::ESMReader& reader, uint32_t type); ///< Records for variables that do not exist are dropped silently. /// /// \return Known type? diff --git a/apps/openmw/mwscript/guiextensions.cpp b/apps/openmw/mwscript/guiextensions.cpp index 1d34adbca0..40c555f503 100644 --- a/apps/openmw/mwscript/guiextensions.cpp +++ b/apps/openmw/mwscript/guiextensions.cpp @@ -12,7 +12,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" - +#include "../mwbase/world.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "interpretercontext.hpp" @@ -231,6 +231,8 @@ namespace MWScript new OpShowDialogue (MWGui::GM_Race)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableStatsReviewMenu, new OpShowDialogue (MWGui::GM_Review)); + interpreter.installSegment5 (Compiler::Gui::opcodeEnableLevelupMenu, + new OpShowDialogue (MWGui::GM_Levelup)); interpreter.installSegment5 (Compiler::Gui::opcodeEnableInventoryMenu, new OpEnableWindow (MWGui::GW_Inventory)); diff --git a/apps/openmw/mwscript/interpretercontext.cpp b/apps/openmw/mwscript/interpretercontext.cpp index d8d13a9211..435f82de7a 100644 --- a/apps/openmw/mwscript/interpretercontext.cpp +++ b/apps/openmw/mwscript/interpretercontext.cpp @@ -200,12 +200,14 @@ namespace MWScript void InterpreterContext::messageBox (const std::string& message, const std::vector& buttons) { - MWBase::Environment::get().getWindowManager()->messageBox (message, buttons); + if (buttons.empty()) + MWBase::Environment::get().getWindowManager()->messageBox (message); + else + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); } void InterpreterContext::report (const std::string& message) { - messageBox (message); } bool InterpreterContext::menuMode() @@ -311,17 +313,19 @@ namespace MWScript std::string InterpreterContext::getNPCRank() const { - if (getReferenceImp().getClass().getNpcStats(getReferenceImp()).getFactionRanks().empty()) + const MWWorld::Ptr& ptr = getReferenceImp(); + std::string faction = ptr.getClass().getPrimaryFaction(ptr); + if (faction.empty()) throw std::runtime_error("getNPCRank(): NPC is not in a faction"); - const std::map& ranks = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks(); - std::map::const_iterator it = ranks.begin(); + int rank = ptr.getClass().getPrimaryFactionRank(ptr); + if (rank < 0 || rank > 9) + throw std::runtime_error("getNPCRank(): invalid rank"); MWBase::World *world = MWBase::Environment::get().getWorld(); const MWWorld::ESMStore &store = world->getStore(); - const ESM::Faction *faction = store.get().find(it->first); - - return faction->mRanks[it->second]; + const ESM::Faction *fact = store.get().find(faction); + return fact->mRanks[rank]; } std::string InterpreterContext::getPCName() const @@ -350,13 +354,12 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - if (getReferenceImp().getClass().getNpcStats(getReferenceImp()).getFactionRanks().empty()) + std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); + if (factionId.empty()) throw std::runtime_error("getPCRank(): NPC is not in a faction"); - std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first; - const std::map& ranks = player.getClass().getNpcStats (player).getFactionRanks(); - std::map::const_iterator it = ranks.find(factionId); + std::map::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); int rank = -1; if (it != ranks.end()) rank = it->second; @@ -380,13 +383,12 @@ namespace MWScript MWBase::World *world = MWBase::Environment::get().getWorld(); MWWorld::Ptr player = world->getPlayerPtr(); - if (getReferenceImp().getClass().getNpcStats(getReferenceImp()).getFactionRanks().empty()) + std::string factionId = getReferenceImp().getClass().getPrimaryFaction(getReferenceImp()); + if (factionId.empty()) throw std::runtime_error("getPCNextRank(): NPC is not in a faction"); - std::string factionId = getReferenceImp().getClass().getNpcStats (getReferenceImp()).getFactionRanks().begin()->first; - const std::map& ranks = player.getClass().getNpcStats (player).getFactionRanks(); - std::map::const_iterator it = ranks.find(factionId); + std::map::const_iterator it = ranks.find(Misc::StringUtils::lowerCase(factionId)); int rank = -1; if (it != ranks.end()) rank = it->second; @@ -590,4 +592,10 @@ namespace MWScript { return mTargetId; } + + void InterpreterContext::updatePtr(const MWWorld::Ptr& updated) + { + if (!mReference.isEmpty()) + mReference = updated; + } } diff --git a/apps/openmw/mwscript/interpretercontext.hpp b/apps/openmw/mwscript/interpretercontext.hpp index b543399656..698df62c2a 100644 --- a/apps/openmw/mwscript/interpretercontext.hpp +++ b/apps/openmw/mwscript/interpretercontext.hpp @@ -5,8 +5,6 @@ #include -#include "../mwbase/world.hpp" - #include "../mwworld/ptr.hpp" #include "../mwworld/action.hpp" @@ -78,7 +76,7 @@ namespace MWScript const std::vector& buttons); virtual void report (const std::string& message); - ///< By default echo via messageBox. + ///< By default, do nothing. virtual bool menuMode(); @@ -169,6 +167,9 @@ namespace MWScript MWWorld::Ptr getReference(bool required=true); ///< Reference, that the script is running from (can be empty) + void updatePtr(const MWWorld::Ptr& updated); + ///< Update the Ptr stored in mReference, if there is one stored there. Should be called after the reference has been moved to a new cell. + virtual std::string getTargetId() const; }; } diff --git a/apps/openmw/mwscript/locals.cpp b/apps/openmw/mwscript/locals.cpp index a1ee48ae68..9dbf07fba9 100644 --- a/apps/openmw/mwscript/locals.cpp +++ b/apps/openmw/mwscript/locals.cpp @@ -10,6 +10,8 @@ #include "../mwbase/environment.hpp" #include "../mwbase/scriptmanager.hpp" +#include + namespace MWScript { void Locals::configure (const ESM::Script& script) @@ -30,6 +32,21 @@ namespace MWScript return (mShorts.empty() && mLongs.empty() && mFloats.empty()); } + bool Locals::hasVar(const std::string &script, const std::string &var) + { + try + { + const Compiler::Locals& locals = + MWBase::Environment::get().getScriptManager()->getLocals(script); + int index = locals.getIndex(var); + return (index != -1); + } + catch (const Compiler::SourceException&) + { + return false; + } + } + int Locals::getIntVar(const std::string &script, const std::string &var) { const Compiler::Locals& locals = MWBase::Environment::get().getScriptManager()->getLocals(script); @@ -124,27 +141,60 @@ namespace MWScript const Compiler::Locals& declarations = MWBase::Environment::get().getScriptManager()->getLocals(script); - for (std::vector >::const_iterator iter - = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter) + int index = 0, numshorts = 0, numlongs = 0; + for (unsigned int v=0; vfirst); - char index = declarations.getIndex (iter->first); + ESM::VarType type = locals.mVariables[v].second.getType(); + if (type == ESM::VT_Short) + ++numshorts; + else if (type == ESM::VT_Int) + ++numlongs; + } - try + for (std::vector >::const_iterator iter + = locals.mVariables.begin(); iter!=locals.mVariables.end(); ++iter,++index) + { + if (iter->first.empty()) { - switch (type) + // no variable names available (this will happen for legacy, i.e. ESS-imported savegames only) + try { - 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 + if (index >= numshorts+numlongs) + mFloats.at(index - (numshorts+numlongs)) = iter->second.getFloat(); + else if (index >= numshorts) + mLongs.at(index - numshorts) = iter->second.getInteger(); + else + mShorts.at(index) = iter->second.getInteger(); + } + catch (std::exception& e) + { + std::cerr << "Failed to read local variable state for script '" + << script << "' (legacy format): " << e.what() + << "\nNum shorts: " << numshorts << " / " << mShorts.size() + << " Num longs: " << numlongs << " / " << mLongs.size() << std::endl; } } - catch (...) + else { - // ignore type changes - /// \todo write to log + 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 9fa4214acc..bd95835ac1 100644 --- a/apps/openmw/mwscript/locals.hpp +++ b/apps/openmw/mwscript/locals.hpp @@ -24,11 +24,15 @@ namespace MWScript bool isEmpty() const; void configure (const ESM::Script& script); + /// @note var needs to be in lowercase 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 + + bool hasVar(const std::string& script, const std::string& var); + + /// if var does not exist, returns 0 /// @note var needs to be in lowercase + int getIntVar (const std::string& script, const std::string& var); void write (ESM::Locals& locals, const std::string& script) const; diff --git a/apps/openmw/mwscript/miscextensions.cpp b/apps/openmw/mwscript/miscextensions.cpp index ec2048e11f..52094947ce 100644 --- a/apps/openmw/mwscript/miscextensions.cpp +++ b/apps/openmw/mwscript/miscextensions.cpp @@ -17,6 +17,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/scriptmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -31,6 +32,42 @@ #include "interpretercontext.hpp" #include "ref.hpp" +namespace +{ + + void addToLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) + { + for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) + { + if (it->mLevel == level && itemId == it->mId) + return; + } + + ESM::LevelledListBase::LevelItem item; + item.mId = itemId; + item.mLevel = level; + list->mList.push_back(item); + } + + void removeFromLevList(ESM::LevelledListBase* list, const std::string& itemId, int level) + { + // level of -1 removes all items with that itemId + for (std::vector::iterator it = list->mList.begin(); it != list->mList.end();) + { + if (level != -1 && it->mLevel != level) + { + ++it; + continue; + } + if (Misc::StringUtils::ciEqual(itemId, it->mId)) + it = list->mList.erase(it); + else + ++it; + } + } + +} + namespace MWScript { namespace Misc @@ -312,6 +349,35 @@ namespace MWScript } }; + class OpPcForce1stPerson : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + if (!MWBase::Environment::get().getWorld()->isFirstPerson()) + MWBase::Environment::get().getWorld()->togglePOV(); + } + }; + + class OpPcForce3rdPerson : public Interpreter::Opcode0 + { + virtual void execute (Interpreter::Runtime& runtime) + { + if (MWBase::Environment::get().getWorld()->isFirstPerson()) + MWBase::Environment::get().getWorld()->togglePOV(); + } + }; + + class OpPcGet3rdPerson : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime& runtime) + { + runtime.push(!MWBase::Environment::get().getWorld()->isFirstPerson()); + } + }; + class OpToggleVanityMode : public Interpreter::Opcode0 { static bool sActivate; @@ -703,6 +769,27 @@ namespace MWScript MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitObject())); + + stats.setLastHitObject(std::string()); + } + }; + + template + class OpHitAttemptOnMe : public Interpreter::Opcode0 + { + public: + + virtual void execute (Interpreter::Runtime& runtime) + { + MWWorld::Ptr ptr = R()(runtime); + + std::string objectID = runtime.getStringLiteral (runtime[0].mInteger); + runtime.pop(); + + MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); + runtime.push(::Misc::StringUtils::ciEqual(objectID, stats.getLastHitAttemptObject())); + + stats.setLastHitAttemptObject(std::string()); } }; @@ -828,6 +915,19 @@ namespace MWScript } }; + class OpToggleScripts : public Interpreter::Opcode0 + { + public: + virtual void execute (Interpreter::Runtime& runtime) + { + InterpreterContext& context = static_cast (runtime.getContext()); + + bool enabled = MWBase::Environment::get().getWorld()->toggleScripts(); + + context.report(enabled ? "Scripts -> On" : "Scripts -> Off"); + } + }; + class OpToggleGodMode : public Interpreter::Opcode0 { public: @@ -921,8 +1021,7 @@ namespace MWScript virtual void execute (Interpreter::Runtime &runtime) { - /// \todo implement jail check - runtime.push (0); + runtime.push (MWBase::Environment::get().getWindowManager()->containsMode(MWGui::GM_Jail)); } }; @@ -949,13 +1048,14 @@ namespace MWScript msg << "Content file: "; - if (ptr.getCellRef().getRefNum().mContentFile == -1) + if (!ptr.getCellRef().hasContentFile()) msg << "[None]" << std::endl; else { std::vector contentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); msg << contentFiles.at (ptr.getCellRef().getRefNum().mContentFile) << std::endl; + msg << "RefNum: " << ptr.getCellRef().getRefNum().mIndex << std::endl; } msg << "RefID: " << ptr.getCellRef().getRefId() << std::endl; @@ -982,6 +1082,78 @@ namespace MWScript } }; + class OpAddToLevCreature : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& creatureId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + addToLevList(&listCopy, creatureId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + + class OpRemoveFromLevCreature : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& creatureId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::CreatureLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + removeFromLevList(&listCopy, creatureId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + + class OpAddToLevItem : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& itemId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + addToLevList(&listCopy, itemId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + + class OpRemoveFromLevItem : public Interpreter::Opcode0 + { + public: + virtual void execute(Interpreter::Runtime &runtime) + { + const std::string& levId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + const std::string& itemId = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + int level = runtime[0].mInteger; + runtime.pop(); + + ESM::ItemLevList listCopy = *MWBase::Environment::get().getWorld()->getStore().get().find(levId); + removeFromLevList(&listCopy, itemId, level); + MWBase::Environment::get().getWorld()->createOverrideRecord(listCopy); + } + }; + void installOpcodes (Interpreter::Interpreter& interpreter) { interpreter.installSegment5 (Compiler::Misc::opcodeXBox, new OpXBox); @@ -1002,6 +1174,9 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeToggleWater, new OpToggleWater); interpreter.installSegment5 (Compiler::Misc::opcodeToggleWorld, new OpToggleWorld); interpreter.installSegment5 (Compiler::Misc::opcodeDontSaveObject, new OpDontSaveObject); + interpreter.installSegment5 (Compiler::Misc::opcodePcForce1stPerson, new OpPcForce1stPerson); + interpreter.installSegment5 (Compiler::Misc::opcodePcForce3rdPerson, new OpPcForce3rdPerson); + interpreter.installSegment5 (Compiler::Misc::opcodePcGet3rdPerson, new OpPcGet3rdPerson); interpreter.installSegment5 (Compiler::Misc::opcodeToggleVanityMode, new OpToggleVanityMode); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcSleep, new OpGetPcSleep); interpreter.installSegment5 (Compiler::Misc::opcodeGetPcJumping, new OpGetPcJumping); @@ -1051,11 +1226,14 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetWindSpeed, new OpGetWindSpeed); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMe, new OpHitOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeHitOnMeExplicit, new OpHitOnMe); + interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMe, new OpHitAttemptOnMe); + interpreter.installSegment5 (Compiler::Misc::opcodeHitAttemptOnMeExplicit, new OpHitAttemptOnMe); interpreter.installSegment5 (Compiler::Misc::opcodeDisableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeEnableTeleporting, new OpEnableTeleporting); interpreter.installSegment5 (Compiler::Misc::opcodeShowVars, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeShowVarsExplicit, new OpShowVars); interpreter.installSegment5 (Compiler::Misc::opcodeToggleGodMode, new OpToggleGodMode); + interpreter.installSegment5 (Compiler::Misc::opcodeToggleScripts, new OpToggleScripts); interpreter.installSegment5 (Compiler::Misc::opcodeDisableLevitation, new OpEnableLevitation); interpreter.installSegment5 (Compiler::Misc::opcodeEnableLevitation, new OpEnableLevitation); interpreter.installSegment5 (Compiler::Misc::opcodeCast, new OpCast); @@ -1066,6 +1244,10 @@ namespace MWScript interpreter.installSegment5 (Compiler::Misc::opcodeGetPcTraveling, new OpGetPcTraveling); interpreter.installSegment5 (Compiler::Misc::opcodeBetaComment, new OpBetaComment); interpreter.installSegment5 (Compiler::Misc::opcodeBetaCommentExplicit, new OpBetaComment); + interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevCreature, new OpAddToLevCreature); + interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevCreature, new OpRemoveFromLevCreature); + interpreter.installSegment5 (Compiler::Misc::opcodeAddToLevItem, new OpAddToLevItem); + interpreter.installSegment5 (Compiler::Misc::opcodeRemoveFromLevItem, new OpRemoveFromLevItem); } } } diff --git a/apps/openmw/mwscript/ref.cpp b/apps/openmw/mwscript/ref.cpp new file mode 100644 index 0000000000..6347c2c2e5 --- /dev/null +++ b/apps/openmw/mwscript/ref.cpp @@ -0,0 +1,29 @@ +#include "ref.hpp" + +#include + +#include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" + +#include "interpretercontext.hpp" + +MWWorld::Ptr MWScript::ExplicitRef::operator() (Interpreter::Runtime& runtime, bool required, + bool activeOnly) const +{ + std::string id = runtime.getStringLiteral(runtime[0].mInteger); + runtime.pop(); + + if (required) + return MWBase::Environment::get().getWorld()->getPtr(id, activeOnly); + else + return MWBase::Environment::get().getWorld()->searchPtr(id, activeOnly); +} + +MWWorld::Ptr MWScript::ImplicitRef::operator() (Interpreter::Runtime& runtime, bool required, + bool activeOnly) const +{ + MWScript::InterpreterContext& context + = static_cast (runtime.getContext()); + + return context.getReference(required); +} diff --git a/apps/openmw/mwscript/ref.hpp b/apps/openmw/mwscript/ref.hpp index 18f7453e44..e572f51471 100644 --- a/apps/openmw/mwscript/ref.hpp +++ b/apps/openmw/mwscript/ref.hpp @@ -3,14 +3,12 @@ #include -#include - -#include "../mwbase/environment.hpp" -#include "../mwbase/world.hpp" - #include "../mwworld/ptr.hpp" -#include "interpretercontext.hpp" +namespace Interpreter +{ + class Runtime; +} namespace MWScript { @@ -18,31 +16,16 @@ namespace MWScript { static const bool implicit = false; - MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required=true, - bool activeOnly = false) const - { - std::string id = runtime.getStringLiteral (runtime[0].mInteger); - runtime.pop(); - - if (required) - return MWBase::Environment::get().getWorld()->getPtr (id, activeOnly); - else - return MWBase::Environment::get().getWorld()->searchPtr (id, activeOnly); - } + MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, + bool activeOnly = false) const; }; struct ImplicitRef { static const bool implicit = true; - MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required=true, - bool activeOnly = false) const - { - MWScript::InterpreterContext& context - = static_cast (runtime.getContext()); - - return context.getReference(required); - } + MWWorld::Ptr operator() (Interpreter::Runtime& runtime, bool required = true, + bool activeOnly = false) const; }; } diff --git a/apps/openmw/mwscript/skyextensions.cpp b/apps/openmw/mwscript/skyextensions.cpp index 8b9efd74e1..0ccd0ce311 100644 --- a/apps/openmw/mwscript/skyextensions.cpp +++ b/apps/openmw/mwscript/skyextensions.cpp @@ -9,6 +9,7 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" #include "interpretercontext.hpp" diff --git a/apps/openmw/mwscript/soundextensions.cpp b/apps/openmw/mwscript/soundextensions.cpp index 73c3ec93a5..606de7aa01 100644 --- a/apps/openmw/mwscript/soundextensions.cpp +++ b/apps/openmw/mwscript/soundextensions.cpp @@ -121,8 +121,8 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, 1.0, 1.0, MWBase::SoundManager::Play_TypeSfx, - mLoop ? MWBase::SoundManager::Play_Loop : - MWBase::SoundManager::Play_Normal); + mLoop ? MWBase::SoundManager::Play_LoopRemoveAtDistance + : MWBase::SoundManager::Play_Normal); } }; @@ -150,8 +150,8 @@ namespace MWScript MWBase::Environment::get().getSoundManager()->playSound3D(ptr, sound, volume, pitch, MWBase::SoundManager::Play_TypeSfx, - mLoop ? MWBase::SoundManager::Play_Loop : - MWBase::SoundManager::Play_Normal); + mLoop ? MWBase::SoundManager::Play_LoopRemoveAtDistance + : MWBase::SoundManager::Play_Normal); } }; diff --git a/apps/openmw/mwscript/statsextensions.cpp b/apps/openmw/mwscript/statsextensions.cpp index 09ab0183c7..b8bb763885 100644 --- a/apps/openmw/mwscript/statsextensions.cpp +++ b/apps/openmw/mwscript/statsextensions.cpp @@ -18,6 +18,7 @@ #include "../mwbase/dialoguemanager.hpp" #include "../mwbase/mechanicsmanager.hpp" #include "../mwbase/windowmanager.hpp" +#include "../mwbase/world.hpp" #include "../mwworld/class.hpp" #include "../mwworld/player.hpp" @@ -32,13 +33,12 @@ namespace { std::string getDialogueActorFaction(MWWorld::Ptr actor) { - const MWMechanics::NpcStats &stats = actor.getClass().getNpcStats (actor); - - if (stats.getFactionRanks().empty()) + std::string factionId = actor.getClass().getPrimaryFaction(actor); + if (factionId.empty()) throw std::runtime_error ( "failed to determine dialogue actors faction (because actor is factionless)"); - return stats.getFactionRanks().begin()->first; + return factionId; } } @@ -349,29 +349,12 @@ namespace MWScript MWMechanics::NpcStats& stats = ptr.getClass().getNpcStats (ptr); - MWWorld::LiveCellRef *ref = ptr.get(); - - assert (ref); - - const ESM::Class& class_ = - *MWBase::Environment::get().getWorld()->getStore().get().find (ref->mBase->mClass); - - float level = stats.getSkill(mIndex).getBase(); - float progress = stats.getSkill(mIndex).getProgress(); - int newLevel = value - (stats.getSkill(mIndex).getModified() - stats.getSkill(mIndex).getBase()); if (newLevel<0) newLevel = 0; - progress = (progress / stats.getSkillGain (mIndex, class_, -1, level)) - * stats.getSkillGain (mIndex, class_, -1, newLevel); - - if (progress>=1) - progress = 0.999999999; - stats.getSkill (mIndex).setBase (newLevel); - stats.getSkill (mIndex).setProgress(progress); } }; @@ -665,14 +648,7 @@ namespace MWScript } else { - if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) - { - factionID = ""; - } - else - { - factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; - } + factionID = ptr.getClass().getPrimaryFaction(ptr); } ::Misc::StringUtils::toLower(factionID); // Make sure this faction exists @@ -779,8 +755,7 @@ namespace MWScript } else { - if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) - factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; + factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) @@ -815,8 +790,7 @@ namespace MWScript } else { - if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) - factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; + factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) @@ -850,8 +824,7 @@ namespace MWScript } else { - if (!ptr.getClass().getNpcStats (ptr).getFactionRanks().empty()) - factionId = ptr.getClass().getNpcStats (ptr).getFactionRanks().begin()->first; + factionId = getDialogueActorFaction(ptr); } if (factionId.empty()) @@ -941,14 +914,7 @@ namespace MWScript } else { - if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) - { - factionID = ""; - } - else - { - factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; - } + factionID = ptr.getClass().getPrimaryFaction(ptr); } ::Misc::StringUtils::toLower(factionID); MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); @@ -980,14 +946,7 @@ namespace MWScript } else { - if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) - { - factionID = ""; - } - else - { - factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; - } + factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") @@ -1014,14 +973,7 @@ namespace MWScript } else { - if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) - { - factionID = ""; - } - else - { - factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; - } + factionID = ptr.getClass().getPrimaryFaction(ptr); } MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); if(factionID!="") @@ -1038,13 +990,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - std::string factionID = ""; - if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) + std::string factionID = ptr.getClass().getPrimaryFaction(ptr); + if(factionID.empty()) return; - else - { - factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; - } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // no-op when executed on the player @@ -1064,13 +1013,10 @@ namespace MWScript { MWWorld::Ptr ptr = R()(runtime); - std::string factionID = ""; - if(ptr.getClass().getNpcStats(ptr).getFactionRanks().empty()) + std::string factionID = ptr.getClass().getPrimaryFaction(ptr); + if(factionID.empty()) return; - else - { - factionID = ptr.getClass().getNpcStats(ptr).getFactionRanks().begin()->first; - } + MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); // no-op when executed on the player diff --git a/apps/openmw/mwscript/transformationextensions.cpp b/apps/openmw/mwscript/transformationextensions.cpp index 8e6d925b7c..7af35a9ff6 100644 --- a/apps/openmw/mwscript/transformationextensions.cpp +++ b/apps/openmw/mwscript/transformationextensions.cpp @@ -10,7 +10,9 @@ #include #include "../mwbase/environment.hpp" +#include "../mwbase/world.hpp" +#include "../mwworld/cellstore.hpp" #include "../mwworld/class.hpp" #include "../mwworld/manualref.hpp" #include "../mwworld/player.hpp" @@ -224,20 +226,23 @@ namespace MWScript float ay = ptr.getRefData().getPosition().pos[1]; float az = ptr.getRefData().getPosition().pos[2]; + MWWorld::Ptr updated = ptr; if(axis == "x") { - MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,pos,ay,az); } else if(axis == "y") { - MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,pos,az); } else if(axis == "z") { - MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr,ax,ay,pos); } else throw std::runtime_error ("invalid axis: " + axis); + + dynamic_cast(runtime.getContext()).updatePtr(updated); } }; @@ -317,6 +322,8 @@ namespace MWScript { MWBase::Environment::get().getWorld()->moveObject(ptr,store,x,y,z); ptr = MWWorld::Ptr(ptr.getBase(), store); + dynamic_cast(runtime.getContext()).updatePtr(ptr); + float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); // Note that you must specify ZRot in minutes (1 degree = 60 minutes; north = 0, east = 5400, south = 10800, west = 16200) @@ -365,15 +372,18 @@ namespace MWScript // another morrowind oddity: player will be moved to the exterior cell at this location, // non-player actors will move within the cell they are in. + MWWorld::Ptr updated; if (ptr.getRefData().getHandle() == "player") { - MWBase::Environment::get().getWorld()->moveObject(ptr, - MWBase::Environment::get().getWorld()->getExterior(cx,cy),x,y,z); + MWWorld::CellStore* cell = MWBase::Environment::get().getWorld()->getExterior(cx,cy); + MWBase::Environment::get().getWorld()->moveObject(ptr,cell,x,y,z); + updated = MWWorld::Ptr(ptr.getBase(), cell); } else { - MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, x, y, z); } + dynamic_cast(runtime.getContext()).updatePtr(updated); float ax = Ogre::Radian(ptr.getRefData().getPosition().rot[0]).valueDegrees(); float ay = Ogre::Radian(ptr.getRefData().getPosition().rot[1]).valueDegrees(); @@ -433,7 +443,8 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + placed.getClass().adjustPosition(placed, true); } else { @@ -480,7 +491,8 @@ namespace MWScript pos.rot[2] = zRot; MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(),itemID); ref.getPtr().getCellRef().setPosition(pos); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + MWWorld::Ptr placed = MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,pos); + placed.getClass().adjustPosition(placed, true); } }; @@ -508,42 +520,41 @@ namespace MWScript if (count<0) throw std::runtime_error ("count must be non-negative"); - // no-op - if (count == 0) - return; - - ESM::Position ipos = actor.getRefData().getPosition(); - Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); - Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); - if(direction == 0) pos = pos + distance*rot.yAxis(); - else if(direction == 1) pos = pos - distance*rot.yAxis(); - else if(direction == 2) pos = pos - distance*rot.xAxis(); - else if(direction == 3) pos = pos + distance*rot.xAxis(); - else throw std::runtime_error ("direction must be 0,1,2 or 3"); - - ipos.pos[0] = pos.x; - ipos.pos[1] = pos.y; - ipos.pos[2] = pos.z; - - if (actor.getClass().isActor()) + for (int i=0; igetStore(), itemID, count); - ref.getPtr().getCellRef().setPosition(ipos); + ESM::Position ipos = actor.getRefData().getPosition(); + Ogre::Vector3 pos(ipos.pos[0],ipos.pos[1],ipos.pos[2]); + Ogre::Quaternion rot(Ogre::Radian(-ipos.rot[2]), Ogre::Vector3::UNIT_Z); + if(direction == 0) pos = pos + distance*rot.yAxis(); + else if(direction == 1) pos = pos - distance*rot.yAxis(); + else if(direction == 2) pos = pos - distance*rot.xAxis(); + else if(direction == 3) pos = pos + distance*rot.xAxis(); + else throw std::runtime_error ("direction must be 0,1,2 or 3"); - MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + ipos.pos[0] = pos.x; + ipos.pos[1] = pos.y; + ipos.pos[2] = pos.z; + + if (actor.getClass().isActor()) + { + // TODO: should this depend on the 'direction' parameter? + ipos.rot[0] = 0; + ipos.rot[1] = 0; + ipos.rot[2] = 0; + } + else + { + ipos.rot[0] = actor.getRefData().getPosition().rot[0]; + ipos.rot[1] = actor.getRefData().getPosition().rot[1]; + ipos.rot[2] = actor.getRefData().getPosition().rot[2]; + } + // create item + MWWorld::CellStore* store = actor.getCell(); + MWWorld::ManualRef ref(MWBase::Environment::get().getWorld()->getStore(), itemID, 1); + ref.getPtr().getCellRef().setPosition(ipos); + + MWBase::Environment::get().getWorld()->safePlaceObject(ref.getPtr(),store,ipos); + } } }; @@ -638,8 +649,10 @@ namespace MWScript ptr.getRefData().setLocalRotation(rot); MWBase::Environment::get().getWorld()->rotateObject(ptr, 0,0,0,true); - MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], - ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2]); + + dynamic_cast(runtime.getContext()).updatePtr( + MWBase::Environment::get().getWorld()->moveObject(ptr, ptr.getCellRef().getPosition().pos[0], + ptr.getCellRef().getPosition().pos[1], ptr.getCellRef().getPosition().pos[2])); } }; @@ -677,7 +690,12 @@ namespace MWScript else throw std::runtime_error ("invalid movement axis: " + axis); - Ogre::Vector3 worldPos = ptr.getRefData().getBaseNode()->convertLocalToWorldPosition(posChange); + if (!ptr.getRefData().getBaseNode()) + return; + + Ogre::Vector3 diff = ptr.getRefData().getBaseNode()->getOrientation() * posChange; + Ogre::Vector3 worldPos(ptr.getRefData().getPosition().pos); + worldPos += diff; MWBase::Environment::get().getWorld()->moveObject(ptr, worldPos.x, worldPos.y, worldPos.z); } }; @@ -701,17 +719,18 @@ namespace MWScript const float *objPos = ptr.getRefData().getPosition().pos; + MWWorld::Ptr updated; if (axis == "x") { - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0]+movement, objPos[1], objPos[2]); } else if (axis == "y") { - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1]+movement, objPos[2]); } else if (axis == "z") { - MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement); + updated = MWBase::Environment::get().getWorld()->moveObject(ptr, objPos[0], objPos[1], objPos[2]+movement); } else throw std::runtime_error ("invalid movement axis: " + axis); diff --git a/apps/openmw/mwsound/ffmpeg_decoder.cpp b/apps/openmw/mwsound/ffmpeg_decoder.cpp index 2eddc98d3d..bc467acff8 100644 --- a/apps/openmw/mwsound/ffmpeg_decoder.cpp +++ b/apps/openmw/mwsound/ffmpeg_decoder.cpp @@ -25,14 +25,28 @@ void FFmpeg_Decoder::fail(const std::string &msg) int FFmpeg_Decoder::readPacket(void *user_data, uint8_t *buf, int buf_size) { - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->read(buf, buf_size); + try + { + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->read(buf, buf_size); + } + catch (std::exception& e) + { + return 0; + } } int FFmpeg_Decoder::writePacket(void *user_data, uint8_t *buf, int buf_size) { - Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; - return stream->write(buf, buf_size); + try + { + Ogre::DataStreamPtr stream = static_cast(user_data)->mDataStream; + return stream->write(buf, buf_size); + } + catch (std::exception& e) + { + return 0; + } } int64_t FFmpeg_Decoder::seek(void *user_data, int64_t offset, int whence) diff --git a/apps/openmw/mwsound/openal_output.cpp b/apps/openmw/mwsound/openal_output.cpp index 3d2795ce10..bc94789456 100644 --- a/apps/openmw/mwsound/openal_output.cpp +++ b/apps/openmw/mwsound/openal_output.cpp @@ -696,7 +696,7 @@ void OpenAL_Output::init(const std::string &devname) fail(std::string("Failed to setup context: ")+alcGetString(mDevice, alcGetError(mDevice))); } - alDistanceModel(AL_LINEAR_DISTANCE_CLAMPED); + alDistanceModel(AL_INVERSE_DISTANCE_CLAMPED); throwALerror(); ALCint maxmono=0, maxstereo=0; diff --git a/apps/openmw/mwsound/soundmanagerimp.cpp b/apps/openmw/mwsound/soundmanagerimp.cpp index 781bfb5d5a..0bc155118f 100644 --- a/apps/openmw/mwsound/soundmanagerimp.cpp +++ b/apps/openmw/mwsound/soundmanagerimp.cpp @@ -103,24 +103,31 @@ namespace MWSound std::string SoundManager::lookup(const std::string &soundId, float &volume, float &min, float &max) { - const ESM::Sound *snd = - MWBase::Environment::get().getWorld()->getStore().get().find(soundId); + MWBase::World* world = MWBase::Environment::get().getWorld(); + const ESM::Sound *snd = world->getStore().get().find(soundId); volume *= pow(10.0, (snd->mData.mVolume/255.0*3348.0 - 3348.0) / 2000.0); if(snd->mData.mMinRange == 0 && snd->mData.mMaxRange == 0) { - min = 100.0f; - max = 2000.0f; + static const float fAudioDefaultMinDistance = world->getStore().get().find("fAudioDefaultMinDistance")->getFloat(); + static const float fAudioDefaultMaxDistance = world->getStore().get().find("fAudioDefaultMaxDistance")->getFloat(); + min = fAudioDefaultMinDistance; + max = fAudioDefaultMaxDistance; } else { - min = snd->mData.mMinRange * 20.0f; - max = snd->mData.mMaxRange * 50.0f; - min = std::max(min, 1.0f); - max = std::max(min, max); + min = snd->mData.mMinRange; + max = snd->mData.mMaxRange; } + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + min *= fAudioMinDistanceMult; + max *= fAudioMaxDistanceMult; + min = std::max(min, 1.0f); + max = std::max(min, max); + return "Sound/"+snd->mSound; } @@ -250,8 +257,19 @@ namespace MWSound const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos); + MWBase::World* world = MWBase::Environment::get().getWorld(); + static const float fAudioMinDistanceMult = world->getStore().get().find("fAudioMinDistanceMult")->getFloat(); + static const float fAudioMaxDistanceMult = world->getStore().get().find("fAudioMaxDistanceMult")->getFloat(); + static const float fAudioVoiceDefaultMinDistance = world->getStore().get().find("fAudioVoiceDefaultMinDistance")->getFloat(); + static const float fAudioVoiceDefaultMaxDistance = world->getStore().get().find("fAudioVoiceDefaultMaxDistance")->getFloat(); + + float minDistance = fAudioVoiceDefaultMinDistance * fAudioMinDistanceMult; + float maxDistance = fAudioVoiceDefaultMaxDistance * fAudioMaxDistanceMult; + minDistance = std::max(minDistance, 1.f); + maxDistance = std::max(minDistance, maxDistance); + MWBase::SoundPtr sound = mOutput->playSound3D(filePath, objpos, 1.0f, basevol, 1.0f, - 20.0f, 1500.0f, Play_Normal|Play_TypeVoice, 0, true); + minDistance, maxDistance, Play_Normal|Play_TypeVoice, 0, true); mActiveSounds[sound] = std::make_pair(ptr, std::string("_say_sound")); } catch(std::exception &e) @@ -367,6 +385,11 @@ namespace MWSound const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos); + if ((mode & Play_RemoveAtDistance) && mListenerPos.squaredDistance(objpos) > 2000*2000) + { + return MWBase::SoundPtr(); + } + sound = mOutput->playSound3D(file, objpos, volume, basevol, pitch, min, max, mode|type, offset); if((mode&Play_NoTrack)) mActiveSounds[sound] = std::make_pair(MWWorld::Ptr(), soundId); @@ -632,6 +655,13 @@ namespace MWSound const ESM::Position &pos = ptr.getRefData().getPosition(); const Ogre::Vector3 objpos(pos.pos); snditer->first->setPosition(objpos); + + if ((snditer->first->mFlags & Play_RemoveAtDistance) + && mListenerPos.squaredDistance(Ogre::Vector3(ptr.getRefData().getPosition().pos)) > 2000*2000) + { + mActiveSounds.erase(snditer++); + continue; + } } //update fade out if(snditer->first->mFadeOutTime>0) @@ -693,9 +723,9 @@ namespace MWSound MWWorld::Ptr player = MWBase::Environment::get().getWorld()->getPlayerPtr(); - const ESM::Cell *cell = player.getCell()->getCell(); + const MWWorld::CellStore *cell = player.getCell(); - mListenerUnderwater = ((cell->mData.mFlags&cell->HasWater) && mListenerPos.z < cell->mWater); + mListenerUnderwater = ((cell->getCell()->mData.mFlags&ESM::Cell::HasWater) && mListenerPos.z < cell->getWaterLevel()); } void SoundManager::updatePtr(const MWWorld::Ptr &old, const MWWorld::Ptr &updated) diff --git a/apps/openmw/mwstate/character.cpp b/apps/openmw/mwstate/character.cpp index f8fcfceec6..f190565daf 100644 --- a/apps/openmw/mwstate/character.cpp +++ b/apps/openmw/mwstate/character.cpp @@ -61,7 +61,8 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) stream << "_"; } - slot.mPath = mPath / stream.str(); + const std::string ext = ".omwsave"; + slot.mPath = mPath / (stream.str() + ext); // Append an index if necessary to ensure a unique file int i=0; @@ -70,7 +71,7 @@ void MWState::Character::addSlot (const ESM::SavedGame& profile) std::ostringstream test; test << stream.str(); test << " - " << ++i; - slot.mPath = mPath / test.str(); + slot.mPath = mPath / (test.str() + ext); } slot.mProfile = profile; @@ -190,3 +191,8 @@ ESM::SavedGame MWState::Character::getSignature() const return slot.mProfile; } + +const boost::filesystem::path& MWState::Character::getPath() const +{ + return mPath; +} diff --git a/apps/openmw/mwstate/character.hpp b/apps/openmw/mwstate/character.hpp index 4703f0cca3..32c79a183e 100644 --- a/apps/openmw/mwstate/character.hpp +++ b/apps/openmw/mwstate/character.hpp @@ -54,12 +54,12 @@ namespace MWState /// \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. + ///< Any call to createSlot and updateSlot can invalidate the returned iterator. SlotIterator end() const; + const boost::filesystem::path& getPath() const; + ESM::SavedGame getSignature() const; ///< Return signature information for this character. /// diff --git a/apps/openmw/mwstate/statemanagerimp.cpp b/apps/openmw/mwstate/statemanagerimp.cpp index 18ebe11cee..0883bc63b3 100644 --- a/apps/openmw/mwstate/statemanagerimp.cpp +++ b/apps/openmw/mwstate/statemanagerimp.cpp @@ -13,6 +13,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -27,6 +28,7 @@ #include "../mwworld/player.hpp" #include "../mwworld/class.hpp" #include "../mwworld/cellstore.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwmechanics/npcstats.hpp" @@ -117,7 +119,7 @@ void MWState::StateManager::askLoadRecent() 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); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(message, buttons); mAskLoadRecent = true; } } @@ -220,7 +222,8 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.save (stream); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - listener.setProgressRange(recordCount); + // Using only Cells for progress information, since they typically have the largest records by far + listener.setProgressRange(MWBase::Environment::get().getWorld()->countSavedGameCells()); listener.setLabel("#{sNotifyMessage4}"); Loading::ScopedLoad load(&listener); @@ -228,7 +231,6 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot writer.startRecord (ESM::REC_SAVE); slot->mProfile.save (writer); writer.endRecord (ESM::REC_SAVE); - listener.increaseProgress(); MWBase::Environment::get().getJournal()->write (writer, listener); MWBase::Environment::get().getDialogueManager()->write (writer, listener); @@ -258,7 +260,7 @@ void MWState::StateManager::saveGame (const std::string& description, const Slot std::vector buttons; buttons.push_back("#{sOk}"); - MWBase::Environment::get().getWindowManager()->messageBox(error.str(), buttons); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); // If no file was written, clean up the slot if (slot && !boost::filesystem::exists(slot->mPath)) @@ -290,26 +292,61 @@ void MWState::StateManager::quickSave (std::string name) saveGame(name, slot); } -void MWState::StateManager::loadGame (const Character *character, const Slot *slot) +void MWState::StateManager::loadGame(const std::string& filepath) +{ + for (CharacterIterator it = mCharacterManager.begin(); it != mCharacterManager.end(); ++it) + { + const MWState::Character& character = *it; + for (MWState::Character::SlotIterator slotIt = character.begin(); slotIt != character.end(); ++slotIt) + { + const MWState::Slot& slot = *slotIt; + if (slot.mPath == boost::filesystem::path(filepath)) + { + loadGame(&character, slot.mPath.string()); + return; + } + } + } + + // have to peek into the save file to get the player name + ESM::ESMReader reader; + reader.open (filepath); + 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(); + ESM::SavedGame profile; + profile.load (reader); + reader.close(); + + MWState::Character* character = mCharacterManager.getCurrentCharacter(true, profile.mPlayerName); + loadGame(character, filepath); + mTimePlayed = profile.mTimePlayed; +} + +void MWState::StateManager::loadGame (const Character *character, const std::string& filepath) { try { cleanup(); - mTimePlayed = slot->mProfile.mTimePlayed; - ESM::ESMReader reader; - reader.open (slot->mPath.string()); + reader.open (filepath); std::map contentFileMap = buildContentFileIndexMap (reader); Loading::Listener& listener = *MWBase::Environment::get().getWindowManager()->getLoadingScreen(); - listener.setProgressRange(reader.getRecordCount()); + listener.setProgressRange(100); listener.setLabel("#{sLoadingMessage14}"); Loading::ScopedLoad load(&listener); + bool firstPersonCam = false; + + size_t total = reader.getFileSize(); + int currentPercent = 0; while (reader.hasMoreRecs()) { ESM::NAME n = reader.getRecName(); @@ -318,12 +355,21 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl switch (n.val) { case ESM::REC_SAVE: - - // don't need to read that here - reader.skipRecord(); + { + ESM::SavedGame profile; + profile.load(reader); + if (!verifyProfile(profile)) + { + cleanup (true); + MWBase::Environment::get().getWindowManager()->pushGuiMode (MWGui::GM_MainMenu); + return; + } + mTimePlayed = profile.mTimePlayed; + } break; case ESM::REC_JOUR: + case ESM::REC_JOUR_LEGACY: case ESM::REC_QUES: MWBase::Environment::get().getJournal()->readRecord (reader, n.val); @@ -352,8 +398,13 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl case ESM::REC_PROJ: case ESM::REC_MPRJ: case ESM::REC_ENAB: + case ESM::REC_LEVC: + case ESM::REC_LEVI: + MWBase::Environment::get().getWorld()->readRecord(reader, n.val, contentFileMap); + break; - MWBase::Environment::get().getWorld()->readRecord (reader, n.val, contentFileMap); + case ESM::REC_CAM_: + reader.getHNT(firstPersonCam, "FIRS"); break; case ESM::REC_GSCR: @@ -370,6 +421,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl break; case ESM::REC_DCOU: + case ESM::REC_STLN: MWBase::Environment::get().getMechanicsManager()->readRecord(reader, n.val); break; @@ -377,10 +429,15 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl default: // ignore invalid records - std::cerr << "Ignoring unknown record: " << n.name << std::endl; + std::cerr << "Ignoring unknown record: " << n.toString() << std::endl; reader.skipRecord(); } - listener.increaseProgress(); + int progressPercent = static_cast(float(reader.getFileOffset())/total*100); + if (progressPercent > currentPercent) + { + listener.increaseProgress(progressPercent-currentPercent); + currentPercent = progressPercent; + } } mCharacterManager.setCurrentCharacter(character); @@ -388,7 +445,7 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl mState = State_Running; Settings::Manager::setString ("character", "Saves", - slot->mPath.parent_path().filename().string()); + character->getPath().filename().string()); MWBase::Environment::get().getWindowManager()->setNewGame(false); MWBase::Environment::get().getWorld()->setupPlayer(); @@ -396,6 +453,9 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl MWBase::Environment::get().getWindowManager()->updatePlayer(); MWBase::Environment::get().getMechanicsManager()->playerLoaded(); + if (firstPersonCam != MWBase::Environment::get().getWorld()->isFirstPerson()) + MWBase::Environment::get().getWorld()->togglePOV(); + MWWorld::Ptr ptr = MWBase::Environment::get().getWorld()->getPlayerPtr(); ESM::CellId cellId = ptr.getCell()->getCell()->getCellId(); @@ -403,6 +463,8 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl // Use detectWorldSpaceChange=false, otherwise some of the data we just loaded would be cleared again MWBase::Environment::get().getWorld()->changeToCell (cellId, ptr.getRefData().getPosition(), false); + // Vanilla MW will restart startup scripts when a save game is loaded. This is unintuitive, + // but some mods may be using it as a reload detector. MWBase::Environment::get().getScriptManager()->getGlobalScripts().addStartup(); // Do not trigger erroneous cellChanged events @@ -420,15 +482,18 @@ void MWState::StateManager::loadGame (const Character *character, const Slot *sl std::vector buttons; buttons.push_back("#{sOk}"); - MWBase::Environment::get().getWindowManager()->messageBox(error.str(), buttons); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox(error.str(), buttons); } } void MWState::StateManager::quickLoad() { if (Character* mCurrentCharacter = getCurrentCharacter (false)) - if (const MWState::Slot* slot = &*mCurrentCharacter->begin()) //Get newest save - loadGame (mCurrentCharacter, slot); + { + if (mCurrentCharacter->begin() == mCurrentCharacter->end()) + return; + loadGame (mCurrentCharacter, mCurrentCharacter->begin()->mPath.string()); //Get newest save + } } void MWState::StateManager::deleteGame(const MWState::Character *character, const MWState::Slot *slot) @@ -469,7 +534,7 @@ void MWState::StateManager::update (float duration) //Load last saved game for current character MWState::Slot lastSave = *curCharacter->begin(); - loadGame(curCharacter, &lastSave); + loadGame(curCharacter, lastSave.mPath.string()); } else if(iButton==1) { @@ -478,3 +543,30 @@ void MWState::StateManager::update (float duration) } } } + +bool MWState::StateManager::verifyProfile(const ESM::SavedGame& profile) const +{ + const std::vector& selectedContentFiles = MWBase::Environment::get().getWorld()->getContentFiles(); + bool notFound = false; + for (std::vector::const_iterator it = profile.mContentFiles.begin(); + it != profile.mContentFiles.end(); ++it) + { + if (std::find(selectedContentFiles.begin(), selectedContentFiles.end(), *it) + == selectedContentFiles.end()) + { + std::cerr << "Savegame dependency " << *it << " is missing." << std::endl; + notFound = true; + } + } + if (notFound) + { + std::vector buttons; + buttons.push_back("#{sYes}"); + buttons.push_back("#{sNo}"); + MWBase::Environment::get().getWindowManager()->interactiveMessageBox("#{sMissingMastersMsg}", buttons, true); + int selectedButton = MWBase::Environment::get().getWindowManager()->readPressedButton(); + if (selectedButton == 1 || selectedButton == -1) + return false; + } + return true; +} diff --git a/apps/openmw/mwstate/statemanagerimp.hpp b/apps/openmw/mwstate/statemanagerimp.hpp index 40c36deb5e..37f38f8df7 100644 --- a/apps/openmw/mwstate/statemanagerimp.hpp +++ b/apps/openmw/mwstate/statemanagerimp.hpp @@ -23,6 +23,8 @@ namespace MWState void cleanup (bool force = false); + bool verifyProfile (const ESM::SavedGame& profile) const; + std::map buildContentFileIndexMap (const ESM::ESMReader& reader) const; public: @@ -61,10 +63,13 @@ namespace MWState /** Used for quickload **/ virtual void quickLoad(); - 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 void loadGame (const std::string& filepath); + ///< Load a saved game directly from the given file path. This will search the CharacterManager + /// for a Character containing this save file, and set this Character current if one was found. + /// Otherwise, a new Character will be created. + + virtual void loadGame (const Character *character, const std::string &filepath); + ///< Load a saved game file belonging to the given character. virtual Character *getCurrentCharacter (bool create = true); ///< \param create Create a new character, if there is no current character. diff --git a/apps/openmw/mwworld/actionequip.cpp b/apps/openmw/mwworld/actionequip.cpp index 50da1e5e5d..87a4c63084 100644 --- a/apps/openmw/mwworld/actionequip.cpp +++ b/apps/openmw/mwworld/actionequip.cpp @@ -31,11 +31,7 @@ namespace MWWorld { case 0: return; - case 2: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedLeft, actor); - break; - case 3: - invStore.unequipSlot(MWWorld::InventoryStore::Slot_CarriedRight, actor); + default: break; } diff --git a/apps/openmw/mwworld/actiontake.cpp b/apps/openmw/mwworld/actiontake.cpp index 548a949819..269d941dc6 100644 --- a/apps/openmw/mwworld/actiontake.cpp +++ b/apps/openmw/mwworld/actiontake.cpp @@ -16,7 +16,7 @@ namespace MWWorld void ActionTake::executeImp (const Ptr& actor) { MWBase::Environment::get().getMechanicsManager()->itemTaken( - actor, getTarget(), getTarget().getRefData().getCount()); + actor, getTarget(), MWWorld::Ptr(), 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 7fd6ba0246..8bbb080086 100644 --- a/apps/openmw/mwworld/actionteleport.cpp +++ b/apps/openmw/mwworld/actionteleport.cpp @@ -37,7 +37,11 @@ namespace MWWorld getFollowers(actor, followers); for(std::set::iterator it = followers.begin();it != followers.end();++it) { - teleport(*it); + MWWorld::Ptr follower = *it; + if (Ogre::Vector3(follower.getRefData().getPosition().pos).squaredDistance( + Ogre::Vector3( actor.getRefData().getPosition().pos)) + <= 800*800) + teleport(*it); } teleport(actor); diff --git a/apps/openmw/mwworld/cellref.cpp b/apps/openmw/mwworld/cellref.cpp index c0b3bb6afc..0d81e06368 100644 --- a/apps/openmw/mwworld/cellref.cpp +++ b/apps/openmw/mwworld/cellref.cpp @@ -5,15 +5,19 @@ namespace MWWorld { - ESM::RefNum CellRef::getRefNum() const + const ESM::RefNum& CellRef::getRefNum() const { return mCellRef.mRefNum; } + bool CellRef::hasContentFile() const + { + return mCellRef.mRefNum.hasContentFile(); + } + void CellRef::unsetRefNum() { - mCellRef.mRefNum.mContentFile = -1; - mCellRef.mRefNum.mIndex = 0; + mCellRef.mRefNum.unset(); } std::string CellRef::getRefId() const @@ -77,15 +81,29 @@ namespace MWWorld int CellRef::getCharge() const { - return mCellRef.mCharge; + return mCellRef.mChargeInt; } void CellRef::setCharge(int charge) { - if (charge != mCellRef.mCharge) + if (charge != mCellRef.mChargeInt) { mChanged = true; - mCellRef.mCharge = charge; + mCellRef.mChargeInt = charge; + } + } + + float CellRef::getChargeFloat() const + { + return mCellRef.mChargeFloat; + } + + void CellRef::setChargeFloat(float charge) + { + if (charge != mCellRef.mChargeFloat) + { + mChanged = true; + mCellRef.mChargeFloat = charge; } } @@ -99,6 +117,24 @@ namespace MWWorld return mCellRef.mGlobalVariable; } + void CellRef::resetGlobalVariable() + { + if (!mCellRef.mGlobalVariable.empty()) + { + mChanged = true; + mCellRef.mGlobalVariable.erase(); + } + } + + void CellRef::setFactionRank(int factionRank) + { + if (factionRank != mCellRef.mFactionRank) + { + mChanged = true; + mCellRef.mFactionRank = factionRank; + } + } + int CellRef::getFactionRank() const { return mCellRef.mFactionRank; diff --git a/apps/openmw/mwworld/cellref.hpp b/apps/openmw/mwworld/cellref.hpp index d2e4fdf1ce..b8e85f286e 100644 --- a/apps/openmw/mwworld/cellref.hpp +++ b/apps/openmw/mwworld/cellref.hpp @@ -23,11 +23,14 @@ namespace MWWorld } // Note: Currently unused for items in containers - ESM::RefNum getRefNum() const; + const ESM::RefNum& getRefNum() const; // Set RefNum to its default state. void unsetRefNum(); + /// Does the RefNum have a content file? + bool hasContentFile() const; + // Id of object being referenced std::string getRefId() const; @@ -57,8 +60,11 @@ namespace MWWorld // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. + // If this returns int(-1) it means full health. int getCharge() const; + float getChargeFloat() const; // Implemented as union with int charge void setCharge(int charge); + void setChargeFloat(float charge); // The NPC that owns this object (and will get angry if you steal it) std::string getOwner() const; @@ -69,6 +75,8 @@ namespace MWWorld // Used by bed rent scripts to allow the player to use the bed for the duration of the rent. std::string getGlobalVariable() const; + void resetGlobalVariable(); + // ID of creature trapped in this soul gem std::string getSoul() const; void setSoul(const std::string& soul); @@ -79,6 +87,7 @@ namespace MWWorld void setFaction (const std::string& faction); // PC faction rank required to use the item. Sometimes is -1, which means "any rank". + void setFactionRank(int factionRank); int getFactionRank() const; // Lock level for doors and containers diff --git a/apps/openmw/mwworld/cellreflist.hpp b/apps/openmw/mwworld/cellreflist.hpp index cf12896548..2c5e01aaa3 100644 --- a/apps/openmw/mwworld/cellreflist.hpp +++ b/apps/openmw/mwworld/cellreflist.hpp @@ -27,7 +27,9 @@ namespace MWWorld LiveRef *find (const std::string& name) { for (typename List::iterator iter (mList.begin()); iter!=mList.end(); ++iter) - if (iter->mRef.getRefId() == name) + if (!iter->mData.isDeletedByContentFile() + && (iter->mRef.hasContentFile() || iter->mData.getCount() > 0) + && iter->mRef.getRefId() == name) return &*iter; return 0; diff --git a/apps/openmw/mwworld/cells.cpp b/apps/openmw/mwworld/cells.cpp index ef3d299a92..2aa817fa50 100644 --- a/apps/openmw/mwworld/cells.cpp +++ b/apps/openmw/mwworld/cells.cpp @@ -241,26 +241,30 @@ MWWorld::Ptr MWWorld::Cells::getPtr (const std::string& name) void MWWorld::Cells::getExteriorPtrs(const std::string &name, std::vector &out) { - for (std::map, CellStore>::iterator iter = mExteriors.begin(); - iter!=mExteriors.end(); ++iter) + const MWWorld::Store &cells = mStore.get(); + for (MWWorld::Store::iterator iter = cells.extBegin(); iter != cells.extEnd(); ++iter) { - Ptr ptr = getPtrAndCache (name, iter->second); + CellStore *cellStore = getCellStore (&(*iter)); + + Ptr ptr = getPtrAndCache (name, *cellStore); + if (!ptr.isEmpty()) out.push_back(ptr); } - } void MWWorld::Cells::getInteriorPtrs(const std::string &name, std::vector &out) { - for (std::map::iterator iter = mInteriors.begin(); - iter!=mInteriors.end(); ++iter) + const MWWorld::Store &cells = mStore.get(); + for (MWWorld::Store::iterator iter = cells.intBegin(); iter != cells.intEnd(); ++iter) { - Ptr ptr = getPtrAndCache (name, iter->second); + CellStore *cellStore = getCellStore (&(*iter)); + + Ptr ptr = getPtrAndCache (name, *cellStore); + if (!ptr.isEmpty()) out.push_back(ptr); } - } int MWWorld::Cells::countSavedGameRecords() const @@ -287,7 +291,7 @@ void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) if (iter->second.hasState()) { writeCell (writer, iter->second); - progress.increaseProgress(); // Assumes that each cell writes one record + progress.increaseProgress(); } for (std::map::iterator iter (mInteriors.begin()); @@ -295,11 +299,11 @@ void MWWorld::Cells::write (ESM::ESMWriter& writer, Loading::Listener& progress) if (iter->second.hasState()) { writeCell (writer, iter->second); - progress.increaseProgress(); // Assumes that each cell writes one record + progress.increaseProgress(); } } -bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, int32_t type, +bool MWWorld::Cells::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { if (type==ESM::REC_CSTA) diff --git a/apps/openmw/mwworld/cells.hpp b/apps/openmw/mwworld/cells.hpp index a9c17fa930..0b7d9444fd 100644 --- a/apps/openmw/mwworld/cells.hpp +++ b/apps/openmw/mwworld/cells.hpp @@ -76,7 +76,7 @@ namespace MWWorld void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, int32_t type, + bool readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); }; } diff --git a/apps/openmw/mwworld/cellstore.cpp b/apps/openmw/mwworld/cellstore.cpp index 52e70fef26..545bbd4b31 100644 --- a/apps/openmw/mwworld/cellstore.cpp +++ b/apps/openmw/mwworld/cellstore.cpp @@ -7,7 +7,6 @@ #include #include #include -#include #include #include #include @@ -72,12 +71,12 @@ namespace iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) { - if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.getRefNum().mContentFile != -1) + if (!iter->mData.hasChanged() && !iter->mRef.hasChanged() && iter->mRef.hasContentFile()) { // Reference that came from a content file and has not been changed -> ignore continue; } - if (iter->mData.getCount()==0 && iter->mRef.getRefNum().mContentFile==-1) + if (iter->mData.getCount()==0 && !iter->mRef.hasContentFile()) { // Deleted reference that did not come from a content file -> ignore continue; @@ -86,7 +85,9 @@ namespace RecordType state; iter->save (state); + // recordId currently unused writer.writeHNT ("OBJE", collection.mList.front().mBase->sRecordId); + state.save (writer); } } @@ -94,15 +95,16 @@ namespace template void readReferenceCollection (ESM::ESMReader& reader, - MWWorld::CellRefList& collection, const std::map& contentFileMap) + MWWorld::CellRefList& collection, const ESM::CellRef& cref, const std::map& contentFileMap) { const MWWorld::ESMStore& esmStore = MWBase::Environment::get().getWorld()->getStore(); RecordType state; - state.load (reader); + state.mRef = cref; + state.load(reader); // If the reference came from a content file, make sure this content file is loaded - if (state.mRef.mRefNum.mContentFile != -1) + if (state.mRef.mRefNum.hasContentFile()) { std::map::const_iterator iter = contentFileMap.find (state.mRef.mRefNum.mContentFile); @@ -121,7 +123,7 @@ namespace if (!record) return; - if (state.mRef.mRefNum.mContentFile != -1) + if (state.mRef.mRefNum.hasContentFile()) { for (typename MWWorld::CellRefList::List::iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) @@ -413,7 +415,7 @@ namespace MWWorld // TODO: the pathgrid graph only needs to be loaded for active cells, so move this somewhere else. // In a simple test, loading the graph for all cells in MW + expansions took 200 ms - mPathgridGraph.load(mCell); + mPathgridGraph.load(this); } } @@ -464,7 +466,7 @@ namespace MWWorld // List moved references, from separately tracked list. for (ESM::CellRefTracker::const_iterator it = mCell->mLeasedRefs.begin(); it != mCell->mLeasedRefs.end(); ++it) { - ESM::CellRef &ref = const_cast(*it); + const ESM::CellRef &ref = *it; mIds.push_back(Misc::StringUtils::lowerCase(ref.mRefID)); } @@ -487,7 +489,7 @@ namespace MWWorld mCell->restore (esm[index], i); ESM::CellRef ref; - ref.mRefNum.mContentFile = -1; + ref.mRefNum.mContentFile = ESM::RefNum::RefNum_NoContentFile; // Get each reference in turn bool deleted = false; @@ -624,7 +626,7 @@ namespace MWWorld writeReferenceCollection (writer, mIngreds); writeReferenceCollection (writer, mCreatureLists); writeReferenceCollection (writer, mItemLists); - writeReferenceCollection (writer, mLights); + writeReferenceCollection (writer, mLights); writeReferenceCollection (writer, mLockpicks); writeReferenceCollection (writer, mMiscItems); writeReferenceCollection (writer, mNpcs); @@ -641,109 +643,121 @@ namespace MWWorld while (reader.isNextSub ("OBJE")) { - unsigned int id = 0; - reader.getHT (id); + unsigned int unused; + reader.getHT (unused); - switch (id) + // load the RefID first so we know what type of object it is + ESM::CellRef cref; + cref.loadId(reader, true); + + int type = MWBase::Environment::get().getWorld()->getStore().find(cref.mRefID); + if (type == 0) + { + std::cerr << "Dropping reference to '" << cref.mRefID << "' (object no longer exists)" << std::endl; + reader.skipHSubUntil("OBJE"); + continue; + } + + switch (type) { case ESM::REC_ACTI: - readReferenceCollection (reader, mActivators, contentFileMap); + readReferenceCollection (reader, mActivators, cref, contentFileMap); break; case ESM::REC_ALCH: - readReferenceCollection (reader, mPotions, contentFileMap); + readReferenceCollection (reader, mPotions, cref, contentFileMap); break; case ESM::REC_APPA: - readReferenceCollection (reader, mAppas, contentFileMap); + readReferenceCollection (reader, mAppas, cref, contentFileMap); break; case ESM::REC_ARMO: - readReferenceCollection (reader, mArmors, contentFileMap); + readReferenceCollection (reader, mArmors, cref, contentFileMap); break; case ESM::REC_BOOK: - readReferenceCollection (reader, mBooks, contentFileMap); + readReferenceCollection (reader, mBooks, cref, contentFileMap); break; case ESM::REC_CLOT: - readReferenceCollection (reader, mClothes, contentFileMap); + readReferenceCollection (reader, mClothes, cref, contentFileMap); break; case ESM::REC_CONT: - readReferenceCollection (reader, mContainers, contentFileMap); + readReferenceCollection (reader, mContainers, cref, contentFileMap); break; case ESM::REC_CREA: - readReferenceCollection (reader, mCreatures, contentFileMap); + readReferenceCollection (reader, mCreatures, cref, contentFileMap); break; case ESM::REC_DOOR: - readReferenceCollection (reader, mDoors, contentFileMap); + readReferenceCollection (reader, mDoors, cref, contentFileMap); break; case ESM::REC_INGR: - readReferenceCollection (reader, mIngreds, contentFileMap); + readReferenceCollection (reader, mIngreds, cref, contentFileMap); break; case ESM::REC_LEVC: - readReferenceCollection (reader, mCreatureLists, contentFileMap); + readReferenceCollection (reader, mCreatureLists, cref, contentFileMap); break; case ESM::REC_LEVI: - readReferenceCollection (reader, mItemLists, contentFileMap); + readReferenceCollection (reader, mItemLists, cref, contentFileMap); break; case ESM::REC_LIGH: - readReferenceCollection (reader, mLights, contentFileMap); + readReferenceCollection (reader, mLights, cref, contentFileMap); break; case ESM::REC_LOCK: - readReferenceCollection (reader, mLockpicks, contentFileMap); + readReferenceCollection (reader, mLockpicks, cref, contentFileMap); break; case ESM::REC_MISC: - readReferenceCollection (reader, mMiscItems, contentFileMap); + readReferenceCollection (reader, mMiscItems, cref, contentFileMap); break; case ESM::REC_NPC_: - readReferenceCollection (reader, mNpcs, contentFileMap); + readReferenceCollection (reader, mNpcs, cref, contentFileMap); break; case ESM::REC_PROB: - readReferenceCollection (reader, mProbes, contentFileMap); + readReferenceCollection (reader, mProbes, cref, contentFileMap); break; case ESM::REC_REPA: - readReferenceCollection (reader, mRepairs, contentFileMap); + readReferenceCollection (reader, mRepairs, cref, contentFileMap); break; case ESM::REC_STAT: - readReferenceCollection (reader, mStatics, contentFileMap); + readReferenceCollection (reader, mStatics, cref, contentFileMap); break; case ESM::REC_WEAP: - readReferenceCollection (reader, mWeapons, contentFileMap); + readReferenceCollection (reader, mWeapons, cref, contentFileMap); break; default: diff --git a/apps/openmw/mwworld/cellstore.hpp b/apps/openmw/mwworld/cellstore.hpp index eba627b3ee..f6f2a3b489 100644 --- a/apps/openmw/mwworld/cellstore.hpp +++ b/apps/openmw/mwworld/cellstore.hpp @@ -6,10 +6,10 @@ #include #include "livecellref.hpp" -#include "esmstore.hpp" #include "cellreflist.hpp" #include +#include #include "../mwmechanics/pathgrid.hpp" // TODO: maybe belongs in mwworld @@ -24,7 +24,7 @@ namespace ESM namespace MWWorld { class Ptr; - + class ESMStore; /// \brief Mutable state of a cell diff --git a/apps/openmw/mwworld/class.cpp b/apps/openmw/mwworld/class.cpp index 16c2469c14..afc5ed6356 100644 --- a/apps/openmw/mwworld/class.cpp +++ b/apps/openmw/mwworld/class.cpp @@ -10,6 +10,7 @@ #include "../mwbase/environment.hpp" #include "../mwbase/windowmanager.hpp" #include "../mwbase/world.hpp" +#include "../mwworld/esmstore.hpp" #include "ptr.hpp" #include "refdata.hpp" @@ -37,12 +38,12 @@ namespace MWWorld throw std::runtime_error ("class does not support ID retrieval"); } - void Class::insertObjectRendering (const Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const + void Class::insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const { } - void Class::insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics) const + void Class::insertObject(const Ptr& ptr, const std::string& mesh, MWWorld::PhysicsSystem& physics) const { } @@ -180,11 +181,6 @@ namespace MWWorld throw std::runtime_error ("class does not support enchanting"); } - float Class::getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const - { - return 0; - } - MWMechanics::Movement& Class::getMovementSettings (const Ptr& ptr) const { throw std::runtime_error ("movement settings not supported by class"); @@ -387,6 +383,16 @@ namespace MWWorld return false; } + bool Class::isPureWaterCreature(const MWWorld::Ptr& ptr) const + { + return canSwim(ptr) && !canWalk(ptr); + } + + bool Class::isMobile(const MWWorld::Ptr& ptr) const + { + return canSwim(ptr) || canWalk(ptr) || canFly(ptr); + } + int Class::getSkill(const MWWorld::Ptr& ptr, int skill) const { throw std::runtime_error("class does not support skills"); @@ -434,4 +440,18 @@ namespace MWWorld { return std::string(); } + + int Class::getBaseFightRating(const Ptr &ptr) const + { + throw std::runtime_error("class does not support fight rating"); + } + + std::string Class::getPrimaryFaction (const MWWorld::Ptr& ptr) const + { + return std::string(); + } + int Class::getPrimaryFactionRank (const MWWorld::Ptr& ptr) const + { + return -1; + } } diff --git a/apps/openmw/mwworld/class.hpp b/apps/openmw/mwworld/class.hpp index b66ca74880..380d2a34b5 100644 --- a/apps/openmw/mwworld/class.hpp +++ b/apps/openmw/mwworld/class.hpp @@ -43,7 +43,6 @@ namespace ESM namespace MWWorld { - class Ptr; class ContainerStore; class InventoryStore; class PhysicsSystem; @@ -81,9 +80,11 @@ namespace MWWorld virtual std::string getId (const Ptr& ptr) const; ///< Return ID of \a ptr or throw an exception, if class does not support ID retrieval /// (default implementation: throw an exception) + /// @note This function is currently redundant; the same ID can be retrieved by CellRef::getRefId. + /// Leaving it here for now in case we want to optimize later. - virtual void insertObjectRendering (const Ptr& ptr, MWRender::RenderingInterface& renderingInterface) const; - virtual void insertObject(const Ptr& ptr, MWWorld::PhysicsSystem& physics) const; + virtual void insertObjectRendering (const Ptr& ptr, const std::string& mesh, MWRender::RenderingInterface& renderingInterface) const; + virtual void insertObject(const Ptr& ptr, const std::string& mesh, MWWorld::PhysicsSystem& physics) const; ///< Add reference into a cell for rendering (default implementation: don't render anything). virtual std::string getName (const Ptr& ptr) const = 0; @@ -184,9 +185,6 @@ namespace MWWorld virtual float getJump(const MWWorld::Ptr &ptr) const; ///< Return jump velocity (not accounting for movement) - virtual float getFallDamage(const MWWorld::Ptr &ptr, float fallHeight) const; - ///< Return amount of health points lost when falling - virtual MWMechanics::Movement& getMovementSettings (const Ptr& ptr) const; ///< Return desired movement. @@ -311,6 +309,8 @@ namespace MWWorld virtual bool canFly(const MWWorld::Ptr& ptr) const; virtual bool canSwim(const MWWorld::Ptr& ptr) const; virtual bool canWalk(const MWWorld::Ptr& ptr) const; + bool isPureWaterCreature(const MWWorld::Ptr& ptr) const; + bool isMobile(const MWWorld::Ptr& ptr) const; virtual int getSkill(const MWWorld::Ptr& ptr, int skill) const; @@ -342,6 +342,11 @@ namespace MWWorld /// Returns sound id virtual std::string getSound(const MWWorld::Ptr& ptr) const; + + virtual int getBaseFightRating (const MWWorld::Ptr& ptr) const; + + virtual std::string getPrimaryFaction (const MWWorld::Ptr& ptr) const; + virtual int getPrimaryFactionRank (const MWWorld::Ptr& ptr) const; }; } diff --git a/apps/openmw/mwworld/containerstore.cpp b/apps/openmw/mwworld/containerstore.cpp index 45728371bf..c2906e4474 100644 --- a/apps/openmw/mwworld/containerstore.cpp +++ b/apps/openmw/mwworld/containerstore.cpp @@ -79,6 +79,14 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::getState (CellRefList void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::ObjectState& state) const { @@ -86,8 +94,8 @@ void MWWorld::ContainerStore::storeState (const LiveCellRef& ref, ESM::Object } template -void MWWorld::ContainerStore::storeStates (const CellRefList& collection, - std::vector > >& states, bool equipable) const +void MWWorld::ContainerStore::storeStates (CellRefList& collection, + ESM::InventoryState& inventory, int& index, bool equipable) const { for (typename CellRefList::List::const_iterator iter (collection.mList.begin()); iter!=collection.mList.end(); ++iter) @@ -96,18 +104,13 @@ void MWWorld::ContainerStore::storeStates (const CellRefList& collection, continue; 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))); + if (equipable) + storeEquipmentState(*iter, index, inventory); + inventory.mItems.push_back (state); + ++index; } } -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) {} @@ -219,29 +222,22 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr MWWorld::ContainerStoreIterator it = end(); - if (setOwner && actorPtr.getClass().isActor()) + // HACK: Set owner on the original item, then reset it after we have copied it + // If we set the owner on the copied item, it would not stack correctly... + std::string oldOwner = itemPtr.getCellRef().getOwner(); + if (!setOwner || actorPtr == MWBase::Environment::get().getWorld()->getPlayerPtr()) // No point in setting owner to the player - NPCs will not respect this anyway { - // HACK: Set owner on the original item, then reset it after we have copied it - // If we set the owner on the copied item, it would not stack correctly... - std::string oldOwner = itemPtr.getCellRef().getOwner(); - if (actorPtr == player) - { - // No point in setting owner to the player - NPCs will not respect this anyway - // Additionally, setting it to "player" would make those items not stack with items that don't have an owner - itemPtr.getCellRef().setOwner(""); - } - else - itemPtr.getCellRef().setOwner(actorPtr.getCellRef().getRefId()); - - it = addImp(itemPtr, count); - - itemPtr.getCellRef().setOwner(oldOwner); + itemPtr.getCellRef().setOwner(""); } else { - it = addImp(itemPtr, count); + itemPtr.getCellRef().setOwner(actorPtr.getCellRef().getRefId()); } + it = addImp(itemPtr, count); + + itemPtr.getCellRef().setOwner(oldOwner); + // The copy of the original item we just made MWWorld::Ptr item = *it; @@ -255,6 +251,14 @@ MWWorld::ContainerStoreIterator MWWorld::ContainerStore::add (const Ptr& itemPtr pos.pos[1] = 0; pos.pos[2] = 0; item.getCellRef().setPosition(pos); + + // reset ownership stuff, owner was already handled above + item.getCellRef().resetGlobalVariable(); + item.getCellRef().setFaction(""); + item.getCellRef().setFactionRank(-1); + + // must reset the RefNum on the copied item, so that the RefNum on the original item stays unique + // maybe we should do this in the copy constructor instead? item.getCellRef().unsetRefNum(); // destroy link to content file std::string script = item.getClass().getScript(item); @@ -396,19 +400,19 @@ 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 std::string& faction, const MWWorld::ESMStore& store) +void MWWorld::ContainerStore::fill (const ESM::InventoryList& items, const std::string& owner) { for (std::vector::const_iterator iter (items.mList.begin()); iter!=items.mList.end(); ++iter) { std::string id = Misc::StringUtils::lowerCase(iter->mItem.toString()); - addInitialItem(id, owner, faction, iter->mCount); + addInitialItem(id, owner, iter->mCount); } flagAsModified(); } -void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, const std::string& faction, +void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel, const std::string& levItem) { ManualRef ref (MWBase::Environment::get().getWorld()->getStore(), id, count); @@ -420,7 +424,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: if (topLevel && std::abs(count) > 1 && levItem->mFlags & ESM::ItemLevList::Each) { for (int i=0; i 0 ? 1 : -1, true, levItem->mId); + addInitialItem(id, owner, count > 0 ? 1 : -1, true, levItem->mId); return; } else @@ -428,7 +432,7 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: std::string id = MWMechanics::getLevelledItem(ref.getPtr().get()->mBase, false); if (id.empty()) return; - addInitialItem(id, owner, faction, count, false, levItem->mId); + addInitialItem(id, owner, count, false, levItem->mId); } } else @@ -444,12 +448,11 @@ void MWWorld::ContainerStore::addInitialItem (const std::string& id, const std:: count = std::abs(count); ref.getPtr().getCellRef().setOwner(owner); - ref.getPtr().getCellRef().setFaction(faction); addImp (ref.getPtr(), count); } } -void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner, const std::string& faction) +void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner) { // Remove the items already spawned by levelled items that will restock for (std::map::iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) @@ -468,13 +471,13 @@ void MWWorld::ContainerStore::restock (const ESM::InventoryList& items, const MW if (MWBase::Environment::get().getWorld()->getStore().get().search(it->mItem.toString())) { - addInitialItem(item, owner, faction, it->mCount, true); + addInitialItem(item, owner, it->mCount, true); } else { int currentCount = count(item); if (currentCount < std::abs(it->mCount)) - add (item, std::abs(it->mCount) - currentCount, ptr); + addInitialItem(item, owner, std::abs(it->mCount) - currentCount, true); } } flagAsModified(); @@ -640,72 +643,66 @@ MWWorld::Ptr MWWorld::ContainerStore::search (const std::string& id) return Ptr(); } -void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) const +void MWWorld::ContainerStore::writeState (ESM::InventoryState& state) { 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(); + int index = 0; + storeStates (potions, state, index); + storeStates (appas, state, index); + storeStates (armors, state, index, true); + storeStates (books, state, index, true); // not equipable as such, but for selectedEnchantItem + storeStates (clothes, state, index, true); + storeStates (ingreds, state, index); + storeStates (lockpicks, state, index, true); + storeStates (miscItems, state, index); + storeStates (probes, state, index, true); + storeStates (repairs, state, index); + storeStates (weapons, state, index, true); + storeStates (lights, state, index, true); state.mLevelledItemMap = mLevelledItemMap; - - for (MWWorld::CellRefList::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) +void MWWorld::ContainerStore::readState (const ESM::InventoryState& inventory) { clear(); - for (std::vector > >::const_iterator - iter (state.mItems.begin()); iter!=state.mItems.end(); ++iter) + int index = 0; + for (std::vector::const_iterator + iter (inventory.mItems.begin()); iter!=inventory.mItems.end(); ++iter) { - int slot = iter->second.second; + const ESM::ObjectState& state = *iter; - switch (iter->second.first) + int type = MWBase::Environment::get().getWorld()->getStore().find(state.mRef.mRefID); + + int thisIndex = index++; + + switch (type) { - 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; - + case ESM::REC_ALCH: getState (potions, state); break; + case ESM::REC_APPA: getState (appas, state); break; + case ESM::REC_ARMO: readEquipmentState (getState (armors, state), thisIndex, inventory); break; + case ESM::REC_BOOK: readEquipmentState (getState (books, state), thisIndex, inventory); break; // not equipable as such, but for selectedEnchantItem + case ESM::REC_CLOT: readEquipmentState (getState (clothes, state), thisIndex, inventory); break; + case ESM::REC_INGR: getState (ingreds, state); break; + case ESM::REC_LOCK: readEquipmentState (getState (lockpicks, state), thisIndex, inventory); break; + case ESM::REC_MISC: getState (miscItems, state); break; + case ESM::REC_PROB: readEquipmentState (getState (probes, state), thisIndex, inventory); break; + case ESM::REC_REPA: getState (repairs, state); break; + case ESM::REC_WEAP: readEquipmentState (getState (weapons, state), thisIndex, inventory); break; + case ESM::REC_LIGH: readEquipmentState (getState (lights, state), thisIndex, inventory); break; + case 0: + std::cerr << "Dropping reference to '" << state.mRef.mRefID << "' (object no longer exists)" << std::endl; + break; default: - - std::cerr << "invalid item type in inventory state" << std::endl; + std::cerr << "Invalid item type in inventory state, refid " << state.mRef.mRefID << std::endl; + break; } } - for (std::vector >::const_iterator iter (state.mLights.begin()); - iter!=state.mLights.end(); ++iter) - { - int slot = iter->second; - setSlot (getState (lights, iter->first), slot); - } - mLevelledItemMap = state.mLevelledItemMap; + mLevelledItemMap = inventory.mLevelledItemMap; } diff --git a/apps/openmw/mwworld/containerstore.hpp b/apps/openmw/mwworld/containerstore.hpp index 6d9d7a6bb1..e9750a6228 100644 --- a/apps/openmw/mwworld/containerstore.hpp +++ b/apps/openmw/mwworld/containerstore.hpp @@ -18,6 +18,7 @@ #include #include "ptr.hpp" +#include "cellreflist.hpp" namespace ESM { @@ -74,7 +75,7 @@ namespace MWWorld mutable float mCachedWeight; mutable bool mWeightUpToDate; 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, const std::string& levItem = ""); + void addInitialItem (const std::string& id, const std::string& owner, int count, bool topLevel=true, const std::string& levItem = ""); template ContainerStoreIterator getState (CellRefList& collection, @@ -84,16 +85,13 @@ namespace MWWorld void storeState (const LiveCellRef& ref, ESM::ObjectState& state) const; template - void storeStates (const CellRefList& collection, - std::vector > >& states, + void storeStates (CellRefList& collection, + ESM::InventoryState& inventory, int& index, 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. + virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; + virtual void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); public: ContainerStore(); @@ -114,7 +112,7 @@ 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? + /// @param setOwner Set the owner of the added item to \a actorPtr? If false, the owner is reset to "". /// /// @return if stacking happened, return iterator to the item that was stacked against, otherwise iterator to the newly inserted item. @@ -153,10 +151,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 std::string& faction, const MWWorld::ESMStore& store); + void fill (const ESM::InventoryList& items, const std::string& owner); ///< Insert items into *this. - void restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner, const std::string& faction); + void restock (const ESM::InventoryList& items, const MWWorld::Ptr& ptr, const std::string& owner); virtual void clear(); ///< Empty container. @@ -170,9 +168,10 @@ namespace MWWorld Ptr search (const std::string& id); - void writeState (ESM::InventoryState& state) const; + /// \todo make this method const once const-correct ContainerStoreIterators are available + virtual void writeState (ESM::InventoryState& state); - void readState (const ESM::InventoryState& state); + virtual void readState (const ESM::InventoryState& state); friend class ContainerStoreIterator; }; diff --git a/apps/openmw/mwworld/esmloader.cpp b/apps/openmw/mwworld/esmloader.cpp index 1b8880d375..13a786d008 100644 --- a/apps/openmw/mwworld/esmloader.cpp +++ b/apps/openmw/mwworld/esmloader.cpp @@ -1,7 +1,7 @@ #include "esmloader.hpp" #include "esmstore.hpp" -#include "components/to_utf8/to_utf8.hpp" +#include namespace MWWorld { diff --git a/apps/openmw/mwworld/esmloader.hpp b/apps/openmw/mwworld/esmloader.hpp index d799c3f152..b96af707ce 100644 --- a/apps/openmw/mwworld/esmloader.hpp +++ b/apps/openmw/mwworld/esmloader.hpp @@ -4,13 +4,17 @@ #include #include "contentloader.hpp" -#include "components/esm/esmreader.hpp" namespace ToUTF8 { class Utf8Encoder; } +namespace ESM +{ + class ESMReader; +} + namespace MWWorld { diff --git a/apps/openmw/mwworld/esmstore.cpp b/apps/openmw/mwworld/esmstore.cpp index 56cb05c646..36c198f010 100644 --- a/apps/openmw/mwworld/esmstore.cpp +++ b/apps/openmw/mwworld/esmstore.cpp @@ -7,6 +7,8 @@ #include +#include + namespace MWWorld { @@ -82,7 +84,7 @@ void ESMStore::load(ESM::ESMReader &esm, Loading::Listener* listener) } else if (n.val == ESM::REC_SKIL) { mSkills.load (esm); } - else if (n.val==ESM::REC_FILT || ESM::REC_DBGP) + else if (n.val==ESM::REC_FILT || n.val == ESM::REC_DBGP) { // ignore project file only records esm.skipRecord(); @@ -149,7 +151,9 @@ void ESMStore::setUp() +mEnchants.getDynamicSize() +mNpcs.getDynamicSize() +mSpells.getDynamicSize() - +mWeapons.getDynamicSize(); + +mWeapons.getDynamicSize() + +mCreatureLists.getDynamicSize() + +mItemLists.getDynamicSize(); } void ESMStore::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -159,7 +163,6 @@ void ESMStore::setUp() writer.writeT(mDynamicCount); writer.endRecord("COUN"); writer.endRecord(ESM::REC_DYNA); - progress.increaseProgress(); mPotions.write (writer, progress); mArmors.write (writer, progress); @@ -170,9 +173,11 @@ void ESMStore::setUp() mSpells.write (writer, progress); mWeapons.write (writer, progress); mNpcs.write (writer, progress); + mItemLists.write (writer, progress); + mCreatureLists.write (writer, progress); } - bool ESMStore::readRecord (ESM::ESMReader& reader, int32_t type) + bool ESMStore::readRecord (ESM::ESMReader& reader, uint32_t type) { switch (type) { @@ -185,8 +190,18 @@ void ESMStore::setUp() case ESM::REC_SPEL: case ESM::REC_WEAP: case ESM::REC_NPC_: + case ESM::REC_LEVI: + case ESM::REC_LEVC: - mStores[type]->read (reader); + { + std::string id = reader.getHNString ("NAME"); + mStores[type]->read (reader, id); + + // FIXME: there might be stale dynamic IDs in mIds from an earlier savegame + // that really should be cleared instead of just overwritten + + mIds[id] = type; + } if (type==ESM::REC_NPC_) { diff --git a/apps/openmw/mwworld/esmstore.hpp b/apps/openmw/mwworld/esmstore.hpp index 90786acd42..05b6339566 100644 --- a/apps/openmw/mwworld/esmstore.hpp +++ b/apps/openmw/mwworld/esmstore.hpp @@ -99,9 +99,6 @@ namespace MWWorld ESMStore() : mDynamicCount(0) { - // Cell store needs access to this for tracking moved references - mCells.mEsmStore = this; - mStores[ESM::REC_ACTI] = &mActivators; mStores[ESM::REC_ALCH] = &mPotions; mStores[ESM::REC_APPA] = &mAppas; @@ -141,6 +138,8 @@ namespace MWWorld mStores[ESM::REC_SSCR] = &mStartScripts; mStores[ESM::REC_STAT] = &mStatics; mStores[ESM::REC_WEAP] = &mWeapons; + + mPathgrids.setCells(mCells); } void clearDynamic () @@ -165,6 +164,7 @@ namespace MWWorld throw std::runtime_error("Storage for this type not exist"); } + /// Insert a custom record (i.e. with a generated ID that will not clash will pre-existing records) template const T *insert(const T &x) { std::ostringstream id; @@ -189,6 +189,20 @@ namespace MWWorld return ptr; } + /// Insert a record with set ID, and allow it to override a pre-existing static record. + template + const T *overrideRecord(const T &x) { + Store &store = const_cast &>(get()); + + T *ptr = store.insert(x); + for (iterator it = mStores.begin(); it != mStores.end(); ++it) { + if (it->second == &store) { + mIds[ptr->mId] = it->first; + } + } + return ptr; + } + template const T *insertStatic(const T &x) { std::ostringstream id; @@ -219,7 +233,7 @@ namespace MWWorld void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, int32_t type); + bool readRecord (ESM::ESMReader& reader, uint32_t type); ///< \return Known type? }; diff --git a/apps/openmw/mwworld/fallback.cpp b/apps/openmw/mwworld/fallback.cpp index c0b21b2ef2..3a8154ca7c 100644 --- a/apps/openmw/mwworld/fallback.cpp +++ b/apps/openmw/mwworld/fallback.cpp @@ -22,6 +22,15 @@ namespace MWWorld else return boost::lexical_cast(fallback); } + int Fallback::getFallbackInt(const std::string& fall) const + { + std::string fallback=getFallbackString(fall); + if(fallback.empty()) + return 0; + else + return boost::lexical_cast(fallback); + } + bool Fallback::getFallbackBool(const std::string& fall) const { std::string fallback=getFallbackString(fall); diff --git a/apps/openmw/mwworld/fallback.hpp b/apps/openmw/mwworld/fallback.hpp index 6c5802e071..f69a5e57bb 100644 --- a/apps/openmw/mwworld/fallback.hpp +++ b/apps/openmw/mwworld/fallback.hpp @@ -15,6 +15,7 @@ namespace MWWorld Fallback(const std::map& fallback); std::string getFallbackString(const std::string& fall) const; float getFallbackFloat(const std::string& fall) const; + int getFallbackInt(const std::string& fall) const; bool getFallbackBool(const std::string& fall) const; Ogre::ColourValue getFallbackColour(const std::string& fall) const; }; diff --git a/apps/openmw/mwworld/globals.cpp b/apps/openmw/mwworld/globals.cpp index 15ba274980..e7cb04590b 100644 --- a/apps/openmw/mwworld/globals.cpp +++ b/apps/openmw/mwworld/globals.cpp @@ -85,11 +85,10 @@ namespace MWWorld writer.writeHNString ("NAME", iter->first); iter->second.write (writer, ESM::Variant::Format_Global); writer.endRecord (ESM::REC_GLOB); - progress.increaseProgress(); } } - bool Globals::readRecord (ESM::ESMReader& reader, int32_t type) + bool Globals::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_GLOB) { diff --git a/apps/openmw/mwworld/globals.hpp b/apps/openmw/mwworld/globals.hpp index 3ff4a5d6e0..bb4ab13d92 100644 --- a/apps/openmw/mwworld/globals.hpp +++ b/apps/openmw/mwworld/globals.hpp @@ -53,7 +53,7 @@ namespace MWWorld void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, int32_t type); + bool readRecord (ESM::ESMReader& reader, uint32_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 9032b04e11..020f9561a5 100644 --- a/apps/openmw/mwworld/inventorystore.cpp +++ b/apps/openmw/mwworld/inventorystore.cpp @@ -5,6 +5,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -48,19 +49,47 @@ void MWWorld::InventoryStore::initSlots (TSlots& slots_) slots_.push_back (end()); } -int MWWorld::InventoryStore::getSlot (const MWWorld::LiveCellRefBase& ref) const +void MWWorld::InventoryStore::storeEquipmentState(const MWWorld::LiveCellRefBase &ref, int index, ESM::InventoryState &inventory) const { for (int i = 0; i (mSlots.size()); ++i) if (mSlots[i].getType()!=-1 && mSlots[i]->getBase()==&ref) - return i; + { + inventory.mEquipmentSlots[index] = i; + } - return -1; + if (mSelectedEnchantItem.getType()!=-1 && mSelectedEnchantItem->getBase() == &ref) + inventory.mSelectedEnchantItem = index; } -void MWWorld::InventoryStore::setSlot (const MWWorld::ContainerStoreIterator& iter, int slot) +void MWWorld::InventoryStore::readEquipmentState(const MWWorld::ContainerStoreIterator &iter, int index, const ESM::InventoryState &inventory) { - if (iter!=end() && slot>=0 && slot::const_iterator found = inventory.mEquipmentSlots.find(index); + if (found != inventory.mEquipmentSlots.end()) + { + if (found->second < 0 || found->second >= MWWorld::InventoryStore::Slots) + throw std::runtime_error("Invalid slot index in inventory state"); + + // make sure the item can actually be equipped in this slot + int slot = found->second; + std::pair, bool> allowedSlots = iter->getClass().getEquipmentSlots(*iter); + if (!allowedSlots.first.size()) + return; + if (std::find(allowedSlots.first.begin(), allowedSlots.first.end(), slot) == allowedSlots.first.end()) + slot = allowedSlots.first.front(); + + // unstack if required + if (!allowedSlots.second && iter->getRefData().getCount() > 1) + { + MWWorld::ContainerStoreIterator newIter = addNewStack(*iter, 1); + iter->getRefData().setCount(iter->getRefData().getCount()-1); + mSlots[slot] = newIter; + } + else + mSlots[slot] = iter; + } } MWWorld::InventoryStore::InventoryStore() @@ -104,12 +133,11 @@ MWWorld::ContainerStoreIterator MWWorld::InventoryStore::add(const Ptr& itemPtr, const MWWorld::ContainerStoreIterator& retVal = MWWorld::ContainerStore::add(itemPtr, count, actorPtr, setOwner); // Auto-equip items if an armor/clothing or weapon item is added, but not for the player nor werewolves - if ((actorPtr.getRefData().getHandle() != "player") - && !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf()) - && !actorPtr.getClass().getCreatureStats(actorPtr).isDead()) + if (actorPtr.getRefData().getHandle() != "player" + && !(actorPtr.getClass().isNpc() && actorPtr.getClass().getNpcStats(actorPtr).isWerewolf())) { std::string type = itemPtr.getTypeName(); - if ((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name()) || (type == typeid(ESM::Weapon).name())) + if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) autoEquip(actorPtr); } @@ -205,7 +233,9 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) // ...unless this is a companion, he should always equip items given to him. if (!Misc::StringUtils::ciEqual(test.getCellRef().getOwner(), actor.getCellRef().getRefId()) && (actor.getClass().getScript(actor).empty() || - !actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion"))) + !actor.getRefData().getLocals().getIntVar(actor.getClass().getScript(actor), "companion")) + && !actor.getClass().getCreatureStats(actor).isDead() // Corpses can be dressed up by the player as desired + ) { continue; } @@ -255,11 +285,7 @@ void MWWorld::InventoryStore::autoEquip (const MWWorld::Ptr& actor) { case 0: continue; - case 2: - slots_[MWWorld::InventoryStore::Slot_CarriedLeft] = end(); - break; - case 3: - // Prefer keeping twohanded weapon + default: break; } @@ -422,6 +448,7 @@ void MWWorld::InventoryStore::updateMagicEffects(const Ptr& actor) void MWWorld::InventoryStore::flagAsModified() { ContainerStore::flagAsModified(); + mRechargingItemsUpToDate = false; } bool MWWorld::InventoryStore::stacks(const Ptr& ptr1, const Ptr& ptr2) @@ -480,8 +507,7 @@ int MWWorld::InventoryStore::remove(const Ptr& item, int count, const Ptr& actor && !(actor.getClass().isNpc() && actor.getClass().getNpcStats(actor).isWerewolf())) { std::string type = item.getTypeName(); - if (((type == typeid(ESM::Armor).name()) || (type == typeid(ESM::Clothing).name())) - && !actor.getClass().getCreatureStats(actor).isDead()) + if (type == typeid(ESM::Armor).name() || type == typeid(ESM::Clothing).name()) autoEquip(actor); } @@ -585,7 +611,8 @@ void MWWorld::InventoryStore::visitEffectSources(MWMechanics::EffectSourceVisito const EffectParams& params = mPermanentMagicEffectMagnitudes[(**iter).getCellRef().getRefId()][i]; float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params.mRandom; magnitude *= params.mMultiplier; - visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), -1, magnitude); + if (magnitude > 0) + visitor.visit(MWMechanics::EffectKey(*effectIt), (**iter).getClass().getName(**iter), (**iter).getCellRef().getRefId(), -1, magnitude); ++i; } @@ -641,9 +668,101 @@ void MWWorld::InventoryStore::purgeEffect(short effectId) mMagicEffects.remove(MWMechanics::EffectKey(effectId)); } +void MWWorld::InventoryStore::purgeEffect(short effectId, const std::string &sourceId) +{ + TEffectMagnitudes::iterator effectMagnitudeIt = mPermanentMagicEffectMagnitudes.find(sourceId); + if (effectMagnitudeIt == mPermanentMagicEffectMagnitudes.end()) + return; + + for (TSlots::const_iterator iter (mSlots.begin()); iter!=mSlots.end(); ++iter) + { + if (*iter==end()) + continue; + + if ((*iter)->getClass().getId(**iter) != sourceId) + continue; + + std::string enchantmentId = (*iter)->getClass().getEnchantment (**iter); + + if (!enchantmentId.empty()) + { + const ESM::Enchantment& enchantment = + *MWBase::Environment::get().getWorld()->getStore().get().find (enchantmentId); + + if (enchantment.mData.mType != ESM::Enchantment::ConstantEffect) + continue; + + std::vector& params = effectMagnitudeIt->second; + + int i=0; + for (std::vector::const_iterator effectIt (enchantment.mEffects.mList.begin()); + effectIt!=enchantment.mEffects.mList.end(); ++effectIt, ++i) + { + if (effectIt->mEffectID != effectId) + continue; + + float magnitude = effectIt->mMagnMin + (effectIt->mMagnMax - effectIt->mMagnMin) * params[i].mRandom; + magnitude *= params[i].mMultiplier; + + if (magnitude) + mMagicEffects.add (*effectIt, -magnitude); + + params[i].mMultiplier = 0; + break; + } + } + } +} + void MWWorld::InventoryStore::clear() { mSlots.clear(); initSlots (mSlots); ContainerStore::clear(); } + +bool MWWorld::InventoryStore::isEquipped(const MWWorld::Ptr &item) +{ + for (int i=0; i < MWWorld::InventoryStore::Slots; ++i) + { + if (getSlot(i) != end() && *getSlot(i) == item) + return true; + } + return false; +} + +void MWWorld::InventoryStore::writeState(ESM::InventoryState &state) +{ + MWWorld::ContainerStore::writeState(state); + + for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) + { + std::vector > params; + for (std::vector::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) + { + params.push_back(std::make_pair(pIt->mRandom, pIt->mMultiplier)); + } + + state.mPermanentMagicEffectMagnitudes[it->first] = params; + } +} + +void MWWorld::InventoryStore::readState(const ESM::InventoryState &state) +{ + MWWorld::ContainerStore::readState(state); + + for (ESM::InventoryState::TEffectMagnitudes::const_iterator it = state.mPermanentMagicEffectMagnitudes.begin(); + it != state.mPermanentMagicEffectMagnitudes.end(); ++it) + { + std::vector params; + for (std::vector >::const_iterator pIt = it->second.begin(); pIt != it->second.end(); ++pIt) + { + EffectParams p; + p.mRandom = pIt->first; + p.mMultiplier = pIt->second; + params.push_back(p); + } + + mPermanentMagicEffectMagnitudes[it->first] = params; + } +} diff --git a/apps/openmw/mwworld/inventorystore.hpp b/apps/openmw/mwworld/inventorystore.hpp index 16d965cda3..9a154373a1 100644 --- a/apps/openmw/mwworld/inventorystore.hpp +++ b/apps/openmw/mwworld/inventorystore.hpp @@ -113,11 +113,8 @@ 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. + virtual void storeEquipmentState (const MWWorld::LiveCellRefBase& ref, int index, ESM::InventoryState& inventory) const; + virtual void readEquipmentState (const MWWorld::ContainerStoreIterator& iter, int index, const ESM::InventoryState& inventory); public: @@ -145,6 +142,9 @@ namespace MWWorld void equip (int slot, const ContainerStoreIterator& iterator, const Ptr& actor); ///< \warning \a iterator can not be an end()-iterator, use unequip function instead + bool isEquipped(const MWWorld::Ptr& item); + ///< Utility function, returns true if the given item is equipped in any slot + void setSelectedEnchantItem(const ContainerStoreIterator& iterator); ///< set the selected magic item (for using enchantments of type "Cast once" or "Cast when used") /// \note to unset the selected item, call this method with end() iterator @@ -200,8 +200,15 @@ namespace MWWorld void purgeEffect (short effectId); ///< Remove a magic effect + void purgeEffect (short effectId, const std::string& sourceId); + ///< Remove a magic effect + virtual void clear(); ///< Empty container. + + virtual void writeState (ESM::InventoryState& state); + + virtual void readState (const ESM::InventoryState& state); }; } diff --git a/apps/openmw/mwworld/livecellref.cpp b/apps/openmw/mwworld/livecellref.cpp index dd313632b8..bfc708185d 100644 --- a/apps/openmw/mwworld/livecellref.cpp +++ b/apps/openmw/mwworld/livecellref.cpp @@ -1,6 +1,7 @@ - #include "livecellref.hpp" +#include + #include #include "../mwbase/environment.hpp" diff --git a/apps/openmw/mwworld/localscripts.cpp b/apps/openmw/mwworld/localscripts.cpp index d74aab6943..5be66ccea3 100644 --- a/apps/openmw/mwworld/localscripts.cpp +++ b/apps/openmw/mwworld/localscripts.cpp @@ -1,5 +1,7 @@ #include "localscripts.hpp" +#include + #include "esmstore.hpp" #include "cellstore.hpp" diff --git a/apps/openmw/mwworld/manualref.cpp b/apps/openmw/mwworld/manualref.cpp new file mode 100644 index 0000000000..30b4fe353b --- /dev/null +++ b/apps/openmw/mwworld/manualref.cpp @@ -0,0 +1,67 @@ +#include "manualref.hpp" + +#include "esmstore.hpp" +#include "cellstore.hpp" + +namespace +{ + + template + void create(const MWWorld::Store& list, const std::string& name, boost::any& refValue, MWWorld::Ptr& ptrValue) + { + const T* base = list.find(name); + + ESM::CellRef cellRef; + cellRef.mRefNum.unset(); + cellRef.mRefID = name; + cellRef.mScale = 1; + cellRef.mFactionRank = 0; + cellRef.mChargeInt = -1; + cellRef.mGoldValue = 1; + cellRef.mEnchantmentCharge = -1; + cellRef.mTeleport = false; + cellRef.mLockLevel = 0; + cellRef.mReferenceBlocked = 0; + + MWWorld::LiveCellRef ref(cellRef, base); + + refValue = ref; + ptrValue = MWWorld::Ptr(&boost::any_cast&>(refValue), 0); + } +} + +MWWorld::ManualRef::ManualRef(const MWWorld::ESMStore& store, const std::string& name, const int count) +{ + std::string lowerName = Misc::StringUtils::lowerCase(name); + switch (store.find(lowerName)) + { + case ESM::REC_ACTI: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_ALCH: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_APPA: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_ARMO: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_BOOK: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_CLOT: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_CONT: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_CREA: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_DOOR: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_INGR: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_LEVC: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_LEVI: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_LIGH: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_LOCK: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_MISC: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_NPC_: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_PROB: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_REPA: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_STAT: create(store.get(), lowerName, mRef, mPtr); break; + case ESM::REC_WEAP: create(store.get(), lowerName, mRef, mPtr); break; + + case 0: + throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown ID)"); + + default: + throw std::logic_error("failed to create manual cell ref for " + lowerName + " (unknown type)"); + } + + mPtr.getRefData().setCount(count); +} \ No newline at end of file diff --git a/apps/openmw/mwworld/manualref.hpp b/apps/openmw/mwworld/manualref.hpp index 0becd75242..2fc5994710 100644 --- a/apps/openmw/mwworld/manualref.hpp +++ b/apps/openmw/mwworld/manualref.hpp @@ -3,9 +3,7 @@ #include -#include "esmstore.hpp" #include "ptr.hpp" -#include "cellstore.hpp" namespace MWWorld { @@ -18,67 +16,8 @@ namespace MWWorld ManualRef (const ManualRef&); ManualRef& operator= (const ManualRef&); - template - void create (const MWWorld::Store& list, const std::string& name) - { - const T* base = list.find(name); - - ESM::CellRef cellRef; - cellRef.mRefNum.mIndex = 0; - cellRef.mRefNum.mContentFile = -1; - cellRef.mRefID = name; - cellRef.mScale = 1; - cellRef.mFactionRank = 0; - cellRef.mCharge = -1; - cellRef.mGoldValue = 1; - cellRef.mEnchantmentCharge = -1; - cellRef.mTeleport = false; - cellRef.mLockLevel = 0; - cellRef.mReferenceBlocked = 0; - - LiveCellRef ref(cellRef, base); - - mRef = ref; - mPtr = Ptr (&boost::any_cast&> (mRef), 0); - } - public: - - ManualRef (const MWWorld::ESMStore& store, const std::string& name, const int count=1) - { - std::string lowerName = Misc::StringUtils::lowerCase (name); - switch (store.find (lowerName)) - { - case ESM::REC_ACTI: create (store.get(), lowerName); break; - case ESM::REC_ALCH: create (store.get(), lowerName); break; - case ESM::REC_APPA: create (store.get(), lowerName); break; - case ESM::REC_ARMO: create (store.get(), lowerName); break; - case ESM::REC_BOOK: create (store.get(), lowerName); break; - case ESM::REC_CLOT: create (store.get(), lowerName); break; - case ESM::REC_CONT: create (store.get(), lowerName); break; - case ESM::REC_CREA: create (store.get(), lowerName); break; - case ESM::REC_DOOR: create (store.get(), lowerName); break; - case ESM::REC_INGR: create (store.get(), lowerName); break; - case ESM::REC_LEVC: create (store.get(), lowerName); break; - case ESM::REC_LEVI: create (store.get(), lowerName); break; - case ESM::REC_LIGH: create (store.get(), lowerName); break; - case ESM::REC_LOCK: create (store.get(), lowerName); break; - case ESM::REC_MISC: create (store.get(), lowerName); break; - case ESM::REC_NPC_: create (store.get(), lowerName); break; - case ESM::REC_PROB: create (store.get(), lowerName); break; - case ESM::REC_REPA: create (store.get(), lowerName); break; - case ESM::REC_STAT: create (store.get(), lowerName); break; - case ESM::REC_WEAP: create (store.get(), lowerName); break; - - case 0: - throw std::logic_error ("failed to create manual cell ref for " + lowerName + " (unknown ID)"); - - default: - throw std::logic_error ("failed to create manual cell ref for " + lowerName + " (unknown type)"); - } - - mPtr.getRefData().setCount(count); - } + ManualRef(const MWWorld::ESMStore& store, const std::string& name, const int count = 1); const Ptr& getPtr() const { diff --git a/apps/openmw/mwworld/physicssystem.cpp b/apps/openmw/mwworld/physicssystem.cpp index eecc4a02db..d31ae520bd 100644 --- a/apps/openmw/mwworld/physicssystem.cpp +++ b/apps/openmw/mwworld/physicssystem.cpp @@ -17,6 +17,8 @@ #include #include +#include +#include #include @@ -51,33 +53,32 @@ void animateCollisionShapes (std::mapgetAnimation(ptr); - if (!animation) // Shouldn't happen either, since keyframe-controlled objects are not batched in StaticGeometry - throw std::runtime_error("can't find Animation for " + ptr.getCellRef().getRefId()); + if (!animation) + continue; OEngine::Physic::AnimatedShapeInstance& instance = it->second; - std::map& shapes = instance.mAnimatedShapes; - for (std::map::iterator shapeIt = shapes.begin(); + std::map& shapes = instance.mAnimatedShapes; + for (std::map::iterator shapeIt = shapes.begin(); shapeIt != shapes.end(); ++shapeIt) { - Ogre::Node* bone; - if (shapeIt->first.empty()) - // HACK: see NifSkeletonLoader::buildBones - bone = animation->getNode(" "); - else - bone = animation->getNode(shapeIt->first); + const std::string& mesh = animation->getObjectRootName(); + int boneHandle = NifOgre::NIFSkeletonLoader::lookupOgreBoneHandle(mesh, shapeIt->first); + Ogre::Node* bone = animation->getNode(boneHandle); if (bone == NULL) - throw std::runtime_error("can't find bone"); + continue; - btCompoundShape* compound = dynamic_cast(instance.mCompound); + btCompoundShape* compound = static_cast(instance.mCompound); btTransform trans; - trans.setOrigin(BtOgre::Convert::toBullet(bone->_getDerivedPosition())); + trans.setOrigin(BtOgre::Convert::toBullet(bone->_getDerivedPosition()) * compound->getLocalScaling()); trans.setRotation(BtOgre::Convert::toBullet(bone->_getDerivedOrientation())); - compound->getChildShape(shapeIt->second)->setLocalScaling(BtOgre::Convert::toBullet(bone->_getDerivedScale())); + compound->getChildShape(shapeIt->second)->setLocalScaling( + compound->getLocalScaling() * + BtOgre::Convert::toBullet(bone->_getDerivedScale())); compound->updateChildTransform(shapeIt->second, trans); } @@ -93,7 +94,9 @@ namespace MWWorld { static const float sMaxSlope = 49.0f; - static const float sStepSize = 32.0f; + static const float sStepSizeUp = 34.0f; + static const float sStepSizeDown = 62.0f; + // Arbitrary number. To prevent infinite loops. They shouldn't happen but it's good to be prepared. static const int sMaxIterations = 8; @@ -106,7 +109,7 @@ namespace MWWorld } static bool stepMove(btCollisionObject *colobj, Ogre::Vector3 &position, - const Ogre::Vector3 &velocity, float &remainingTime, + const Ogre::Vector3 &toMove, float &remainingTime, OEngine::Physic::PhysicEngine *engine) { /* @@ -122,7 +125,7 @@ namespace MWWorld * If not successful return 'false'. May fail for these reasons: * - can't move directly up from current position * - having moved up by between epsilon() and sStepSize, can't move forward - * - having moved forward by between epsilon() and velocity*remainingTime, + * - having moved forward by between epsilon() and toMove, * = moved down between 0 and just under sStepSize but slope was too steep, or * = moved the full sStepSize down (FIXME: this could be a bug) * @@ -131,7 +134,7 @@ namespace MWWorld * Starting position. Obstacle or stairs with height upto sStepSize in front. * * +--+ +--+ |XX - * | | -------> velocity | | +--+XX + * | | -------> toMove | | +--+XX * | | | | |XXXXX * | | +--+ | | +--+XXXXX * | | |XX| | | |XXXXXXXX @@ -155,7 +158,7 @@ namespace MWWorld */ OEngine::Physic::ActorTracer tracer, stepper; - stepper.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSize), engine); + stepper.doTrace(colobj, position, position+Ogre::Vector3(0.0f,0.0f,sStepSizeUp), engine); if(stepper.mFraction < std::numeric_limits::epsilon()) return false; // didn't even move the smallest representable amount // (TODO: shouldn't this be larger? Why bother with such a small amount?) @@ -169,16 +172,16 @@ namespace MWWorld * | | * <------------------->| | * +--+ +--+ - * |XX| the moved amount is velocity*remainingTime*tracer.mFraction + * |XX| the moved amount is toMove*tracer.mFraction * +--+ * ============================================== */ - tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + velocity*remainingTime, engine); + tracer.doTrace(colobj, stepper.mEndPos, stepper.mEndPos + toMove, engine); if(tracer.mFraction < std::numeric_limits::epsilon()) return false; // didn't even move the smallest representable amount /* - * Try moving back down sStepSize using stepper. + * Try moving back down sStepSizeDown using stepper. * NOTE: if there is an obstacle below (e.g. stairs), we'll be "stepping up". * Below diagram is the case where we "stepped over" an obstacle in front. * @@ -192,9 +195,12 @@ namespace MWWorld * +--+ +--+ * ============================================== */ - stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSize), engine); + stepper.doTrace(colobj, tracer.mEndPos, tracer.mEndPos-Ogre::Vector3(0.0f,0.0f,sStepSizeDown), engine); if(stepper.mFraction < 1.0f && getSlope(stepper.mPlaneNormal) <= sMaxSlope) { + // don't allow stepping up other actors + if (stepper.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Actor) + return false; // only step down onto semi-horizontal surfaces. don't step down onto the side of a house or a wall. // TODO: stepper.mPlaneNormal does not appear to be reliable - needs more testing // NOTE: caller's variables 'position' & 'remainingTime' are modified here @@ -276,14 +282,17 @@ namespace MWWorld // Early-out for totally static creatures // (Not sure if gravity should still apply?) - if (!ptr.getClass().canWalk(ptr) && !ptr.getClass().canFly(ptr) && !ptr.getClass().canSwim(ptr)) + if (!ptr.getClass().isMobile(ptr)) return position; OEngine::Physic::PhysicActor *physicActor = engine->getCharacter(ptr.getRefData().getHandle()); + if (!physicActor) + return position; + // Reset per-frame data physicActor->setWalkingOnWater(false); - /* Anything to collide with? */ - if(!physicActor || !physicActor->getCollisionMode()) + // Anything to collide with? + if(!physicActor->getCollisionMode()) { return position + (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) @@ -294,28 +303,16 @@ namespace MWWorld Ogre::Vector3 halfExtents = physicActor->getHalfExtents(); position.z += halfExtents.z; - waterlevel -= halfExtents.z * 0.5; - /* - * A 3/4 submerged example - * - * +---+ - * | | - * | | <- (original waterlevel) - * | | - * | | <- position <- waterlevel - * | | - * | | - * | | - * +---+ <- (original position) - */ + static const float fSwimHeightScale = MWBase::Environment::get().getWorld()->getStore().get() + .find("fSwimHeightScale")->getFloat(); + float swimlevel = waterlevel + halfExtents.z - (halfExtents.z * 2 * fSwimHeightScale); OEngine::Physic::ActorTracer tracer; - Ogre::Vector3 inertia(0.0f); + Ogre::Vector3 inertia = physicActor->getInertialForce(); Ogre::Vector3 velocity; - if(position.z < waterlevel || isFlying) // under water by 3/4 or can fly + if(position.z < swimlevel || isFlying) { - // TODO: Shouldn't water have higher drag in calculating velocity? velocity = (Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z)* Ogre::Quaternion(Ogre::Radian(refpos.rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X)) * movement; } @@ -323,25 +320,12 @@ namespace MWWorld { velocity = Ogre::Quaternion(Ogre::Radian(refpos.rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * movement; - // not in water nor can fly, so need to deal with gravity - if(!physicActor->getOnGround()) // if current OnGround status is false, must be falling or jumping + if (velocity.z > 0.f) + inertia = velocity; + if(!physicActor->getOnGround()) { - // If falling or jumping up, add part of the incoming velocity with the current inertia, - // but don't allow increasing inertia beyond actor's speed (except on the initial jump impulse) - float actorSpeed = ptr.getClass().getSpeed(ptr); - float cap = std::max(actorSpeed, Ogre::Vector2(physicActor->getInertialForce().x, physicActor->getInertialForce().y).length()); - Ogre::Vector3 newVelocity = velocity + physicActor->getInertialForce(); - if (Ogre::Vector2(newVelocity.x, newVelocity.y).squaredLength() > cap*cap) - { - velocity = newVelocity; - float speedXY = Ogre::Vector2(velocity.x, velocity.y).length(); - velocity.x *= cap / speedXY; - velocity.y *= cap / speedXY; - } - else - velocity = newVelocity; + velocity = velocity + physicActor->getInertialForce(); } - inertia = velocity; // NOTE: velocity is for z axis only in this code block } ptr.getClass().getMovementSettings(ptr).mPosition[2] = 0; @@ -355,6 +339,8 @@ namespace MWWorld velocity *= 1.f-(fStromWalkMult * (angle.valueDegrees()/180.f)); } + Ogre::Vector3 origVelocity = velocity; + Ogre::Vector3 newPosition = position; /* * A loop to find newPosition using tracer, if successful different from the starting position. @@ -364,16 +350,13 @@ namespace MWWorld float remainingTime = time; for(int iterations = 0; iterations < sMaxIterations && remainingTime > 0.01f; ++iterations) { - // NOTE: velocity is either z axis only or x & z axis Ogre::Vector3 nextpos = newPosition + velocity * remainingTime; // If not able to fly, don't allow to swim up into the air - // TODO: this if condition may not work for large creatures or situations - // where the creature gets above the waterline for some reason - if(newPosition.z < waterlevel && // started 3/4 under water + if(newPosition.z < swimlevel && !isFlying && // can't fly - nextpos.z > waterlevel && // but about to go above water - newPosition.z <= waterlevel) + nextpos.z > swimlevel && // but about to go above water + newPosition.z <= swimlevel) { const Ogre::Vector3 down(0,0,-1); Ogre::Real movelen = velocity.normalise(); @@ -420,36 +403,48 @@ namespace MWWorld Ogre::Vector3 oldPosition = newPosition; // We hit something. Try to step up onto it. (NOTE: stepMove does not allow stepping over) // NOTE: stepMove modifies newPosition if successful - if(stepMove(colobj, newPosition, velocity, remainingTime, engine)) + bool result = stepMove(colobj, newPosition, velocity*remainingTime, remainingTime, engine); + if (!result) // to make sure the maximum stepping distance isn't framerate-dependent or movement-speed dependent + result = stepMove(colobj, newPosition, velocity.normalisedCopy()*10.f, remainingTime, engine); + if(result) { // don't let pure water creatures move out of water after stepMove - if((ptr.getClass().canSwim(ptr) && !ptr.getClass().canWalk(ptr)) - && newPosition.z > (waterlevel - halfExtents.z * 0.5)) + if (ptr.getClass().isPureWaterCreature(ptr) + && newPosition.z + halfExtents.z > waterlevel) newPosition = oldPosition; } else { // Can't move this way, try to find another spot along the plane - Ogre::Real movelen = velocity.normalise(); + Ogre::Vector3 direction = velocity; + Ogre::Real movelen = direction.normalise(); Ogre::Vector3 reflectdir = velocity.reflect(tracer.mPlaneNormal); reflectdir.normalise(); - velocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; + + Ogre::Vector3 newVelocity = slide(reflectdir, tracer.mPlaneNormal)*movelen; + if ((newVelocity-velocity).squaredLength() < 0.01) + break; + if (velocity.dotProduct(origVelocity) <= 0.f) + break; + + velocity = newVelocity; // Do not allow sliding upward if there is gravity. Stepping will have taken // care of that. - if(!(newPosition.z < waterlevel || isFlying)) + if(!(newPosition.z < swimlevel || isFlying)) velocity.z = std::min(velocity.z, 0.0f); } } bool isOnGround = false; - if (!(inertia.z > 0.f) && !(newPosition.z < waterlevel)) + if (!(inertia.z > 0.f) && !(newPosition.z < swimlevel)) { Ogre::Vector3 from = newPosition; Ogre::Vector3 to = newPosition - (physicActor->getOnGround() ? - Ogre::Vector3(0,0,sStepSize+2.f) : Ogre::Vector3(0,0,2.f)); + Ogre::Vector3(0,0,sStepSizeDown+2.f) : Ogre::Vector3(0,0,2.f)); tracer.doTrace(colobj, from, to, engine); - if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope) + if(tracer.mFraction < 1.0f && getSlope(tracer.mPlaneNormal) <= sMaxSlope + && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup != OEngine::Physic::CollisionType_Actor) { const btCollisionObject* standingOn = tracer.mHitObject; if (const OEngine::Physic::RigidBody* body = dynamic_cast(standingOn)) @@ -465,10 +460,28 @@ namespace MWWorld isOnGround = true; } else + { + // standing on actors is not allowed (see above). + // in addition to that, apply a sliding effect away from the center of the actor, + // so that we do not stay suspended in air indefinitely. + if (tracer.mFraction < 1.0f && tracer.mHitObject->getBroadphaseHandle()->m_collisionFilterGroup == OEngine::Physic::CollisionType_Actor) + { + if (Ogre::Vector3(velocity.x, velocity.y, 0).squaredLength() < 100.f*100.f) + { + btVector3 aabbMin, aabbMax; + tracer.mHitObject->getCollisionShape()->getAabb(tracer.mHitObject->getWorldTransform(), aabbMin, aabbMax); + btVector3 center = (aabbMin + aabbMax) / 2.f; + inertia = Ogre::Vector3(position.x - center.x(), position.y - center.y(), 0); + inertia.normalise(); + inertia *= 100; + } + } + isOnGround = false; + } } - if(isOnGround || newPosition.z < waterlevel || isFlying) + if(isOnGround || newPosition.z < swimlevel || isFlying) physicActor->setInertialForce(Ogre::Vector3(0.0f)); else { @@ -479,7 +492,7 @@ namespace MWWorld } physicActor->setOnGround(isOnGround); - newPosition.z -= halfExtents.z; // remove what was added at the beggining + newPosition.z -= halfExtents.z; // remove what was added at the beginning return newPosition; } }; @@ -659,9 +672,8 @@ namespace MWWorld mEngine->removeHeightField(x, y); } - void PhysicsSystem::addObject (const Ptr& ptr, bool placeable) + void PhysicsSystem::addObject (const Ptr& ptr, const std::string& mesh, bool placeable) { - std::string mesh = ptr.getClass().getModel(ptr); Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); handleToMesh[node->getName()] = mesh; mEngine->createAndAdjustRigidBody( @@ -670,9 +682,8 @@ namespace MWWorld mesh, node->getName(), ptr.getCellRef().getScale(), node->getPosition(), node->getOrientation(), 0, 0, true, placeable); } - void PhysicsSystem::addActor (const Ptr& ptr) + void PhysicsSystem::addActor (const Ptr& ptr, const std::string& mesh) { - std::string mesh = ptr.getClass().getModel(ptr); Ogre::SceneNode* node = ptr.getRefData().getBaseNode(); //TODO:optimize this. Searching the std::map isn't very efficient i think. mEngine->addCharacter(node->getName(), mesh, node->getPosition(), node->getScale().x, node->getOrientation()); @@ -743,19 +754,25 @@ namespace MWWorld const std::string &handle = node->getName(); if(handleToMesh.find(handle) != handleToMesh.end()) { + std::string model = ptr.getClass().getModel(ptr); + model = Misc::ResourceHelpers::correctActorModelPath(model); // FIXME: scaling shouldn't require model + bool placeable = false; if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle,true)) placeable = body->mPlaceable; else if (OEngine::Physic::RigidBody* body = mEngine->getRigidBody(handle,false)) placeable = body->mPlaceable; removeObject(handle); - addObject(ptr, placeable); + addObject(ptr, model, placeable); } if (OEngine::Physic::PhysicActor* act = mEngine->getCharacter(handle)) { - // NOTE: Ignoring Npc::adjustScale (race height) on purpose. This is a bug in MW and must be replicated for compatibility reasons - act->setScale(ptr.getCellRef().getScale()); + float scale = ptr.getCellRef().getScale(); + if (!ptr.getClass().isNpc()) + // NOTE: Ignoring Npc::adjustScale (race height) on purpose. This is a bug in MW and must be replicated for compatibility reasons + ptr.getClass().adjustScale(ptr, scale); + act->setScale(scale); } } @@ -787,6 +804,7 @@ namespace MWWorld bool PhysicsSystem::getObjectAABB(const MWWorld::Ptr &ptr, Ogre::Vector3 &min, Ogre::Vector3 &max) { std::string model = ptr.getClass().getModel(ptr); + model = Misc::ResourceHelpers::correctActorModelPath(model); if (model.empty()) { return false; } @@ -844,9 +862,9 @@ namespace MWWorld for(;iter != mMovementQueue.end();++iter) { float waterlevel = -std::numeric_limits::max(); - const ESM::Cell *cell = iter->first.getCell()->getCell(); - if(cell->hasWater()) - waterlevel = cell->mWater; + const MWWorld::CellStore *cell = iter->first.getCell(); + if(cell->getCell()->hasWater()) + waterlevel = cell->getWaterLevel(); float oldHeight = iter->first.getRefData().getPosition().pos[2]; @@ -854,7 +872,7 @@ namespace MWWorld bool waterCollision = false; if (effects.get(ESM::MagicEffect::WaterWalking).getMagnitude() - && cell->hasWater() + && cell->getCell()->hasWater() && !world->isUnderwater(iter->first.getCell(), Ogre::Vector3(iter->first.getRefData().getPosition().pos))) waterCollision = true; diff --git a/apps/openmw/mwworld/physicssystem.hpp b/apps/openmw/mwworld/physicssystem.hpp index 7dc8acaa19..c1046aacb4 100644 --- a/apps/openmw/mwworld/physicssystem.hpp +++ b/apps/openmw/mwworld/physicssystem.hpp @@ -38,9 +38,9 @@ namespace MWWorld void setWaterHeight(float height); void disableWater(); - void addObject (const MWWorld::Ptr& ptr, bool placeable=false); + void addObject (const MWWorld::Ptr& ptr, const std::string& mesh, bool placeable=false); - void addActor (const MWWorld::Ptr& ptr); + void addActor (const MWWorld::Ptr& ptr, const std::string& mesh); void addHeightField (float* heights, int x, int y, float yoffset, diff --git a/apps/openmw/mwworld/player.cpp b/apps/openmw/mwworld/player.cpp index b3996f7566..8f3560a691 100644 --- a/apps/openmw/mwworld/player.cpp +++ b/apps/openmw/mwworld/player.cpp @@ -225,11 +225,9 @@ namespace MWWorld writer.startRecord (ESM::REC_PLAY); player.save (writer); writer.endRecord (ESM::REC_PLAY); - - progress.increaseProgress(); } - bool Player::readRecord (ESM::ESMReader& reader, int32_t type) + bool Player::readRecord (ESM::ESMReader& reader, uint32_t type) { if (type==ESM::REC_PLAY) { @@ -262,9 +260,20 @@ namespace MWWorld mCellStore = world.getExterior(0,0); } - if (!player.mBirthsign.empty() && - !world.getStore().get().search (player.mBirthsign)) - throw std::runtime_error ("invalid player state record (birthsign)"); + if (!player.mBirthsign.empty()) + { + const ESM::BirthSign* sign = world.getStore().get().search (player.mBirthsign); + if (!sign) + throw std::runtime_error ("invalid player state record (birthsign does not exist)"); + + // To handle the case where a birth sign was edited in between play sessions (does not yet handle removing the old spells) + // Also needed for ess-imported savegames which do not specify the birtsign spells in the player's spell list. + for (std::vector::const_iterator iter (sign->mPowers.mList.begin()); + iter!=sign->mPowers.mList.end(); ++iter) + { + getPlayer().getClass().getCreatureStats(getPlayer()).getSpells().add (*iter); + } + } mCurrentCrimeId = player.mCurrentCrimeId; mPaidCrimeId = player.mPaidCrimeId; diff --git a/apps/openmw/mwworld/player.hpp b/apps/openmw/mwworld/player.hpp index d8cde5952b..25d8981cde 100644 --- a/apps/openmw/mwworld/player.hpp +++ b/apps/openmw/mwworld/player.hpp @@ -102,7 +102,7 @@ namespace MWWorld void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, int32_t type); + bool readRecord (ESM::ESMReader& reader, uint32_t type); int getNewCrimeId(); // get new id for witnesses void recordCrimeId(); // record the paid crime id when bounty is 0 diff --git a/apps/openmw/mwworld/projectilemanager.cpp b/apps/openmw/mwworld/projectilemanager.cpp index afda6fe606..c97e4e3a50 100644 --- a/apps/openmw/mwworld/projectilemanager.cpp +++ b/apps/openmw/mwworld/projectilemanager.cpp @@ -9,6 +9,7 @@ #include "../mwworld/manualref.hpp" #include "../mwworld/class.hpp" +#include "../mwworld/esmstore.hpp" #include "../mwworld/inventorystore.hpp" #include "../mwbase/soundmanager.hpp" @@ -44,7 +45,7 @@ namespace MWWorld state.mObject->mControllers[i].setSource(Ogre::SharedPtr (new MWRender::EffectAnimationTime())); } - MWRender::Animation::setRenderProperties(state.mObject, MWRender::RV_Misc, + MWRender::Animation::setRenderProperties(state.mObject, MWRender::RV_Effects, MWRender::RQG_Main, MWRender::RQG_Alpha, 0.f, false, NULL); } @@ -206,7 +207,7 @@ namespace MWWorld if (hit) { MWWorld::Ptr caster = MWBase::Environment::get().getWorld()->searchPtrViaActorId(it->mActorId); - MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, it->mSpellId, it->mSourceName); + MWBase::Environment::get().getWorld()->explodeSpell(pos, it->mEffects, caster, ESM::RT_Target, it->mSpellId, it->mSourceName); MWBase::Environment::get().getSoundManager()->stopSound(it->mSound); @@ -318,8 +319,6 @@ namespace MWWorld state.save(writer); writer.endRecord(ESM::REC_PROJ); - - progress.increaseProgress(); } for (std::vector::const_iterator it = mMagicBolts.begin(); it != mMagicBolts.end(); ++it) @@ -342,12 +341,10 @@ namespace MWWorld state.save(writer); writer.endRecord(ESM::REC_MPRJ); - - progress.increaseProgress(); } } - bool ProjectileManager::readRecord(ESM::ESMReader &reader, int32_t type) + bool ProjectileManager::readRecord(ESM::ESMReader &reader, uint32_t type) { if (type == ESM::REC_PROJ) { diff --git a/apps/openmw/mwworld/projectilemanager.hpp b/apps/openmw/mwworld/projectilemanager.hpp index 6e84b9efb2..93f54c0086 100644 --- a/apps/openmw/mwworld/projectilemanager.hpp +++ b/apps/openmw/mwworld/projectilemanager.hpp @@ -53,7 +53,7 @@ namespace MWWorld void clear(); void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - bool readRecord (ESM::ESMReader& reader, int32_t type); + bool readRecord (ESM::ESMReader& reader, uint32_t type); int countSavedGameRecords() const; private: diff --git a/apps/openmw/mwworld/ptr.hpp b/apps/openmw/mwworld/ptr.hpp index 2f37a1cfd8..4d928dacf1 100644 --- a/apps/openmw/mwworld/ptr.hpp +++ b/apps/openmw/mwworld/ptr.hpp @@ -6,7 +6,7 @@ #include #include -#include "cellreflist.hpp" +#include "livecellref.hpp" namespace MWWorld { diff --git a/apps/openmw/mwworld/recordcmp.hpp b/apps/openmw/mwworld/recordcmp.hpp index 7de4f5565a..500f86b1eb 100644 --- a/apps/openmw/mwworld/recordcmp.hpp +++ b/apps/openmw/mwworld/recordcmp.hpp @@ -5,6 +5,8 @@ #include +#include + namespace MWWorld { struct RecordCmp diff --git a/apps/openmw/mwworld/scene.cpp b/apps/openmw/mwworld/scene.cpp index 02c9db9ea4..72d475f169 100644 --- a/apps/openmw/mwworld/scene.cpp +++ b/apps/openmw/mwworld/scene.cpp @@ -3,6 +3,7 @@ #include #include +#include #include "../mwbase/environment.hpp" #include "../mwbase/world.hpp" @@ -20,6 +21,18 @@ namespace { + + void addObject(const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, + MWRender::RenderingManager& rendering) + { + std::string model = Misc::ResourceHelpers::correctActorModelPath(ptr.getClass().getModel(ptr)); + std::string id = ptr.getClass().getId(ptr); + if (id == "prisonmarker" || id == "divinemarker" || id == "templemarker" || id == "northmarker") + model = ""; // marker objects that have a hardcoded function in the game logic, should be hidden from the player + rendering.addObject(ptr, model); + ptr.getClass().insertObject (ptr, model, physics); + } + void updateObjectLocalRotation (const MWWorld::Ptr& ptr, MWWorld::PhysicsSystem& physics, MWRender::RenderingManager& rendering) { @@ -79,9 +92,7 @@ namespace { try { - mRendering.addObject (ptr); - ptr.getClass().insertObject (ptr, mPhysics); - + addObject(ptr, mPhysics, mRendering); updateObjectLocalRotation(ptr, mPhysics, mRendering); if (ptr.getRefData().getBaseNode()) { @@ -220,6 +231,11 @@ namespace MWWorld cell->getCell()->getGridY() ); if (land) { + // Actually only VHGT is needed here, but we'll need the rest for rendering anyway. + // Load everything now to reduce IO overhead. + const int flags = ESM::Land::DATA_VCLR|ESM::Land::DATA_VHGT|ESM::Land::DATA_VNML|ESM::Land::DATA_VTEX; + if (!land->isDataLoaded(flags)) + land->loadData(flags); mPhysics->addHeightField ( land->mLandData->mHeights, cell->getCell()->getGridX(), @@ -296,15 +312,17 @@ namespace MWWorld std::string loadingExteriorText = "#{sLoadingMessage3}"; loadingListener->setLabel(loadingExteriorText); + const int halfGridSize = Settings::Manager::getInt("exterior grid size", "Cells")/2; + CellStoreCollection::iterator active = mActiveCells.begin(); while (active!=mActiveCells.end()) { if ((*active)->getCell()->isExterior()) { - if (std::abs (X-(*active)->getCell()->getGridX())<=1 && - std::abs (Y-(*active)->getCell()->getGridY())<=1) + if (std::abs (X-(*active)->getCell()->getGridX())<=halfGridSize && + std::abs (Y-(*active)->getCell()->getGridY())<=halfGridSize) { - // keep cells within the new 3x3 grid + // keep cells within the new grid ++active; continue; } @@ -314,9 +332,9 @@ namespace MWWorld int refsToLoad = 0; // get the number of refs to load - for (int x=X-1; x<=X+1; ++x) + for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) { - for (int y=Y-1; y<=Y+1; ++y) + for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -339,9 +357,9 @@ namespace MWWorld loadingListener->setProgressRange(refsToLoad); // Load cells - for (int x=X-1; x<=X+1; ++x) + for (int x=X-halfGridSize; x<=X+halfGridSize; ++x) { - for (int y=Y-1; y<=Y+1; ++y) + for (int y=Y-halfGridSize; y<=Y+halfGridSize; ++y) { CellStoreCollection::iterator iter = mActiveCells.begin(); @@ -484,8 +502,7 @@ namespace MWWorld // Sky system MWBase::Environment::get().getWorld()->adjustSky(); - mCellChanged = true; - MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); + mCellChanged = true; MWBase::Environment::get().getWindowManager()->fadeScreenIn(0.5); MWBase::Environment::get().getWindowManager()->changeCell(mCurrentCell); @@ -527,10 +544,16 @@ namespace MWWorld void Scene::addObjectToScene (const Ptr& ptr) { - mRendering.addObject(ptr); - ptr.getClass().insertObject(ptr, *mPhysics); - MWBase::Environment::get().getWorld()->rotateObject(ptr, 0, 0, 0, true); - MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); + try + { + addObject(ptr, *mPhysics, mRendering); + MWBase::Environment::get().getWorld()->rotateObject(ptr, 0, 0, 0, true); + MWBase::Environment::get().getWorld()->scaleObject(ptr, ptr.getCellRef().getScale()); + } + catch (std::exception& e) + { + std::cerr << "error during rendering: " << e.what() << std::endl; + } } void Scene::removeObjectFromScene (const Ptr& ptr) diff --git a/apps/openmw/mwworld/store.cpp b/apps/openmw/mwworld/store.cpp index caf4083fe0..cdcc00b4d3 100644 --- a/apps/openmw/mwworld/store.cpp +++ b/apps/openmw/mwworld/store.cpp @@ -1,6 +1,8 @@ #include "store.hpp" #include "esmstore.hpp" +#include + namespace MWWorld { void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) @@ -11,8 +13,7 @@ void Store::handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell) ESM::MovedCellRef cMRef; cell->getNextMVRF(esm, cMRef); - MWWorld::Store &cStore = const_cast&>(mEsmStore->get()); - ESM::Cell *cellAlt = const_cast(cStore.searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); + ESM::Cell *cellAlt = const_cast(searchOrCreate(cMRef.mTarget[0], cMRef.mTarget[1])); // Get regular moved reference data. Adapted from CellStore::loadRefs. Maybe we can optimize the following // implementation when the oher implementation works as well. @@ -118,4 +119,9 @@ void Store::load(ESM::ESMReader &esm, const std::string &id) } } +void Store::load(ESM::ESMReader &esm, const std::string &id) +{ + load(esm, id, esm.getIndex()); +} + } diff --git a/apps/openmw/mwworld/store.hpp b/apps/openmw/mwworld/store.hpp index c8fa087c20..a155a37609 100644 --- a/apps/openmw/mwworld/store.hpp +++ b/apps/openmw/mwworld/store.hpp @@ -5,6 +5,7 @@ #include #include #include +#include #include @@ -30,7 +31,7 @@ namespace MWWorld virtual void write (ESM::ESMWriter& writer) const {} - virtual void read (ESM::ESMReader& reader) {} + virtual void read (ESM::ESMReader& reader, const std::string& id) {} ///< Read into dynamic storage }; @@ -99,7 +100,9 @@ namespace MWWorld class Store : public StoreBase { std::map mStatic; - std::vector mShared; + std::vector mShared; // Preserves the record order as it came from the content files (this + // is relevant for the spell autocalc code and selection order + // for heads/hairs in the character creation) std::map mDynamic; typedef std::map Dynamic; @@ -137,25 +140,27 @@ namespace MWWorld // setUp needs to be called again after virtual void clearDynamic() { + // remove the dynamic part of mShared + assert(mShared.size() >= mStatic.size()); + mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); mDynamic.clear(); - mShared.clear(); } const T *search(const std::string &id) const { T item; item.mId = Misc::StringUtils::lowerCase(id); + typename Dynamic::const_iterator dit = mDynamic.find(item.mId); + if (dit != mDynamic.end()) { + return &dit->second; + } + typename std::map::const_iterator it = mStatic.find(item.mId); if (it != mStatic.end() && Misc::StringUtils::ciEqual(it->second.mId, id)) { return &(it->second); } - typename Dynamic::const_iterator dit = mDynamic.find(item.mId); - if (dit != mDynamic.end()) { - return &dit->second; - } - return 0; } @@ -203,18 +208,16 @@ namespace MWWorld void load(ESM::ESMReader &esm, const std::string &id) { std::string idLower = Misc::StringUtils::lowerCase(id); - mStatic[idLower] = T(); - mStatic[idLower].mId = idLower; - mStatic[idLower].load(esm); + + std::pair inserted = mStatic.insert(std::make_pair(idLower, T())); + if (inserted.second) + mShared.push_back(&inserted.first->second); + + inserted.first->second.mId = idLower; + inserted.first->second.load(esm); } void setUp() { - mShared.clear(); - mShared.reserve(mStatic.size()); - typename std::map::iterator it = mStatic.begin(); - for (; it != mStatic.end(); ++it) { - mShared.push_back(&(it->second)); - } } iterator begin() const { @@ -302,6 +305,7 @@ namespace MWWorld mDynamic.erase(it); // have to reinit the whole shared part + assert(mShared.size() >= mStatic.size()); mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); for (it = mDynamic.begin(); it != mDynamic.end(); ++it) { mShared.push_back(&it->second); @@ -322,33 +326,18 @@ namespace MWWorld writer.writeHNString ("NAME", iter->second.mId); iter->second.save (writer); writer.endRecord (T::sRecordId); - progress.increaseProgress(); } } - void read (ESM::ESMReader& reader) + void read (ESM::ESMReader& reader, const std::string& id) { T record; - record.mId = reader.getHNString ("NAME"); + record.mId = id; record.load (reader); insert (record); } }; - template <> - inline void Store::clearDynamic() - { - std::map::iterator iter = mDynamic.begin(); - - while (iter!=mDynamic.end()) - if (iter->first=="player") - ++iter; - else - mDynamic.erase (iter++); - - mShared.clear(); - } - template <> inline void Store::load(ESM::ESMReader &esm, const std::string &id) { std::string idLower = Misc::StringUtils::lowerCase(id); @@ -356,10 +345,9 @@ namespace MWWorld std::map::iterator it = mStatic.find(idLower); if (it == mStatic.end()) { it = mStatic.insert( std::make_pair( idLower, ESM::Dialogue() ) ).first; - it->second.mId = id; // don't smash case here, as this line is printed... I think + it->second.mId = id; // don't smash case here, as this line is printed } - //I am not sure is it need to load the dialog from a plugin if it was already loaded from prevois plugins it->second.load(esm); } @@ -368,7 +356,12 @@ namespace MWWorld ESM::Script scpt; scpt.load(esm); Misc::StringUtils::toLower(scpt.mId); - mStatic[scpt.mId] = scpt; + + std::pair inserted = mStatic.insert(std::make_pair(scpt.mId, scpt)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = scpt; } template <> @@ -376,7 +369,12 @@ namespace MWWorld ESM::StartScript s; s.load(esm); s.mId = Misc::StringUtils::toLower(s.mScript); - mStatic[s.mId] = s; + + std::pair inserted = mStatic.insert(std::make_pair(s.mId, s)); + if (inserted.second) + mShared.push_back(&inserted.first->second); + else + inserted.first->second = s; } template <> @@ -443,9 +441,7 @@ namespace MWWorld ltexl[lt.mIndex] = lt; } - void load(ESM::ESMReader &esm, const std::string &id) { - load(esm, id, esm.getIndex()); - } + void load(ESM::ESMReader &esm, const std::string &id); iterator begin(size_t plugin) const { assert(plugin < mStatic.size()); @@ -585,14 +581,8 @@ namespace MWWorld void handleMovedCellRefs(ESM::ESMReader& esm, ESM::Cell* cell); public: - ESMStore *mEsmStore; - typedef SharedIterator iterator; - Store() - : mEsmStore(NULL) - {} - const ESM::Cell *search(const std::string &id) const { ESM::Cell cell; cell.mName = Misc::StringUtils::lowerCase(id); @@ -630,9 +620,6 @@ namespace MWWorld } const ESM::Cell *searchOrCreate(int x, int y) { - ESM::Cell cell; - cell.mData.mX = x, cell.mData.mY = y; - std::pair key(x, y); DynamicExt::const_iterator it = mExt.find(key); if (it != mExt.end()) { @@ -644,13 +631,15 @@ namespace MWWorld return &dit->second; } - ESM::Cell *newCell = new ESM::Cell; - newCell->mData.mX = x; - newCell->mData.mY = y; - mExt[std::make_pair(x, y)] = *newCell; - delete newCell; - - return &mExt[std::make_pair(x, y)]; + ESM::Cell newCell; + newCell.mData.mX = x; + newCell.mData.mY = y; + newCell.mData.mFlags = ESM::Cell::HasWater; + newCell.mAmbi.mAmbient = 0; + newCell.mAmbi.mSunlight = 0; + newCell.mAmbi.mFog = 0; + newCell.mAmbi.mFogDensity = 0; + return &mExt.insert(std::make_pair(key, newCell)).first->second; } const ESM::Cell *find(const std::string &id) const { @@ -849,15 +838,33 @@ namespace MWWorld Interior mInt; Exterior mExt; + Store* mCells; + public: - void load(ESM::ESMReader &esm, const std::string &id) { + Store() + : mCells(NULL) + { + } + void setCells(Store& cells) + { + mCells = &cells; + } + + void load(ESM::ESMReader &esm, const std::string &id) { ESM::Pathgrid pathgrid; pathgrid.load(esm); + // Unfortunately the Pathgrid record model does not specify whether the pathgrid belongs to an interior or exterior cell. + // For interior cells, mCell is the cell name, but for exterior cells it is either the cell name or if that doesn't exist, the cell's region name. + // mX and mY will be (0,0) for interior cells, but there is also an exterior cell with the coordinates of (0,0), so that doesn't help. + // Check whether mCell is an interior cell. This isn't perfect, will break if a Region with the same name as an interior cell is created. + // A proper fix should be made for future versions of the file format. + bool interior = mCells->search(pathgrid.mCell) != NULL; + // Try to overwrite existing record - if (!pathgrid.mCell.empty()) + if (interior) { std::pair ret = mInt.insert(std::make_pair(pathgrid.mCell, pathgrid)); if (!ret.second) @@ -865,8 +872,7 @@ namespace MWWorld } else { - std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), - pathgrid)); + std::pair ret = mExt.insert(std::make_pair(std::make_pair(pathgrid.mData.mX, pathgrid.mData.mY), pathgrid)); if (!ret.second) ret.first->second = pathgrid; } @@ -886,45 +892,47 @@ namespace MWWorld return NULL; } - const ESM::Pathgrid *find(int x, int y) const { - const ESM::Pathgrid *ptr = search(x, y); - if (ptr == 0) { - std::ostringstream msg; - msg << "Pathgrid at (" << x << ", " << y << ") not found"; - throw std::runtime_error(msg.str()); - } - return ptr; - } - - const ESM::Pathgrid *search(const std::string &name) const { + const ESM::Pathgrid *search(const std::string& name) const { Interior::const_iterator it = mInt.find(name); if (it != mInt.end()) return &(it->second); return NULL; } - const ESM::Pathgrid *find(const std::string &name) const { - const ESM::Pathgrid *ptr = search(name); - if (ptr == 0) { + const ESM::Pathgrid *find(int x, int y) const { + const ESM::Pathgrid* pathgrid = search(x,y); + if (!pathgrid) + { + std::ostringstream msg; + msg << "Pathgrid in cell '" << x << " " << y << "' not found"; + throw std::runtime_error(msg.str()); + } + return pathgrid; + } + + const ESM::Pathgrid* find(const std::string& name) const { + const ESM::Pathgrid* pathgrid = search(name); + if (!pathgrid) + { std::ostringstream msg; msg << "Pathgrid in cell '" << name << "' not found"; throw std::runtime_error(msg.str()); } - return ptr; + return pathgrid; } const ESM::Pathgrid *search(const ESM::Cell &cell) const { - if (cell.mData.mFlags & ESM::Cell::Interior) { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return search(cell.mData.mX, cell.mData.mY); + else return search(cell.mName); - } - return search(cell.mData.mX, cell.mData.mY); } const ESM::Pathgrid *find(const ESM::Cell &cell) const { - if (cell.mData.mFlags & ESM::Cell::Interior) { + if (!(cell.mData.mFlags & ESM::Cell::Interior)) + return find(cell.mData.mX, cell.mData.mY); + else return find(cell.mName); - } - return find(cell.mData.mX, cell.mData.mY); } }; @@ -1049,37 +1057,6 @@ namespace MWWorld } }; - - // Specialisation for ESM::Spell to preserve record order as it was in the content files. - // The NPC spell autocalc code heavily depends on this order. - // We could also do this in the base class, but it's usually not a good idea to depend on record order. - template<> - inline void Store::clearDynamic() - { - // remove the dynamic part of mShared - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - mDynamic.clear(); - } - - template<> - inline void Store::load(ESM::ESMReader &esm, const std::string &id) { - std::string idLower = Misc::StringUtils::lowerCase(id); - - std::pair inserted = mStatic.insert(std::make_pair(idLower, ESM::Spell())); - if (inserted.second) - mShared.push_back(&mStatic[idLower]); - - inserted.first->second.mId = idLower; - inserted.first->second.load(esm); - } - - template<> - inline void Store::setUp() - { - // remove the dynamic part of mShared - mShared.erase(mShared.begin() + mStatic.size(), mShared.end()); - } - template<> inline void Store::setUp() { diff --git a/apps/openmw/mwworld/weather.cpp b/apps/openmw/mwworld/weather.cpp index 3f9b7d5623..4440ed030e 100644 --- a/apps/openmw/mwworld/weather.cpp +++ b/apps/openmw/mwworld/weather.cpp @@ -1,3 +1,6 @@ +#define _USE_MATH_DEFINES +#include + #include "weather.hpp" #include @@ -427,29 +430,35 @@ void WeatherManager::update(float duration, bool paused) else mRendering->getSkyManager()->sunEnable(); - // sun angle - float height; + // Update the sun direction. Run it east to west at a fixed angle from overhead. + // The sun's speed at day and night may differ, since mSunriseTime and mNightStart + // mark when the sun is level with the horizon. + { + // Shift times into a 24-hour window beginning at mSunriseTime... + float adjustedHour = mHour; + float adjustedNightStart = mNightStart; + if ( mHour < mSunriseTime ) + adjustedHour += 24.f; + if ( mNightStart < mSunriseTime ) + adjustedNightStart += 24.f; - //Day duration - float dayDuration = (mNightStart - 1) - mSunriseTime; + const bool is_night = adjustedHour >= adjustedNightStart; + const float dayDuration = adjustedNightStart - mSunriseTime; + const float nightDuration = 24.f - dayDuration; - // rise at 6, set at 20 - if (mHour >= mSunriseTime && mHour <= mNightStart) - height = 1 - std::abs(((mHour - dayDuration) / 7.f)); - else if (mHour > mNightStart) - height = (mHour - mNightStart) / 4.f; - else //if (mHour > 0 && mHour < 6) - height = 1 - (mHour / mSunriseTime); + double theta; + if ( !is_night ) { + theta = M_PI * (adjustedHour - mSunriseTime) / dayDuration; + } else { + theta = M_PI * (adjustedHour - adjustedNightStart) / nightDuration; + } - int facing = (mHour > 13.f) ? 1 : -1; - - bool sun_is_moon = mHour >= mNightStart || mHour <= mSunriseTime; - - Vector3 final( - (height - 1) * facing, - (height - 1) * facing, - height); - mRendering->setSunDirection(final, sun_is_moon); + Vector3 final( + cos( theta ), + -0.268f, // approx tan( -15 degrees ) + sin( theta ) ); + mRendering->setSunDirection( final, is_night ); + } /* * TODO: import separated fadeInStart/Finish, fadeOutStart/Finish @@ -760,10 +769,9 @@ void WeatherManager::write(ESM::ESMWriter& writer, Loading::Listener& progress) writer.startRecord(ESM::REC_WTHR); state.save(writer); writer.endRecord(ESM::REC_WTHR); - progress.increaseProgress(); } -bool WeatherManager::readRecord(ESM::ESMReader& reader, int32_t type) +bool WeatherManager::readRecord(ESM::ESMReader& reader, uint32_t type) { if(ESM::REC_WTHR == type) { @@ -771,14 +779,6 @@ bool WeatherManager::readRecord(ESM::ESMReader& reader, int32_t type) ESM::WeatherState state; state.load(reader); - // reset other temporary state, now that we loaded successfully - stopSounds(); // let's hope this never throws - mRegionOverrides.clear(); - mRegionMods.clear(); - mThunderFlash = 0.0; - mThunderChance = 0.0; - mThunderChanceNeeded = 50.0; - // swap in the loaded values now that we can't fail mHour = state.mHour; mWindSpeed = state.mWindSpeed; @@ -795,6 +795,16 @@ bool WeatherManager::readRecord(ESM::ESMReader& reader, int32_t type) return false; } +void WeatherManager::clear() +{ + stopSounds(); + mRegionOverrides.clear(); + mRegionMods.clear(); + mThunderFlash = 0.0; + mThunderChance = 0.0; + mThunderChanceNeeded = 50.0; +} + void WeatherManager::switchToNextWeather(bool instantly) { MWBase::World* world = MWBase::Environment::get().getWorld(); diff --git a/apps/openmw/mwworld/weather.hpp b/apps/openmw/mwworld/weather.hpp index dee9fc52ae..a2e6681595 100644 --- a/apps/openmw/mwworld/weather.hpp +++ b/apps/openmw/mwworld/weather.hpp @@ -199,7 +199,9 @@ namespace MWWorld void write(ESM::ESMWriter& writer, Loading::Listener& progress); - bool readRecord(ESM::ESMReader& reader, int32_t type); + bool readRecord(ESM::ESMReader& reader, uint32_t type); + + void clear(); private: float mHour; diff --git a/apps/openmw/mwworld/worldimp.cpp b/apps/openmw/mwworld/worldimp.cpp index d783857f10..188f3cdad3 100644 --- a/apps/openmw/mwworld/worldimp.cpp +++ b/apps/openmw/mwworld/worldimp.cpp @@ -18,6 +18,7 @@ #include #include #include +#include #include @@ -43,6 +44,7 @@ #include "player.hpp" #include "manualref.hpp" +#include "cellstore.hpp" #include "cellfunctors.hpp" #include "containerstore.hpp" #include "inventorystore.hpp" @@ -149,7 +151,8 @@ namespace MWWorld mFallback(fallbackMap), mTeleportEnabled(true), mLevitationEnabled(true), mGodMode(false), mContentFiles (contentFiles), mGoToJail(false), mDaysInPrison(0), - mStartCell (startCell), mStartupScript(startupScript) + mStartCell (startCell), mStartupScript(startupScript), + mScriptsEnabled(true) { mPhysics = new PhysicsSystem(renderer); mPhysEngine = mPhysics->getEngine(); @@ -186,6 +189,8 @@ namespace MWWorld mStore.setUp(); mStore.movePlayerRecord(); + mSwimHeightScale = mStore.get().find("fSwimHeightScale")->getFloat(); + mGlobalVariables.fill (mStore); mWorldScene = new Scene(*mRendering, mPhysics); @@ -201,6 +206,7 @@ namespace MWWorld setupPlayer(); renderPlayer(); + mRendering->resetCamera(); MWBase::Environment::get().getWindowManager()->updatePlayer(); @@ -263,6 +269,7 @@ namespace MWWorld void World::clear() { + mWeatherManager->clear(); mRendering->clear(); mProjectileManager->clear(); @@ -287,6 +294,7 @@ namespace MWWorld mDoorStates.clear(); mGodMode = false; + mScriptsEnabled = true; mSky = true; mTeleportEnabled = true; mLevitationEnabled = true; @@ -304,7 +312,13 @@ namespace MWWorld +1 // player record +1 // weather record +1 // actorId counter - +1; // levitation/teleport enabled state + +1 // levitation/teleport enabled state + +1; // camera + } + + int World::countSavedGameCells() const + { + return mCells.countSavedGameRecords(); } void World::write (ESM::ESMWriter& writer, Loading::Listener& progress) const @@ -318,7 +332,6 @@ namespace MWWorld } MWMechanics::CreatureStats::writeActorIdCounter(writer); - progress.increaseProgress(); mStore.write (writer, progress); // dynamic Store must be written (and read) before Cells, so that // references to custom made records will be recognized @@ -332,10 +345,13 @@ namespace MWWorld writer.writeHNT("TELE", mTeleportEnabled); writer.writeHNT("LEVT", mLevitationEnabled); writer.endRecord(ESM::REC_ENAB); - progress.increaseProgress(); + + writer.startRecord(ESM::REC_CAM_); + writer.writeHNT("FIRS", isFirstPerson()); + writer.endRecord(ESM::REC_CAM_); } - void World::readRecord (ESM::ESMReader& reader, int32_t type, + void World::readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap) { switch (type) @@ -393,6 +409,8 @@ namespace MWWorld gmst["sTauntFail"] = ESM::Variant("Taunt Fail"); gmst["sBribeSuccess"] = ESM::Variant("Bribe Success"); gmst["sBribeFail"] = ESM::Variant("Bribe Fail"); + gmst["fNPCHealthBarTime"] = ESM::Variant(5.f); + gmst["fNPCHealthBarFade"] = ESM::Variant(1.f); // Werewolf (BM) gmst["fWereWolfRunMult"] = ESM::Variant(1.f); @@ -1074,7 +1092,7 @@ namespace MWWorld void World::undeleteObject(const Ptr& ptr) { - if (ptr.getCellRef().getRefNum().mContentFile == -1) + if (!ptr.getCellRef().hasContentFile()) return; if (ptr.getRefData().isDeleted()) { @@ -1158,6 +1176,7 @@ namespace MWWorld ptr.getClass().copyToCell(ptr, *newCell, pos); mRendering->updateObjectCell(ptr, copy); + ptr.getRefData().setBaseNode(NULL); MWBase::Environment::get().getSoundManager()->updatePtr (ptr, copy); MWBase::MechanicsManager *mechMgr = MWBase::Environment::get().getMechanicsManager(); @@ -1187,7 +1206,7 @@ namespace MWWorld } } - bool World::moveObjectImp(const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObjectImp(const Ptr& ptr, float x, float y, float z) { CellStore *cell = ptr.getCell(); @@ -1200,12 +1219,14 @@ namespace MWWorld moveObject(ptr, cell, x, y, z); - return cell != ptr.getCell(); + MWWorld::Ptr updated = ptr; + updated.mCell = cell; + return updated; } - void World::moveObject (const Ptr& ptr, float x, float y, float z) + MWWorld::Ptr World::moveObject (const Ptr& ptr, float x, float y, float z) { - moveObjectImp(ptr, x, y, z); + return moveObjectImp(ptr, x, y, z); } void World::scaleObject (const Ptr& ptr, float scale) @@ -1487,6 +1508,16 @@ namespace MWWorld return mStore.insert(record); } + const ESM::CreatureLevList *World::createOverrideRecord(const ESM::CreatureLevList &record) + { + return mStore.overrideRecord(record); + } + + const ESM::ItemLevList *World::createOverrideRecord(const ESM::ItemLevList &record) + { + return mStore.overrideRecord(record); + } + const ESM::NPC *World::createRecord(const ESM::NPC &record) { bool update = false; @@ -1560,6 +1591,8 @@ namespace MWWorld updateWindowManager (); + updateSoundListener(); + if (!paused && mPlayer->getPlayer().getCell()->isExterior()) { ESM::Position pos = mPlayer->getPlayer().getRefData().getPosition(); @@ -1567,6 +1600,18 @@ namespace MWWorld } } + void World::updateSoundListener() + { + Ogre::Vector3 playerPos = mPlayer->getPlayer().getRefData().getBaseNode()->getPosition(); + const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(getPlayerPtr().getRefData().getHandle()); + if(actor) playerPos.z += 1.85*actor->getHalfExtents().z; + Ogre::Quaternion playerOrient = Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[2]), Ogre::Vector3::NEGATIVE_UNIT_Z) * + Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[0]), Ogre::Vector3::NEGATIVE_UNIT_X) * + Ogre::Quaternion(Ogre::Radian(getPlayerPtr().getRefData().getPosition().rot[1]), Ogre::Vector3::NEGATIVE_UNIT_Y); + MWBase::Environment::get().getSoundManager()->setListenerPosDir(playerPos, playerOrient.yAxis(), + playerOrient.zAxis()); + } + void World::updateWindowManager () { // inform the GUI about focused object @@ -1672,8 +1717,9 @@ namespace MWWorld MWWorld::LiveCellRef* ref = statics.find("northmarker"); if (!ref) return Vector2(0, 1); - Ogre::SceneNode* node = ref->mData.getBaseNode(); - Vector3 dir = node->_getDerivedOrientation() * Ogre::Vector3(0,1,0); + + Ogre::Quaternion orient (Ogre::Radian(-ref->mData.getPosition().rot[2]), Ogre::Vector3::UNIT_Z); + Vector3 dir = orient * Ogre::Vector3(0,1,0); Vector2 d = Vector2(dir.x, dir.y); return d; } @@ -1694,6 +1740,23 @@ namespace MWWorld World::DoorMarker newMarker; newMarker.name = MWClass::Door::getDestination(ref); + ESM::CellId cellid; + if (!ref.mRef.getDestCell().empty()) + { + cellid.mWorldspace = ref.mRef.getDestCell(); + cellid.mPaged = false; + } + else + { + cellid.mPaged = true; + MWBase::Environment::get().getWorld()->positionToIndex( + ref.mRef.getDoorDest().pos[0], + ref.mRef.getDoorDest().pos[1], + cellid.mIndex.mX, + cellid.mIndex.mY); + } + newMarker.dest = cellid; + ESM::Position pos = ref.mData.getPosition (); newMarker.x = pos.pos[0]; @@ -1894,8 +1957,7 @@ namespace MWWorld mRendering->getTriangleBatchCount(triangles, batches); } - bool - World::isFlying(const MWWorld::Ptr &ptr) const + bool World::isFlying(const MWWorld::Ptr &ptr) const { const MWMechanics::CreatureStats &stats = ptr.getClass().getCreatureStats(ptr); bool isParalyzed = (stats.getMagicEffects().get(ESM::MagicEffect::Paralyze).getMagnitude() > 0); @@ -1920,8 +1982,7 @@ namespace MWWorld return false; } - bool - World::isSlowFalling(const MWWorld::Ptr &ptr) const + bool World::isSlowFalling(const MWWorld::Ptr &ptr) const { if(!ptr.getClass().isActor()) return false; @@ -1935,31 +1996,35 @@ namespace MWWorld bool World::isSubmerged(const MWWorld::Ptr &object) const { - const float *fpos = object.getRefData().getPosition().pos; - Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); - - const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle()); - if(actor) pos.z += 1.85*actor->getHalfExtents().z; - - return isUnderwater(object.getCell(), pos); + return isUnderwater(object, 1.0/mSwimHeightScale); } - bool - World::isSwimming(const MWWorld::Ptr &object) const + bool World::isSwimming(const MWWorld::Ptr &object) const + { + return isUnderwater(object, mSwimHeightScale); + } + + bool World::isWading(const MWWorld::Ptr &object) const + { + const float kneeDeep = 0.25f; + return isUnderwater(object, kneeDeep); + } + + bool World::isUnderwater(const MWWorld::Ptr &object, const float heightRatio) const { - /// \todo add check ifActor() - only actors can swim const float *fpos = object.getRefData().getPosition().pos; Ogre::Vector3 pos(fpos[0], fpos[1], fpos[2]); - /// \fixme 3/4ths submerged? const OEngine::Physic::PhysicActor *actor = mPhysEngine->getCharacter(object.getRefData().getHandle()); - if(actor) pos.z += actor->getHalfExtents().z * 1.5; + if (actor) + { + pos.z += heightRatio*2*actor->getHalfExtents().z; + } return isUnderwater(object.getCell(), pos); } - bool - World::isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const + bool World::isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const { if (!(cell->getCell()->mData.mFlags & ESM::Cell::HasWater)) { return false; @@ -1980,7 +2045,7 @@ namespace MWWorld bool World::isOnGround(const MWWorld::Ptr &ptr) const { RefData &refdata = ptr.getRefData(); - const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); + OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); if(!physactor) return false; @@ -1998,7 +2063,7 @@ namespace MWWorld mPhysEngine); if(tracer.mFraction < 1.0f) // collision, must be close to something below { - const_cast (physactor)->setOnGround(true); + physactor->setOnGround(true); return true; } else @@ -2042,8 +2107,9 @@ namespace MWWorld // 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(); + std::string model = getPlayerPtr().getClass().getModel(getPlayerPtr()); + model = Misc::ResourceHelpers::correctActorModelPath(model); + mPhysics->addActor(mPlayer->getPlayer(), model); } int World::canRest () @@ -2055,6 +2121,9 @@ namespace MWWorld Ogre::Vector3 playerPos(refdata.getPosition().pos); const OEngine::Physic::PhysicActor *physactor = mPhysEngine->getCharacter(refdata.getHandle()); + if (!physactor) + throw std::runtime_error("can't find player"); + if((!physactor->getOnGround()&&physactor->getCollisionMode()) || isUnderwater(currentCell, playerPos) || isWalkingOnWater(player)) return 2; if((currentCell->getCell()->mData.mFlags&ESM::Cell::NoSleep) || @@ -2232,6 +2301,8 @@ namespace MWWorld for (CellRefList::List::iterator container = refList.begin(); container != refList.end(); ++container) { MWWorld::Ptr ptr (&*container, *cellIt); + if (ptr.getRefData().isDeleted()) + continue; if (Misc::StringUtils::ciEqual(ptr.getCellRef().getOwner(), npc.getCellRef().getRefId())) out.push_back(ptr); } @@ -2272,9 +2343,15 @@ namespace MWWorld if (!targetActor.getRefData().getBaseNode() || !targetActor.getRefData().getBaseNode()) return false; // not in active cell - Ogre::Vector3 halfExt1 = mPhysEngine->getCharacter(actor.getRefData().getHandle())->getHalfExtents(); + OEngine::Physic::PhysicActor* actor1 = mPhysEngine->getCharacter(actor.getRefData().getHandle()); + OEngine::Physic::PhysicActor* actor2 = mPhysEngine->getCharacter(targetActor.getRefData().getHandle()); + + if (!actor1 || !actor2) + return false; + + Ogre::Vector3 halfExt1 = actor1->getHalfExtents(); const float* pos1 = actor.getRefData().getPosition().pos; - Ogre::Vector3 halfExt2 = mPhysEngine->getCharacter(targetActor.getRefData().getHandle())->getHalfExtents(); + Ogre::Vector3 halfExt2 = actor2->getHalfExtents(); const float* pos2 = targetActor.getRefData().getPosition().pos; btVector3 from(pos1[0],pos1[1],pos1[2]+halfExt1.z*2*0.9); // eye level @@ -2309,6 +2386,7 @@ namespace MWWorld bool World::findInteriorPosition(const std::string &name, ESM::Position &pos) { typedef MWWorld::CellRefList::List DoorList; + typedef MWWorld::CellRefList::List StaticList; pos.rot[0] = pos.rot[1] = pos.rot[2] = 0; pos.pos[0] = pos.pos[1] = pos.pos[2] = 0; @@ -2353,6 +2431,13 @@ namespace MWWorld } } } + // Fall back to the first static location. + const StaticList &statics = cellStore->get().mList; + if ( statics.begin() != statics.end() ) { + pos = statics.begin()->mRef.getPosition(); + return true; + } + return false; } @@ -2393,6 +2478,11 @@ namespace MWWorld return mLevitationEnabled; } + void World::reattachPlayerCamera() + { + mRendering->rebuildPtr(getPlayerPtr()); + } + void World::setWerewolf(const MWWorld::Ptr& actor, bool werewolf) { MWMechanics::NpcStats& npcStats = actor.getClass().getNpcStats(actor); @@ -2449,7 +2539,7 @@ namespace MWWorld getStore().get().search("fAlarmRadius")->getFloat(), closeActors); - bool detected = false; + bool detected = false, reported = false; for (std::vector::const_iterator it = closeActors.begin(); it != closeActors.end(); ++it) { if (*it == actor) @@ -2459,16 +2549,22 @@ namespace MWWorld continue; if (getLOS(*it, actor) && MWBase::Environment::get().getMechanicsManager()->awarenessCheck(actor, *it)) - { detected = true; - break; - } + if (it->getClass().getCreatureStats(*it).getAiSetting(MWMechanics::CreatureStats::AI_Alarm).getModified() > 0) + reported = true; } if (detected) { windowManager->messageBox("#{sWerewolfAlarmMessage}"); setGlobalInt("pcknownwerewolf", 1); + + if (reported) + { + npcStats.setBounty(npcStats.getBounty()+ + MWBase::Environment::get().getWorld()->getStore().get().find("iWereWolfBounty")->getInt()); + windowManager->messageBox("#{sCrimeMessage}"); + } } } } @@ -2493,6 +2589,17 @@ namespace MWWorld return mGodMode; } + bool World::toggleScripts() + { + mScriptsEnabled = !mScriptsEnabled; + return mScriptsEnabled; + } + + bool World::getScriptsEnabled() const + { + return mScriptsEnabled; + } + void World::loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ContentLoader& contentLoader) { @@ -2527,11 +2634,11 @@ namespace MWWorld if (!selectedSpell.empty()) { - const ESM::Spell* spell = getStore().get().search(selectedSpell); + const ESM::Spell* spell = getStore().get().find(selectedSpell); // Check mana MWMechanics::DynamicStat magicka = stats.getMagicka(); - if (magicka.getCurrent() < spell->mData.mCost) + if (magicka.getCurrent() < spell->mData.mCost && !(isPlayer && getGodModeState())) { message = "#{sMagicInsufficientSP}"; fail = true; @@ -2614,7 +2721,7 @@ namespace MWWorld if (!selectedSpell.empty()) { - const ESM::Spell* spell = getStore().get().search(selectedSpell); + const ESM::Spell* spell = getStore().get().find(selectedSpell); // A power can be used once per 24h if (spell->mData.mType == ESM::Spell::ST_Power) @@ -2792,6 +2899,9 @@ namespace MWWorld } else if (ptr.getClass().getTypeName() != typeid(ESM::Creature).name()) return false; + + if (ptr.getClass().getCreatureStats(ptr).isDead()) + return false; } if (mType == World::Detect_Key && !ptr.getClass().isKey(ptr)) return false; @@ -2895,18 +3005,10 @@ namespace MWWorld if (!closestChest.isEmpty()) //Found a close chest { - ContainerStore& store = ptr.getClass().getContainerStore(ptr); - for (ContainerStoreIterator it = store.begin(); it != store.end(); ++it) //Move all stolen stuff into chest - { - MWWorld::Ptr dummy; - if (!MWBase::Environment::get().getMechanicsManager()->isAllowedToUse(getPlayerPtr(), *it, dummy)) - { - closestChest.getClass().getContainerStore(closestChest).add(*it, it->getRefData().getCount(), closestChest); - store.remove(*it, it->getRefData().getCount(), ptr); - } - } - closestChest.getClass().lock(closestChest,50); + MWBase::Environment::get().getMechanicsManager()->confiscateStolenItems(ptr, closestChest); } + else + std::cerr << "Failed to confiscate items: no stolen_goods container found" << std::endl; } void World::goToJail() @@ -2934,59 +3036,7 @@ namespace MWWorld MWBase::Environment::get().getWindowManager()->removeGuiMode(MWGui::GM_Dialogue); - MWWorld::Ptr player = getPlayerPtr(); - teleportToClosestMarker(player, "prisonmarker"); - - int days = mDaysInPrison; - advanceTime(days * 24); - for (int i=0; irest (true); - - std::set skills; - for (int day=0; day (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& gmst = getStore().get(); - - 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::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 buttons; - buttons.push_back("#{sOk}"); - MWBase::Environment::get().getWindowManager()->messageBox(message, buttons); + MWBase::Environment::get().getWindowManager()->goToJail(mDaysInPrison); } } @@ -3058,7 +3108,7 @@ namespace MWWorld mRendering->spawnEffect(model, textureOverride, worldPos); } - void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, + void World::explodeSpell(const Vector3 &origin, const ESM::EffectList &effects, const Ptr &caster, int rangeType, const std::string& id, const std::string& sourceName) { std::map > toApply; @@ -3094,7 +3144,6 @@ namespace MWWorld std::vector objects; MWBase::Environment::get().getMechanicsManager()->getObjectsInRange( origin, feetToGameUnits(effectIt->mArea), objects); - for (std::vector::iterator affected = objects.begin(); affected != objects.end(); ++affected) toApply[*affected].push_back(*effectIt); } @@ -3118,7 +3167,7 @@ namespace MWWorld cast.mStack = false; ESM::EffectList effects; effects.mList = apply->second; - cast.inflict(apply->first, caster, effects, ESM::RT_Target, false, true); + cast.inflict(apply->first, caster, effects, (ESM::RangeType)rangeType, false, true); } } @@ -3131,12 +3180,17 @@ namespace MWWorld breakInvisibility(actor); - if (!script.empty()) + if (mScriptsEnabled) { - getLocalScripts().setIgnore (object); - MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + if (!script.empty()) + { + getLocalScripts().setIgnore (object); + MWBase::Environment::get().getScriptManager()->run (script, interpreterContext); + } + if (!interpreterContext.hasActivationBeenHandled()) + interpreterContext.executeActivation(object, actor); } - if (!interpreterContext.hasActivationBeenHandled()) + else interpreterContext.executeActivation(object, actor); } @@ -3146,7 +3200,7 @@ namespace MWWorld { // Can't reset actors that were moved to a different cell, because we don't know what cell they came from. // This could be fixed once we properly track actor cell changes, but may not be desirable behaviour anyhow. - if (ptr.getClass().isActor() && ptr.getCellRef().getRefNum().mContentFile != -1) + if (ptr.getClass().isActor() && ptr.getCellRef().hasContentFile()) { const ESM::Position& origPos = ptr.getCellRef().getPosition(); MWBase::Environment::get().getWorld()->moveObject(ptr, origPos.pos[0], origPos.pos[1], origPos.pos[2]); diff --git a/apps/openmw/mwworld/worldimp.hpp b/apps/openmw/mwworld/worldimp.hpp index 2da6a6e05f..9980f6f906 100644 --- a/apps/openmw/mwworld/worldimp.hpp +++ b/apps/openmw/mwworld/worldimp.hpp @@ -82,6 +82,7 @@ namespace MWWorld boost::shared_ptr mProjectileManager; bool mGodMode; + bool mScriptsEnabled; std::vector mContentFiles; // not implemented @@ -104,11 +105,12 @@ namespace MWWorld void rotateObjectImp (const Ptr& ptr, Ogre::Vector3 rot, bool adjust); - bool moveObjectImp (const Ptr& ptr, float x, float y, float z); - ///< @return true if the active cell (cell player is in) changed + Ptr moveObjectImp (const Ptr& ptr, float x, float y, float z); + ///< @return an updated Ptr in case the Ptr's cell changes Ptr copyObjectToCell(const Ptr &ptr, CellStore* cell, ESM::Position pos, bool adjustPos=true); + void updateSoundListener(); void updateWindowManager (); void performUpdateSceneQueries (); void getFacedHandle(std::string& facedHandle, float maxDistance, bool ignorePlayer=true); @@ -138,6 +140,10 @@ namespace MWWorld void loadContentFiles(const Files::Collections& fileCollections, const std::vector& content, ContentLoader& contentLoader); + float mSwimHeightScale; + bool isUnderwater(const MWWorld::Ptr &object, const float heightRatio) const; + ///< helper function for implementing isSwimming(), isSubmerged(), isWading() + bool mTeleportEnabled; bool mLevitationEnabled; bool mGoToJail; @@ -162,10 +168,11 @@ namespace MWWorld virtual void clear(); virtual int countSavedGameRecords() const; + virtual int countSavedGameCells() const; virtual void write (ESM::ESMWriter& writer, Loading::Listener& progress) const; - virtual void readRecord (ESM::ESMReader& reader, int32_t type, + virtual void readRecord (ESM::ESMReader& reader, uint32_t type, const std::map& contentFileMap); virtual CellStore *getExterior (int x, int y); @@ -340,7 +347,8 @@ namespace MWWorld virtual void deleteObject (const Ptr& ptr); virtual void undeleteObject (const Ptr& ptr); - virtual void moveObject (const Ptr& ptr, float x, float y, float z); + virtual MWWorld::Ptr moveObject (const Ptr& ptr, float x, float y, float z); + ///< @return an updated Ptr in case the Ptr's cell changes virtual void moveObject (const Ptr& ptr, CellStore* newCell, float x, float y, float z); virtual void scaleObject (const Ptr& ptr, float scale); @@ -418,6 +426,14 @@ namespace MWWorld ///< Create a new record (of type book) in the ESM store. /// \return pointer to created record + virtual const ESM::CreatureLevList *createOverrideRecord (const ESM::CreatureLevList& record); + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + + virtual const ESM::ItemLevList *createOverrideRecord (const ESM::ItemLevList& record); + ///< Write this record to the ESM store, allowing it to override a pre-existing record with the same ID. + /// \return pointer to created record + virtual void update (float duration, bool paused); virtual MWWorld::Ptr placeObject (const MWWorld::Ptr& object, float cursorX, float cursorY, int amount); @@ -444,12 +460,17 @@ namespace MWWorld virtual bool isSubmerged(const MWWorld::Ptr &object) const; virtual bool isSwimming(const MWWorld::Ptr &object) const; virtual bool isUnderwater(const MWWorld::CellStore* cell, const Ogre::Vector3 &pos) const; + virtual bool isWading(const MWWorld::Ptr &object) const; virtual bool isOnGround(const MWWorld::Ptr &ptr) const; virtual void togglePOV() { mRendering->togglePOV(); } + virtual bool isFirstPerson() const { + return mRendering->getCamera()->isFirstPerson(); + } + virtual void togglePreviewMode(bool enable) { mRendering->togglePreviewMode(enable); } @@ -518,6 +539,7 @@ namespace MWWorld /// \todo Probably shouldn't be here virtual MWRender::Animation* getAnimation(const MWWorld::Ptr &ptr); + virtual void reattachPlayerCamera(); /// \todo this does not belong here virtual void frameStarted (float dt, bool paused); @@ -551,6 +573,9 @@ namespace MWWorld virtual bool toggleGodMode(); + virtual bool toggleScripts(); + virtual bool getScriptsEnabled() const; + /** * @brief startSpellCast attempt to start casting a spell. Might fail immediately if conditions are not met. * @param actor @@ -608,7 +633,7 @@ namespace MWWorld virtual void spawnEffect (const std::string& model, const std::string& textureOverride, const Ogre::Vector3& worldPos); virtual void explodeSpell (const Ogre::Vector3& origin, const ESM::EffectList& effects, - const MWWorld::Ptr& caster, const std::string& id, const std::string& sourceName); + const MWWorld::Ptr& caster, int rangeType, const std::string& id, const std::string& sourceName); virtual void activate (const MWWorld::Ptr& object, const MWWorld::Ptr& actor); diff --git a/apps/openmw_test_suite/CMakeLists.txt b/apps/openmw_test_suite/CMakeLists.txt index 9fe7890acc..2ffb7ffa0e 100644 --- a/apps/openmw_test_suite/CMakeLists.txt +++ b/apps/openmw_test_suite/CMakeLists.txt @@ -1,24 +1,18 @@ -# TODO: This should not be needed, check how it was done in FindGTEST -set(GMOCK_ROOT "/usr/include") -set(GMOCK_BUILD "/usr/lib") - find_package(GTest REQUIRED) -find_package(GMock REQUIRED) - -if (GTEST_FOUND AND GMOCK_FOUND) +if (GTEST_FOUND) include_directories(${GTEST_INCLUDE_DIRS}) - include_directories(${GMOCK_INCLUDE_DIRS}) file(GLOB UNITTEST_SRC_FILES components/misc/test_*.cpp + mwdialogue/test_*.cpp ) source_group(apps\\openmw_test_suite FILES openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) add_executable(openmw_test_suite openmw_test_suite.cpp ${UNITTEST_SRC_FILES}) - target_link_libraries(openmw_test_suite ${GMOCK_BOTH_LIBRARIES} ${GTEST_BOTH_LIBRARIES} components) + target_link_libraries(openmw_test_suite ${GTEST_BOTH_LIBRARIES} components) # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(openmw_test_suite ${CMAKE_THREAD_LIBS_INIT}) diff --git a/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp new file mode 100644 index 0000000000..e0e1871d2c --- /dev/null +++ b/apps/openmw_test_suite/mwdialogue/test_keywordsearch.cpp @@ -0,0 +1,67 @@ +#include +#include "apps/openmw/mwdialogue/keywordsearch.hpp" + +struct KeywordSearchTest : public ::testing::Test +{ + protected: + virtual void SetUp() + { + } + + virtual void TearDown() + { + } +}; + +TEST_F(KeywordSearchTest, keyword_test_conflict_resolution) +{ + // test to make sure the longest keyword in a chain of conflicting keywords gets chosen + MWDialogue::KeywordSearch search; + search.seed("foo bar", 0); + search.seed("bar lock", 0); + search.seed("lock switch", 0); + + std::string text = "foo bar lock switch"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + // Should contain: "foo bar", "lock switch" + ASSERT_TRUE (matches.size() == 2); + ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "foo bar"); + ASSERT_TRUE (std::string(matches.rbegin()->mBeg, matches.rbegin()->mEnd) == "lock switch"); +} + +TEST_F(KeywordSearchTest, keyword_test_conflict_resolution2) +{ + MWDialogue::KeywordSearch search; + search.seed("the dwemer", 0); + search.seed("dwemer language", 0); + + std::string text = "the dwemer language"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + ASSERT_TRUE (matches.size() == 1); + ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "dwemer language"); +} + + +TEST_F(KeywordSearchTest, keyword_test_conflict_resolution3) +{ + // testing that the longest keyword is chosen, rather than maximizing the + // amount of highlighted characters by highlighting the first and last keyword + MWDialogue::KeywordSearch search; + search.seed("foo bar", 0); + search.seed("bar lock", 0); + search.seed("lock so", 0); + + std::string text = "foo bar lock so"; + + std::vector::Match> matches; + search.highlightKeywords(text.begin(), text.end(), matches); + + ASSERT_TRUE (matches.size() == 1); + ASSERT_TRUE (std::string(matches.front().mBeg, matches.front().mEnd) == "bar lock"); +} diff --git a/apps/openmw_test_suite/openmw_test_suite.cpp b/apps/openmw_test_suite/openmw_test_suite.cpp index 81476325ea..7cc76b25b7 100644 --- a/apps/openmw_test_suite/openmw_test_suite.cpp +++ b/apps/openmw_test_suite/openmw_test_suite.cpp @@ -1,12 +1,6 @@ -#include #include - -int main(int argc, char** argv) { - // The following line causes Google Mock to throw an exception on failure, - // which will be interpreted by your testing framework as a test failure. - ::testing::GTEST_FLAG(throw_on_failure) = false; - ::testing::InitGoogleMock(&argc, argv); - - return RUN_ALL_TESTS(); +GTEST_API_ int main(int argc, char **argv) { + testing::InitGoogleTest(&argc, argv); + return RUN_ALL_TESTS(); } diff --git a/apps/wizard/installationpage.cpp b/apps/wizard/installationpage.cpp index 09e5773173..dc2674680e 100644 --- a/apps/wizard/installationpage.cpp +++ b/apps/wizard/installationpage.cpp @@ -185,7 +185,7 @@ void Wizard::InstallationPage::installationFinished() msgBox.setWindowTitle(tr("Installation finished")); msgBox.setIcon(QMessageBox::Information); msgBox.setStandardButtons(QMessageBox::Ok); - msgBox.setText(tr("Installation completed sucessfully!")); + msgBox.setText(tr("Installation completed successfully!")); msgBox.exec(); diff --git a/apps/wizard/mainwizard.cpp b/apps/wizard/mainwizard.cpp index e68070c4d5..a1370b1253 100644 --- a/apps/wizard/mainwizard.cpp +++ b/apps/wizard/mainwizard.cpp @@ -182,7 +182,7 @@ void Wizard::MainWizard::setupGameSettings() void Wizard::MainWizard::setupLauncherSettings() { QString path(QString::fromUtf8(mCfgMgr.getUserConfigPath().string().c_str())); - path.append(QLatin1String("launcher.cfg")); + path.append(QLatin1String(Config::LauncherSettings::sLauncherConfigFileName)); QString message(tr("

Could not open %1 for reading

\

Please make sure you have the right permissions \ @@ -281,7 +281,7 @@ void Wizard::MainWizard::runSettingsImporter() arguments.append(QLatin1String("--cfg")); arguments.append(userPath + QLatin1String("openmw.cfg")); - if (!mImporterInvoker->startProcess(QLatin1String("mwiniimport"), arguments, false)) + if (!mImporterInvoker->startProcess(QLatin1String("openmw-iniimporter"), arguments, false)) return qApp->quit(); } @@ -427,7 +427,7 @@ void Wizard::MainWizard::writeSettings() file.close(); // Launcher settings - file.setFileName(userPath + QLatin1String("launcher.cfg")); + file.setFileName(userPath + QLatin1String(Config::LauncherSettings::sLauncherConfigFileName)); if (!file.open(QIODevice::ReadWrite | QIODevice::Text | QIODevice::Truncate)) { // File cannot be opened or created diff --git a/apps/wizard/unshield/unshieldworker.cpp b/apps/wizard/unshield/unshieldworker.cpp index 5fa0433476..11e090ed18 100644 --- a/apps/wizard/unshield/unshieldworker.cpp +++ b/apps/wizard/unshield/unshieldworker.cpp @@ -739,7 +739,8 @@ bool Wizard::UnshieldWorker::extractFile(Unshield *unshield, const QString &dest // Ensure the target path exists QDir dir; - dir.mkpath(path); + if (!dir.mkpath(path)) + return false; QString fileName(path); fileName.append(QString::fromUtf8(unshield_file_name(unshield, index))); diff --git a/cmake/FindGMock.cmake b/cmake/FindGMock.cmake deleted file mode 100644 index eda7d4d72f..0000000000 --- a/cmake/FindGMock.cmake +++ /dev/null @@ -1,91 +0,0 @@ -# Locate the Google C++ Mocking Framework. -# -# Defines the following variables: -# -# GMOCK_FOUND - Found the Google Mocking framework -# GMOCK_INCLUDE_DIRS - Include directories -# -# Also defines the library variables below as normal -# variables. These contain debug/optimized keywords when -# a debugging library is found. -# -# GMOCK_BOTH_LIBRARIES - Both libgmock & libgmock-main -# GMOCK_LIBRARIES - libgmock -# GMOCK_MAIN_LIBRARIES - libgmock-main -# -# Accepts the following variables as input: -# -# GMOCK_ROOT - (as CMake or env. variable) -# The root directory of the gmock install prefix -# -#----------------------- -# Example Usage: -# -# enable_testing(true) -# find_package(GMock REQUIRED) -# include_directories(${GMOCK_INCLUDE_DIRS}) -# -# add_executable(foo foo.cc) -# target_link_libraries(foo ${GMOCK_BOTH_LIBRARIES}) -# -# add_test(AllTestsInFoo foo) -# - -#set (GMOCK_FOUND FALSE) - - -#set (GMOCK_ROOT $ENV{GMOCK_ROOT} CACHE PATH "Path to the gmock root directory.") -if (NOT EXISTS ${GMOCK_ROOT}) - message (FATAL_ERROR "GMOCK_ROOT does not exist.") -endif () - -#set (GMOCK_BUILD ${GMOCK_ROOT}/build CACHE PATH "Path to the gmock build directory.") -if (NOT EXISTS ${GMOCK_BUILD}) - message (FATAL_ERROR "GMOCK_BUILD does not exist.") -endif () - -# Find the include directory -find_path(GMOCK_INCLUDE_DIRS gmock/gmock.h - HINTS - $ENV{GMOCK_ROOT}/include - ${GMOCK_ROOT}/include -) -mark_as_advanced(GMOCK_INCLUDE_DIRS) - -function(_gmock_find_library _name) - find_library(${_name} - NAMES ${ARGN} - HINTS - $ENV{GMOCK_BUILD} - ${GMOCK_BUILD} - ) - mark_as_advanced(${_name}) -endfunction() - -# Find the gmock libraries -if (MSVC) - _gmock_find_library (GMOCK_LIBRARIES_DEBUG gmock ${GMOCK_BUILD}/Debug) - _gmock_find_library (GMOCK_LIBRARIES_RELEASE gmock ${GMOCK_BUILD}/Release) - _gmock_find_library (GMOCK_MAIN_LIBRARIES_DEBUG gmock_main ${GMOCK_BUILD}/Debug) - _gmock_find_library (GMOCK_MAIN_LIBRARIES_RELEASE gmock_main ${GMOCK_BUILD}/Release) - set (GMOCK_LIBRARIES - debug ${GMOCK_LIBRARIES_DEBUG} - optimized ${GMOCK_LIBRARIES_RELEASE} - ) - set (GMOCK_MAIN_LIBRARIES - debug ${GMOCK_MAIN_LIBRARIES_DEBUG} - optimized ${GMOCK_MAIN_LIBRARIES_RELEASE} - ) -else () - _gmock_find_library (GMOCK_LIBRARIES gmock ${GMOCK_BUILD}) - _gmock_find_library (GMOCK_MAIN_LIBRARIES gmock_main ${GMOCK_BUILD} ${GMOCK_BUILD}/Debug) -endif () - -FIND_PACKAGE_HANDLE_STANDARD_ARGS(GMock DEFAULT_MSG GMOCK_LIBRARIES GMOCK_INCLUDE_DIRS GMOCK_MAIN_LIBRARIES) - -if(GMOCK_FOUND) - set(GMOCK_INCLUDE_DIRS ${GMOCK_INCLUDE_DIR}) - set(GMOCK_BOTH_LIBRARIES ${GMOCK_LIBRARIES} ${GMOCK_MAIN_LIBRARIES}) -endif() - - diff --git a/cmake/GetGitRevisionDescription.cmake b/cmake/GetGitRevisionDescription.cmake deleted file mode 100644 index 56ff1d5459..0000000000 --- a/cmake/GetGitRevisionDescription.cmake +++ /dev/null @@ -1,154 +0,0 @@ -# - 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( [ ...]) -# -# Returns the refspec and sha hash of the current head revision -# -# 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( [ ...]) -# -# 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 -# 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 deleted file mode 100644 index 888ce13aab..0000000000 --- a/cmake/GetGitRevisionDescription.cmake.in +++ /dev/null @@ -1,38 +0,0 @@ -# -# Internal file for GetGitRevisionDescription.cmake -# -# Requires CMake 2.6 or newer (uses the 'function' command) -# -# Original Author: -# 2009-2010 Ryan Pavlik -# 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/cmake/GitVersion.cmake b/cmake/GitVersion.cmake new file mode 100644 index 0000000000..0087461a12 --- /dev/null +++ b/cmake/GitVersion.cmake @@ -0,0 +1,24 @@ +execute_process ( + COMMAND ${GIT_EXECUTABLE} rev-list --tags --max-count=1 + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE EXITCODE1 + OUTPUT_VARIABLE TAGHASH + OUTPUT_STRIP_TRAILING_WHITESPACE) + +execute_process ( + COMMAND ${GIT_EXECUTABLE} rev-parse HEAD + WORKING_DIRECTORY ${PROJECT_SOURCE_DIR} + RESULT_VARIABLE EXITCODE2 + OUTPUT_VARIABLE COMMITHASH + OUTPUT_STRIP_TRAILING_WHITESPACE) + +string (COMPARE EQUAL "${EXITCODE1}:${EXITCODE2}" "0:0" SUCCESS) +if (SUCCESS) + set(OPENMW_VERSION_COMMITHASH "${COMMITHASH}") + set(OPENMW_VERSION_TAGHASH "${TAGHASH}") + message(STATUS "OpenMW version ${OPENMW_VERSION}") +else (SUCCESS) + message(WARNING "Failed to get valid version information from Git") +endif (SUCCESS) + +configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) diff --git a/components/CMakeLists.txt b/components/CMakeLists.txt index 234325718d..a49e54dd3e 100644 --- a/components/CMakeLists.txt +++ b/components/CMakeLists.txt @@ -1,8 +1,24 @@ 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") +set (VERSION_HPP_IN ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp.cmake) +set (VERSION_HPP ${CMAKE_CURRENT_SOURCE_DIR}/version/version.hpp) +if (GIT_CHECKOUT) + add_custom_target (git-version + COMMAND ${CMAKE_COMMAND} + -DGIT_EXECUTABLE=${GIT_EXECUTABLE} + -DPROJECT_SOURCE_DIR=${PROJECT_SOURCE_DIR} + -DVERSION_HPP_IN=${VERSION_HPP_IN} + -DVERSION_HPP=${VERSION_HPP} + -DOPENMW_VERSION_MAJOR=${OPENMW_VERSION_MAJOR} + -DOPENMW_VERSION_MINOR=${OPENMW_VERSION_MINOR} + -DOPENMW_VERSION_RELEASE=${OPENMW_VERSION_RELEASE} + -DOPENMW_VERSION=${OPENMW_VERSION} + -P ${CMAKE_CURRENT_SOURCE_DIR}/../cmake/GitVersion.cmake + VERBATIM) +else (GIT_CHECKOUT) + configure_file(${VERSION_HPP_IN} ${VERSION_HPP}) +endif (GIT_CHECKOUT) # source files @@ -40,13 +56,13 @@ add_component_dir (to_utf8 add_component_dir (esm attr defs esmcommon esmreader esmwriter loadacti loadalch loadappa loadarmo loadbody loadbook loadbsgn loadcell - loadclas loadclot loadcont loadcrea loadcrec loaddial loaddoor loadench loadfact loadglob loadgmst - loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc loadnpcc + loadclas loadclot loadcont loadcrea loaddial loaddoor loadench loadfact loadglob loadgmst + loadinfo loadingr loadland loadlevlist loadligh loadlock loadprob loadrepa loadltex loadmgef loadmisc 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 + savedgame journalentry queststate locals globalscript player objectstate cellid cellstate globalmap inventorystate containerstate npcstate creaturestate dialoguestate statstate npcstats creaturestats weatherstate quickkeys fogstate spellstate activespells creaturelevliststate doorstate projectilestate debugprofile - aisequence magiceffects + aisequence magiceffects util custommarkerstate stolenitems ) add_component_dir (esmterrain @@ -70,7 +86,7 @@ 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 declarationparser - quickfileparser discardparser + quickfileparser discardparser junkparser ) add_component_dir (interpreter @@ -96,7 +112,7 @@ add_component_dir (ogreinit ) add_component_dir (widgets - box imagebutton tags list numericeditbox sharedstatebutton widgets + box imagebutton tags list numericeditbox sharedstatebutton windowcaption widgets ) add_component_dir (fontloader @@ -116,6 +132,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) add_component_qt_dir (contentselector model/modelitem model/esmfile model/naturalsort model/contentmodel + model/loadordererror view/combobox view/contentselector ) add_component_qt_dir (config @@ -123,7 +140,7 @@ if(QT_QTGUI_LIBRARY AND QT_QTCORE_LIBRARY) launchersettings settingsbase ) - + add_component_qt_dir (process processinvoker ) @@ -145,6 +162,10 @@ add_library(components STATIC ${COMPONENT_FILES} ${MOC_SRCS} ${ESM_UI_HDR}) target_link_libraries(components ${Boost_LIBRARIES} ${OGRE_LIBRARIES}) +if (GIT_CHECKOUT) + add_dependencies (components git-version) +endif (GIT_CHECKOUT) + # Fix for not visible pthreads functions for linker with glibc 2.15 if (UNIX AND NOT APPLE) target_link_libraries(components ${CMAKE_THREAD_LIBS_INIT}) diff --git a/components/compiler/exprparser.cpp b/components/compiler/exprparser.cpp index 6dcca08df9..7eaca5c02a 100644 --- a/components/compiler/exprparser.cpp +++ b/components/compiler/exprparser.cpp @@ -17,6 +17,7 @@ #include "extensions.hpp" #include "context.hpp" #include "discardparser.hpp" +#include "junkparser.hpp" namespace Compiler { @@ -660,7 +661,7 @@ namespace Compiler else { // no comma was used between arguments - scanner.putbackKeyword (code, loc); + scanner.putbackSpecial (code, loc); return false; } } @@ -752,7 +753,7 @@ namespace Compiler } int ExprParser::parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code) + std::vector& code, int ignoreKeyword) { bool optional = false; int optionalCount = 0; @@ -760,6 +761,7 @@ namespace Compiler ExprParser parser (getErrorHandler(), getContext(), mLocals, mLiterals, true); StringParser stringParser (getErrorHandler(), getContext(), mLiterals); DiscardParser discardParser (getErrorHandler(), getContext()); + JunkParser junkParser (getErrorHandler(), getContext(), ignoreKeyword); std::stack > stack; @@ -815,6 +817,13 @@ namespace Compiler if (discardParser.isEmpty()) break; } + else if (*iter=='j') + { + /// \todo disable this when operating in strict mode + junkParser.reset(); + + scanner.scan (junkParser); + } else { parser.reset(); diff --git a/components/compiler/exprparser.hpp b/components/compiler/exprparser.hpp index e4e385ff0f..639ca65aab 100644 --- a/components/compiler/exprparser.hpp +++ b/components/compiler/exprparser.hpp @@ -96,11 +96,12 @@ namespace Compiler /// \return Type ('l': integer, 'f': float) int parseArguments (const std::string& arguments, Scanner& scanner, - std::vector& code); + std::vector& code, int ignoreKeyword = -1); ///< Parse sequence of arguments specified by \a arguments. /// \param arguments Uses ScriptArgs typedef /// \see Compiler::ScriptArgs /// \param invert Store arguments in reverted order. + /// \param ignoreKeyword A keyword that is seen as junk /// \return number of optional arguments }; } diff --git a/components/compiler/extensions.hpp b/components/compiler/extensions.hpp index a15218d99f..9fb9bdb95a 100644 --- a/components/compiler/extensions.hpp +++ b/components/compiler/extensions.hpp @@ -23,6 +23,7 @@ namespace Compiler x - Optional, ignored string argument X - Optional, ignored numeric expression z - Optional, ignored string or numeric argument + j - A piece of junk (either . or a specific keyword) **/ typedef std::string ScriptArgs; diff --git a/components/compiler/extensions0.cpp b/components/compiler/extensions0.cpp index 7531cdd5bf..c56ee2ffb5 100644 --- a/components/compiler/extensions0.cpp +++ b/components/compiler/extensions0.cpp @@ -116,7 +116,7 @@ namespace Compiler extensions.registerInstruction ("additem", "clX", opcodeAddItem, opcodeAddItemExplicit); extensions.registerFunction ("getitemcount", 'l', "c", opcodeGetItemCount, opcodeGetItemCountExplicit); - extensions.registerInstruction ("removeitem", "cl", opcodeRemoveItem, + extensions.registerInstruction ("removeitem", "clX", opcodeRemoveItem, opcodeRemoveItemExplicit); extensions.registerInstruction ("equip", "cX", opcodeEquip, opcodeEquipExplicit); extensions.registerFunction ("getarmortype", 'l', "l", opcodeGetArmorType, opcodeGetArmorTypeExplicit); @@ -179,7 +179,7 @@ namespace Compiler extensions.registerInstruction ("setjournalindex", "cl", opcodeSetJournalIndex); extensions.registerFunction ("getjournalindex", 'l', "c", opcodeGetJournalIndex); extensions.registerInstruction ("addtopic", "S" , opcodeAddTopic); - extensions.registerInstruction ("choice", "/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); + extensions.registerInstruction ("choice", "j/SlSlSlSlSlSlSlSlSlSlSlSlSlSlSlSl", opcodeChoice); extensions.registerInstruction("forcegreeting","",opcodeForceGreeting, opcodeForceGreetingExplicit); extensions.registerInstruction("goodbye", "", opcodeGoodbye); @@ -192,6 +192,7 @@ namespace Compiler extensions.registerFunction("samefaction", 'l', "", opcodeSameFaction, opcodeSameFactionExplicit); extensions.registerInstruction("modfactionreaction", "ccl", opcodeModFactionReaction); + extensions.registerInstruction("setfactionreaction", "ccl", opcodeSetFactionReaction); extensions.registerFunction("getfactionreaction", 'l', "ccX", opcodeGetFactionReaction); extensions.registerInstruction("clearinfoactor", "", opcodeClearInfoActor, opcodeClearInfoActorExplicit); } @@ -214,7 +215,7 @@ namespace Compiler extensions.registerInstruction ("enablestatsmenu", "", opcodeEnableStatsMenu); extensions.registerInstruction ("enablerest", "", opcodeEnableRest); - extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableRest); + extensions.registerInstruction ("enablelevelupmenu", "", opcodeEnableLevelupMenu); extensions.registerInstruction ("showrestmenu", "", opcodeShowRestMenu, opcodeShowRestMenuExplicit); @@ -261,6 +262,9 @@ namespace Compiler extensions.registerInstruction ("togglepathgrid", "", opcodeTogglePathgrid); extensions.registerInstruction ("tpg", "", opcodeTogglePathgrid); extensions.registerInstruction ("dontsaveobject", "", opcodeDontSaveObject); + extensions.registerInstruction ("pcforce1stperson", "", opcodePcForce1stPerson); + extensions.registerInstruction ("pcforce3rdperson", "", opcodePcForce3rdPerson); + extensions.registerFunction ("pcget3rdperson", 'l', "", opcodePcGet3rdPerson); extensions.registerInstruction ("togglevanitymode", "", opcodeToggleVanityMode); extensions.registerInstruction ("tvm", "", opcodeToggleVanityMode); extensions.registerFunction ("getpcsleep", 'l', "", opcodeGetPcSleep); @@ -292,18 +296,24 @@ namespace Compiler extensions.registerInstruction ("hurtcollidingactor", "f", opcodeHurtCollidingActor, opcodeHurtCollidingActorExplicit); extensions.registerFunction ("getwindspeed", 'f', "", opcodeGetWindSpeed); extensions.registerFunction ("hitonme", 'l', "S", opcodeHitOnMe, opcodeHitOnMeExplicit); + extensions.registerFunction ("hitattemptonme", 'l', "S", opcodeHitAttemptOnMe, opcodeHitAttemptOnMeExplicit); extensions.registerInstruction ("disableteleporting", "", opcodeDisableTeleporting); extensions.registerInstruction ("enableteleporting", "", opcodeEnableTeleporting); extensions.registerInstruction ("showvars", "", opcodeShowVars, opcodeShowVarsExplicit); extensions.registerInstruction ("sv", "", opcodeShowVars, opcodeShowVarsExplicit); extensions.registerInstruction("tgm", "", opcodeToggleGodMode); extensions.registerInstruction("togglegodmode", "", opcodeToggleGodMode); + extensions.registerInstruction("togglescripts", "", opcodeToggleScripts); extensions.registerInstruction ("disablelevitation", "", opcodeDisableLevitation); extensions.registerInstruction ("enablelevitation", "", opcodeEnableLevitation); extensions.registerFunction ("getpcinjail", 'l', "", opcodeGetPcInJail); extensions.registerFunction ("getpctraveling", 'l', "", opcodeGetPcTraveling); extensions.registerInstruction ("betacomment", "S", opcodeBetaComment, opcodeBetaCommentExplicit); extensions.registerInstruction ("bc", "S", opcodeBetaComment, opcodeBetaCommentExplicit); + extensions.registerInstruction ("addtolevcreature", "ccl", opcodeAddToLevCreature); + extensions.registerInstruction ("removefromlevcreature", "ccl", opcodeRemoveFromLevCreature); + extensions.registerInstruction ("addtolevitem", "ccl", opcodeAddToLevItem); + extensions.registerInstruction ("removefromlevitem", "ccl", opcodeRemoveFromLevItem); } } diff --git a/components/compiler/fileparser.cpp b/components/compiler/fileparser.cpp index 37ad1f2581..423841ac3e 100644 --- a/components/compiler/fileparser.cpp +++ b/components/compiler/fileparser.cpp @@ -112,6 +112,7 @@ namespace Compiler scanner.scan (mScriptParser); mState = EndNameState; + scanner.allowNameStartingwithDigit(); return true; } diff --git a/components/compiler/generator.cpp b/components/compiler/generator.cpp index 2efa2477e5..ead0c72901 100644 --- a/components/compiler/generator.cpp +++ b/components/compiler/generator.cpp @@ -150,10 +150,13 @@ namespace code.push_back (Compiler::Generator::segment0 (2, offset)); } + /* + Currently unused void opSkipOnZero (Compiler::Generator::CodeContainer& code) { code.push_back (Compiler::Generator::segment5 (24)); } + */ void opSkipOnNonZero (Compiler::Generator::CodeContainer& code) { diff --git a/components/compiler/junkparser.cpp b/components/compiler/junkparser.cpp new file mode 100644 index 0000000000..cfa94044e7 --- /dev/null +++ b/components/compiler/junkparser.cpp @@ -0,0 +1,48 @@ + +#include "junkparser.hpp" + +#include "scanner.hpp" + +Compiler::JunkParser::JunkParser (ErrorHandler& errorHandler, const Context& context, + int ignoreKeyword) +: Parser (errorHandler, context), mIgnoreKeyword (ignoreKeyword) +{} + +bool Compiler::JunkParser::parseInt (int value, const TokenLoc& loc, Scanner& scanner) +{ + scanner.putbackInt (value, loc); + return false; +} + +bool Compiler::JunkParser::parseFloat (float value, const TokenLoc& loc, Scanner& scanner) +{ + scanner.putbackFloat (value, loc); + return false; +} + +bool Compiler::JunkParser::parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner) +{ + scanner.putbackName (name, loc); + return false; +} + +bool Compiler::JunkParser::parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner) +{ + if (keyword==mIgnoreKeyword) + reportWarning ("found junk (ignoring it)", loc); + else + scanner.putbackKeyword (keyword, loc); + + return false; +} + +bool Compiler::JunkParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) +{ + if (code==Scanner::S_member) + reportWarning ("found junk (ignoring it)", loc); + else + scanner.putbackSpecial (code, loc); + + return false; +} diff --git a/components/compiler/junkparser.hpp b/components/compiler/junkparser.hpp new file mode 100644 index 0000000000..6dfbd97af4 --- /dev/null +++ b/components/compiler/junkparser.hpp @@ -0,0 +1,41 @@ +#ifndef COMPILER_JUNKPARSER_H_INCLUDED +#define COMPILER_JUNKPARSER_H_INCLUDED + +#include "parser.hpp" + +namespace Compiler +{ + /// \brief Parse an optional single junk token + class JunkParser : public Parser + { + int mIgnoreKeyword; + + public: + + JunkParser (ErrorHandler& errorHandler, const Context& context, + int ignoreKeyword = -1); + + virtual bool parseInt (int value, const TokenLoc& loc, Scanner& scanner); + ///< Handle an int token. + /// \return fetch another token? + + virtual bool parseFloat (float value, const TokenLoc& loc, Scanner& scanner); + ///< Handle a float token. + /// \return fetch another token? + + virtual bool parseName (const std::string& name, const TokenLoc& loc, + Scanner& scanner); + ///< Handle a name token. + /// \return fetch another token? + + virtual bool parseKeyword (int keyword, const TokenLoc& loc, Scanner& scanner); + ///< Handle a keyword token. + /// \return fetch another token? + + virtual bool parseSpecial (int code, const TokenLoc& loc, Scanner& scanner); + ///< Handle a special character token. + /// \return fetch another token? + }; +} + +#endif diff --git a/components/compiler/lineparser.cpp b/components/compiler/lineparser.cpp index dc19b9a4b0..88b5f5ddb3 100644 --- a/components/compiler/lineparser.cpp +++ b/components/compiler/lineparser.cpp @@ -304,7 +304,7 @@ namespace Compiler errorDowngrade.reset (new ErrorDowngrade (getErrorHandler())); std::vector code; - int optionals = mExprParser.parseArguments (argumentType, scanner, code); + int optionals = mExprParser.parseArguments (argumentType, scanner, code, keyword); mCode.insert (mCode.end(), code.begin(), code.end()); extensions->generateInstructionCode (keyword, mCode, mLiterals, mExplicit, optionals); @@ -489,6 +489,13 @@ namespace Compiler bool LineParser::parseSpecial (int code, const TokenLoc& loc, Scanner& scanner) { + if (mState==EndState && code==Scanner::S_open) + { + getErrorHandler().warning ("stray '[' or '(' at the end of the line (ignoring it)", + loc); + return true; + } + if (code==Scanner::S_newline && (mState==EndState || mState==BeginState || mState==PotentialEndState)) return false; diff --git a/components/compiler/opcodes.hpp b/components/compiler/opcodes.hpp index 5063397e11..a4aab8aa1b 100644 --- a/components/compiler/opcodes.hpp +++ b/components/compiler/opcodes.hpp @@ -167,6 +167,7 @@ namespace Compiler const int opcodeSameFaction = 0x20001b5; const int opcodeSameFactionExplicit = 0x20001b6; const int opcodeModFactionReaction = 0x2000242; + const int opcodeSetFactionReaction = 0x20002ff; const int opcodeGetFactionReaction = 0x2000243; const int opcodeClearInfoActor = 0x2000245; const int opcodeClearInfoActorExplicit = 0x2000246; @@ -184,6 +185,7 @@ namespace Compiler const int opcodeEnableMapMenu = 0x2000015; const int opcodeEnableStatsMenu = 0x2000016; const int opcodeEnableRest = 0x2000017; + const int opcodeEnableLevelupMenu = 0x2000300; const int opcodeShowRestMenu = 0x2000018; const int opcodeShowRestMenuExplicit = 0x2000234; const int opcodeGetButtonPressed = 0x2000137; @@ -215,6 +217,9 @@ namespace Compiler const int opcodeToggleWorld = 0x20002f5; const int opcodeTogglePathgrid = 0x2000146; const int opcodeDontSaveObject = 0x2000153; + const int opcodePcForce1stPerson = 0x20002f6; + const int opcodePcForce3rdPerson = 0x20002f7; + const int opcodePcGet3rdPerson = 0x20002f8; const int opcodeToggleVanityMode = 0x2000174; const int opcodeGetPcSleep = 0x200019f; const int opcodeGetPcJumping = 0x2000233; @@ -266,11 +271,14 @@ namespace Compiler const int opcodePayFineThief = 0x2000237; const int opcodeHitOnMe = 0x2000213; const int opcodeHitOnMeExplicit = 0x2000214; + const int opcodeHitAttemptOnMe = 0x20002f9; + const int opcodeHitAttemptOnMeExplicit = 0x20002fa; const int opcodeDisableTeleporting = 0x2000215; const int opcodeEnableTeleporting = 0x2000216; const int opcodeShowVars = 0x200021d; const int opcodeShowVarsExplicit = 0x200021e; const int opcodeToggleGodMode = 0x200021f; + const int opcodeToggleScripts = 0x2000301; const int opcodeDisableLevitation = 0x2000220; const int opcodeEnableLevitation = 0x2000221; const int opcodeCast = 0x2000227; @@ -279,6 +287,10 @@ namespace Compiler const int opcodeExplodeSpellExplicit = 0x200022a; const int opcodeGetPcInJail = 0x200023e; const int opcodeGetPcTraveling = 0x200023f; + const int opcodeAddToLevCreature = 0x20002fb; + const int opcodeRemoveFromLevCreature = 0x20002fc; + const int opcodeAddToLevItem = 0x20002fd; + const int opcodeRemoveFromLevItem = 0x20002fe; } namespace Sky diff --git a/components/compiler/scanner.cpp b/components/compiler/scanner.cpp index 16d54ff51d..83d4359621 100644 --- a/components/compiler/scanner.cpp +++ b/components/compiler/scanner.cpp @@ -270,8 +270,9 @@ namespace Compiler bool Scanner::scanName (char c, Parser& parser, bool& cont) { std::string name; + name += c; - if (!scanName (c, name)) + if (!scanName (name)) return false; TokenLoc loc (mLoc); @@ -312,15 +313,11 @@ namespace Compiler return true; } - bool Scanner::scanName (char c, std::string& name) + bool Scanner::scanName (std::string& name) { - bool first = false; + char c; bool error = false; - name.clear(); - - putback (c); - while (get (c)) { if (!name.empty() && name[0]=='"') @@ -335,12 +332,14 @@ namespace Compiler // { // if (!get (c)) // { +// error = true; // mErrorHandler.error ("incomplete escape sequence", mLoc); // break; // } // } else if (c=='\n') { + error = true; mErrorHandler.error ("incomplete string or name", mLoc); break; } @@ -352,13 +351,9 @@ namespace Compiler putback (c); break; } - - if (first && (std::isdigit (c) || c=='`' || c=='-')) - error = true; } name += c; - first = false; } return !error; @@ -391,14 +386,17 @@ namespace Compiler { if (get (c)) { - if (c=='=') + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ' && !get (c)) + special = S_cmpEQ; + else if (c=='=') special = S_cmpEQ; else { special = S_cmpEQ; putback (c); // return false; -// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. +/// Allow = as synonym for ==. \todo optionally disable for post-1.0 scripting improvements. } } else @@ -411,6 +409,10 @@ namespace Compiler { if (get (c)) { + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ' && !get (c)) + return false; + if (c=='=') special = S_cmpNE; else @@ -437,11 +439,40 @@ namespace Compiler else special = S_minus; } + else if (static_cast (c)==0xe2) + { + /// Workaround for some translator who apparently can't keep his minus in order + /// \todo disable for later script formats + if (get (c) && static_cast (c)==0x80 && + get (c) && static_cast (c)==0x93) + { + if (get (c)) + { + if (c=='>') + special = S_ref; + else + { + putback (c); + special = S_minus; + } + } + else + special = S_minus; + } + else + { + mErrorHandler.error ("Invalid character", mLoc); + return false; + } + } else if (c=='<') { if (get (c)) { - if (c=='=') + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ' && !get (c)) + special = S_cmpLT; + else if (c=='=') { special = S_cmpLE; @@ -461,7 +492,10 @@ namespace Compiler { if (get (c)) { - if (c=='=') + /// \todo hack to allow a space in comparison operators (add option to disable) + if (c==' ' && !get (c)) + special = S_cmpGT; + else if (c=='=') { special = S_cmpGE; @@ -503,7 +537,7 @@ namespace Compiler { return std::isalpha (c) || std::isdigit (c) || c=='_' || /// \todo disable this when doing more stricter compiling - c=='`' || + c=='`' || c=='\'' || /// \todo disable this when doing more stricter compiling. Also, find out who is /// responsible for allowing it in the first place and meet up with that person in /// a dark alley. @@ -512,8 +546,7 @@ namespace Compiler bool Scanner::isWhitespace (char c) { - return c==' ' || c=='\t' - || c=='['; ///< \todo disable this when doing more strict compiling + return c==' ' || c=='\t'; } // constructor diff --git a/components/compiler/scanner.hpp b/components/compiler/scanner.hpp index 349a8afb6f..ed01bbe44e 100644 --- a/components/compiler/scanner.hpp +++ b/components/compiler/scanner.hpp @@ -89,7 +89,8 @@ namespace Compiler bool scanName (char c, Parser& parser, bool& cont); - bool scanName (char c, std::string& name); + /// \param name May contain the start of the name (one or more characters) + bool scanName (std::string& name); bool scanSpecial (char c, Parser& parser, bool& cont); diff --git a/components/config/gamesettings.cpp b/components/config/gamesettings.cpp index 1e7f716e21..0481235c78 100644 --- a/components/config/gamesettings.cpp +++ b/components/config/gamesettings.cpp @@ -1,4 +1,5 @@ #include "gamesettings.hpp" +#include "launchersettings.hpp" #include #include @@ -26,6 +27,7 @@ namespace boost } /* namespace boost */ #endif /* (BOOST_VERSION <= 104600) */ +const char Config::GameSettings::sContentKey[] = "content"; Config::GameSettings::GameSettings(Files::ConfigurationManager &cfg) : mCfgMgr(cfg) @@ -81,7 +83,7 @@ void Config::GameSettings::validatePaths() } } -QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) +QStringList Config::GameSettings::values(const QString &key, const QStringList &defaultValues) const { if (!mSettings.values(key).isEmpty()) return mSettings.values(key); @@ -149,9 +151,6 @@ bool Config::GameSettings::writeFile(QTextStream &stream) while (i.hasPrevious()) { i.previous(); - if (i.key() == QLatin1String("content")) - continue; - // Quote paths with spaces if (i.key() == QLatin1String("data") || i.key() == QLatin1String("data-local") @@ -171,18 +170,13 @@ bool Config::GameSettings::writeFile(QTextStream &stream) } - QStringList content = mUserSettings.values(QString("content")); - for (int i = content.count(); i--;) { - stream << "content=" << content.at(i) << "\n"; - } - return true; } bool Config::GameSettings::hasMaster() { bool result = false; - QStringList content = mSettings.values(QString("content")); + QStringList content = mSettings.values(QString(Config::GameSettings::sContentKey)); for (int i = 0; i < content.count(); ++i) { if (content.at(i).contains(".omwgame") || content.at(i).contains(".esm")) { result = true; @@ -192,3 +186,19 @@ bool Config::GameSettings::hasMaster() return result; } + +void Config::GameSettings::setContentList(const QStringList& fileNames) +{ + remove(sContentKey); + foreach(const QString& fileName, fileNames) + { + setMultiValue(sContentKey, fileName); + } +} + +QStringList Config::GameSettings::getContentList() const +{ + // QMap returns multiple rows in LIFO order, so need to reverse + return Config::LauncherSettings::reverse(values(sContentKey)); +} + diff --git a/components/config/gamesettings.hpp b/components/config/gamesettings.hpp index c4a6ead79b..cc5033f351 100644 --- a/components/config/gamesettings.hpp +++ b/components/config/gamesettings.hpp @@ -59,7 +59,7 @@ namespace Config bool hasMaster(); - QStringList values(const QString &key, const QStringList &defaultValues = QStringList()); + QStringList values(const QString &key, const QStringList &defaultValues = QStringList()) const; bool readFile(QTextStream &stream); bool readFile(QTextStream &stream, QMap &settings); @@ -67,6 +67,9 @@ namespace Config bool writeFile(QTextStream &stream); + void setContentList(const QStringList& fileNames); + QStringList getContentList() const; + private: Files::ConfigurationManager &mCfgMgr; @@ -76,6 +79,8 @@ namespace Config QStringList mDataDirs; QString mDataLocal; + + static const char sContentKey[]; }; } #endif // GAMESETTINGS_HPP diff --git a/components/config/launchersettings.cpp b/components/config/launchersettings.cpp index c014579dc5..66f05f691c 100644 --- a/components/config/launchersettings.cpp +++ b/components/config/launchersettings.cpp @@ -7,6 +7,11 @@ #include +const char Config::LauncherSettings::sCurrentContentListKey[] = "Profiles/currentprofile"; +const char Config::LauncherSettings::sLauncherConfigFileName[] = "launcher.cfg"; +const char Config::LauncherSettings::sContentListsSectionPrefix[] = "Profiles/"; +const char Config::LauncherSettings::sContentListSuffix[] = "/content"; + Config::LauncherSettings::LauncherSettings() { } @@ -15,27 +20,6 @@ Config::LauncherSettings::~LauncherSettings() { } -QStringList Config::LauncherSettings::values(const QString &key, Qt::MatchFlags flags) -{ - QMap settings = SettingsBase::getSettings(); - - if (flags == Qt::MatchExactly) - return settings.values(key); - - QStringList result; - - if (flags == Qt::MatchStartsWith) { - QStringList keys = settings.keys(); - - foreach (const QString ¤tKey, keys) { - if (currentKey.startsWith(key)) - result.append(settings.value(currentKey)); - } - } - - return result; -} - QStringList Config::LauncherSettings::subKeys(const QString &key) { QMap settings = SettingsBase::getSettings(); @@ -66,6 +50,7 @@ QStringList Config::LauncherSettings::subKeys(const QString &key) return result; } + bool Config::LauncherSettings::writeFile(QTextStream &stream) { QString sectionPrefix; @@ -104,3 +89,110 @@ bool Config::LauncherSettings::writeFile(QTextStream &stream) return true; } + +QStringList Config::LauncherSettings::getContentLists() +{ + return subKeys(QString(sContentListsSectionPrefix)); +} + +QString Config::LauncherSettings::makeContentListKey(const QString& contentListName) +{ + return QString(sContentListsSectionPrefix) + contentListName + QString(sContentListSuffix); +} + +void Config::LauncherSettings::setContentList(const GameSettings& gameSettings) +{ + // obtain content list from game settings (if present) + const QStringList files(gameSettings.getContentList()); + + // if any existing profile in launcher matches the content list, make that profile the default + foreach(const QString &listName, getContentLists()) + { + if (isEqual(files, getContentListFiles(listName))) + { + setCurrentContentListName(listName); + return; + } + } + + // otherwise, add content list + QString newContentListName(makeNewContentListName()); + setCurrentContentListName(newContentListName); + setContentList(newContentListName, files); +} + +void Config::LauncherSettings::removeContentList(const QString &contentListName) +{ + remove(makeContentListKey(contentListName)); +} + +void Config::LauncherSettings::setCurrentContentListName(const QString &contentListName) +{ + remove(QString(sCurrentContentListKey)); + setValue(QString(sCurrentContentListKey), contentListName); +} + +void Config::LauncherSettings::setContentList(const QString& contentListName, const QStringList& fileNames) +{ + removeContentList(contentListName); + QString key = makeContentListKey(contentListName); + foreach(const QString& fileName, fileNames) + { + setMultiValue(key, fileName); + } +} + +QString Config::LauncherSettings::getCurrentContentListName() const +{ + return value(QString(sCurrentContentListKey)); +} + +QStringList Config::LauncherSettings::getContentListFiles(const QString& contentListName) const +{ + // QMap returns multiple rows in LIFO order, so need to reverse + return reverse(getSettings().values(makeContentListKey(contentListName))); +} + +QStringList Config::LauncherSettings::reverse(const QStringList& toReverse) +{ + QStringList result; + result.reserve(toReverse.size()); + std::reverse_copy(toReverse.begin(), toReverse.end(), std::back_inserter(result)); + return result; +} + +bool Config::LauncherSettings::isEqual(const QStringList& list1, const QStringList& list2) +{ + if (list1.count() != list2.count()) + { + return false; + } + + for (int i = 0; i < list1.count(); ++i) + { + if (list1.at(i) != list2.at(i)) + { + return false; + } + } + + // if get here, lists are same + return true; +} + +QString Config::LauncherSettings::makeNewContentListName() +{ + // basically, use date and time as the name e.g. YYYY-MM-DDThh:mm:ss + time_t rawtime; + struct tm * timeinfo; + + time(&rawtime); + timeinfo = localtime(&rawtime); + int base = 10; + QChar zeroPad('0'); + return QString("%1-%2-%3T%4:%5:%6") + .arg(timeinfo->tm_year + 1900, 4).arg(timeinfo->tm_mon + 1, 2, base, zeroPad).arg(timeinfo->tm_mday, 2, base, zeroPad) + .arg(timeinfo->tm_hour, 2, base, zeroPad).arg(timeinfo->tm_min, 2, base, zeroPad).arg(timeinfo->tm_sec, 2, base, zeroPad); +} + + diff --git a/components/config/launchersettings.hpp b/components/config/launchersettings.hpp index 042823ca93..cbe21c54a6 100644 --- a/components/config/launchersettings.hpp +++ b/components/config/launchersettings.hpp @@ -2,6 +2,7 @@ #define LAUNCHERSETTINGS_HPP #include "settingsbase.hpp" +#include "gamesettings.hpp" namespace Config { @@ -11,11 +12,49 @@ namespace Config LauncherSettings(); ~LauncherSettings(); - QStringList subKeys(const QString &key); - QStringList values(const QString &key, Qt::MatchFlags flags = Qt::MatchExactly); - bool writeFile(QTextStream &stream); + /// \return names of all Content Lists in the launcher's .cfg file. + QStringList getContentLists(); + + /// Set initally selected content list to match values from openmw.cfg, creating if necessary + void setContentList(const GameSettings& gameSettings); + + /// Create a Content List (or replace if it already exists) + void setContentList(const QString& contentListName, const QStringList& fileNames); + + void removeContentList(const QString &contentListName); + + void setCurrentContentListName(const QString &contentListName); + + QString getCurrentContentListName() const; + + QStringList getContentListFiles(const QString& contentListName) const; + + /// \return new list that is reversed order of input + static QStringList reverse(const QStringList& toReverse); + + static const char sLauncherConfigFileName[]; + + private: + + /// \return key to use to get/set the files in the specified Content List + static QString makeContentListKey(const QString& contentListName); + + /// \return true if both lists are same + static bool isEqual(const QStringList& list1, const QStringList& list2); + + static QString makeNewContentListName(); + + QStringList subKeys(const QString &key); + + /// name of entry in launcher.cfg that holds name of currently selected Content List + static const char sCurrentContentListKey[]; + + /// section of launcher.cfg holding the Content Lists + static const char sContentListsSectionPrefix[]; + + static const char sContentListSuffix[]; }; } #endif // LAUNCHERSETTINGS_HPP diff --git a/components/config/settingsbase.hpp b/components/config/settingsbase.hpp index 92ca34cdfe..c798d2893b 100644 --- a/components/config/settingsbase.hpp +++ b/components/config/settingsbase.hpp @@ -17,7 +17,7 @@ namespace Config SettingsBase() { mMultiValue = false; } ~SettingsBase() {} - inline QString value(const QString &key, const QString &defaultValue = QString()) + inline QString value(const QString &key, const QString &defaultValue = QString()) const { return mSettings.value(key).isEmpty() ? defaultValue : mSettings.value(key); } @@ -46,11 +46,11 @@ namespace Config mSettings.remove(key); } - Map getSettings() {return mSettings;} + Map getSettings() const {return mSettings;} bool readFile(QTextStream &stream) { - mCache.clear(); + Map cache; QString sectionPrefix; @@ -79,31 +79,30 @@ namespace Config mSettings.remove(key); - QStringList values = mCache.values(key); + QStringList values = cache.values(key); if (!values.contains(value)) { if (mMultiValue) { - mCache.insertMulti(key, value); + cache.insertMulti(key, value); } else { - mCache.insert(key, value); + cache.insert(key, value); } } } } if (mSettings.isEmpty()) { - mSettings = mCache; // This is the first time we read a file + mSettings = cache; // This is the first time we read a file return true; } // Merge the changed keys with those which didn't - mSettings.unite(mCache); + mSettings.unite(cache); return true; } private: Map mSettings; - Map mCache; bool mMultiValue; }; diff --git a/components/contentselector/model/contentmodel.cpp b/components/contentselector/model/contentmodel.cpp index 0d4f2365a6..ec1fcc21e4 100644 --- a/components/contentselector/model/contentmodel.cpp +++ b/components/contentselector/model/contentmodel.cpp @@ -9,18 +9,24 @@ #include "components/esm/esmreader.hpp" -ContentSelectorModel::ContentModel::ContentModel(QObject *parent) : +ContentSelectorModel::ContentModel::ContentModel(QObject *parent, QIcon warningIcon) : QAbstractTableModel(parent), + mWarningIcon(warningIcon), mMimeType ("application/omwcontent"), mMimeTypes (QStringList() << mMimeType), mColumnCount (1), - mDragDropFlags (Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled), - mDropActions (Qt::CopyAction | Qt::MoveAction) + mDropActions (Qt::MoveAction) { setEncoding ("win1252"); uncheckAll(); } +ContentSelectorModel::ContentModel::~ContentModel() +{ + qDeleteAll(mFiles); + mFiles.clear(); +} + void ContentSelectorModel::ContentModel::setEncoding(const QString &encoding) { mEncoding = encoding; @@ -97,7 +103,7 @@ QModelIndex ContentSelectorModel::ContentModel::indexFromItem(const EsmFile *ite Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index) const { if (!index.isValid()) - return Qt::NoItemFlags; + return Qt::ItemIsDropEnabled; const EsmFile *file = item(index.row()); @@ -106,7 +112,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index //game files can always be checked if (file->isGameFile()) - return Qt::ItemIsEnabled | Qt::ItemIsSelectable; + return Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable; Qt::ItemFlags returnFlags; bool allDependenciesFound = true; @@ -145,7 +151,7 @@ Qt::ItemFlags ContentSelectorModel::ContentModel::flags(const QModelIndex &index if (gamefileChecked) { if (allDependenciesFound) - returnFlags = returnFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | mDragDropFlags; + returnFlags = returnFlags | Qt::ItemIsEnabled | Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsDragEnabled; else returnFlags = Qt::ItemIsSelectable; } @@ -170,6 +176,11 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int switch (role) { + case Qt::DecorationRole: + { + return isLoadOrderError(file) ? mWarningIcon : QVariant(); + } + case Qt::EditRole: case Qt::DisplayRole: { @@ -177,7 +188,6 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int return file->fileProperty(static_cast(column)); return QVariant(); - break; } case Qt::TextAlignmentRole: @@ -193,8 +203,6 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int default: return Qt::AlignLeft + Qt::AlignVCenter; } - return QVariant(); - break; } case Qt::ToolTipRole: @@ -202,8 +210,7 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int if (column != 0) return QVariant(); - return file->toolTip(); - break; + return toolTip(file); } case Qt::CheckStateRole: @@ -212,8 +219,6 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int return QVariant(); return mCheckStates[file->filePath()]; - - break; } case Qt::UserRole: @@ -229,7 +234,6 @@ QVariant ContentSelectorModel::ContentModel::data(const QModelIndex &index, int case Qt::UserRole + 1: return isChecked(file->filePath()); - break; } return QVariant(); } @@ -291,7 +295,7 @@ bool ContentSelectorModel::ContentModel::setData(const QModelIndex &index, const { setCheckState(file->filePath(), success); emit dataChanged(index, index); - + checkForLoadOrderErrors(); } else return success; @@ -341,6 +345,8 @@ bool ContentSelectorModel::ContentModel::removeRows(int position, int rows, cons } endRemoveRows(); + // at this point we know that drag and drop has finished. + checkForLoadOrderErrors(); return true; } @@ -436,15 +442,12 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) filters << "*.esp" << "*.esm" << "*.omwgame" << "*.omwaddon"; dir.setNameFilters(filters); - QTextCodec *codec = QTextCodec::codecForName("UTF8"); - - // Create a decoder for non-latin characters in esx metadata - QTextDecoder *decoder = codec->makeDecoder(); - foreach (const QString &path, dir.entryList()) { QFileInfo info(dir.absoluteFilePath(path)); - EsmFile *file = new EsmFile(path); + + if (item(info.absoluteFilePath()) != 0) + continue; try { ESM::ESMReader fileReader; @@ -453,19 +456,19 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) fileReader.setEncoder(&encoder); fileReader.open(dir.absoluteFilePath(path).toStdString()); - foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) - file->addGameFile(QString::fromStdString(item.name)); + EsmFile *file = new EsmFile(path); - file->setAuthor (decoder->toUnicode(fileReader.getAuthor().c_str())); + foreach (const ESM::Header::MasterData &item, fileReader.getGameFiles()) + file->addGameFile(QString::fromUtf8(item.name.c_str())); + + file->setAuthor (QString::fromUtf8(fileReader.getAuthor().c_str())); file->setDate (info.lastModified()); file->setFormat (fileReader.getFormat()); file->setFilePath (info.absoluteFilePath()); - file->setDescription(decoder->toUnicode(fileReader.getDesc().c_str())); - + file->setDescription(QString::fromUtf8(fileReader.getDesc().c_str())); // Put the file in the table - if (item(file->filePath()) == 0) - addFile(file); + addFile(file); } catch(std::runtime_error &e) { // An error occurred while reading the .esp @@ -475,8 +478,6 @@ void ContentSelectorModel::ContentModel::addFiles(const QString &path) } - delete decoder; - sortFiles(); } @@ -530,11 +531,98 @@ bool ContentSelectorModel::ContentModel::isEnabled (QModelIndex index) const return (flags(index) & Qt::ItemIsEnabled); } -void ContentSelectorModel::ContentModel::setCheckStates (const QStringList &fileList, bool isChecked) +bool ContentSelectorModel::ContentModel::isLoadOrderError(const EsmFile *file) const { - foreach (const QString &file, fileList) + return mPluginsWithLoadOrderError.contains(file->filePath()); +} + +void ContentSelectorModel::ContentModel::setContentList(const QStringList &fileList, bool isChecked) +{ + mPluginsWithLoadOrderError.clear(); + int previousPosition = -1; + foreach (const QString &filepath, fileList) { - setCheckState (file, isChecked); + if (setCheckState(filepath, isChecked)) + { + // as necessary, move plug-ins in visible list to match sequence of supplied filelist + const EsmFile* file = item(filepath); + int filePosition = indexFromItem(file).row(); + if (filePosition < previousPosition) + { + mFiles.move(filePosition, previousPosition); + emit dataChanged(index(filePosition, 0, QModelIndex()), index(previousPosition, 0, QModelIndex())); + } + else + { + previousPosition = filePosition; + } + } + } + checkForLoadOrderErrors(); +} + +void ContentSelectorModel::ContentModel::checkForLoadOrderErrors() +{ + for (int row = 0; row < mFiles.count(); ++row) + { + EsmFile* file = item(row); + bool isRowInError = checkForLoadOrderErrors(file, row).count() != 0; + if (isRowInError) + { + mPluginsWithLoadOrderError.insert(file->filePath()); + } + else + { + mPluginsWithLoadOrderError.remove(file->filePath()); + } + } +} + +QList ContentSelectorModel::ContentModel::checkForLoadOrderErrors(const EsmFile *file, int row) const +{ + QList errors = QList(); + foreach(QString dependentfileName, file->gameFiles()) + { + const EsmFile* dependentFile = item(dependentfileName); + + if (!dependentFile) + { + errors.append(LoadOrderError(LoadOrderError::ErrorCode_MissingDependency, dependentfileName)); + } + else + { + if (!isChecked(dependentFile->filePath())) + { + errors.append(LoadOrderError(LoadOrderError::ErrorCode_InactiveDependency, dependentfileName)); + } + if (row < indexFromItem(dependentFile).row()) + { + errors.append(LoadOrderError(LoadOrderError::ErrorCode_LoadOrder, dependentfileName)); + } + } + } + return errors; +} + +QString ContentSelectorModel::ContentModel::toolTip(const EsmFile *file) const +{ + if (isLoadOrderError(file)) + { + QString text(""); + int index = indexFromItem(item(file->filePath())).row(); + foreach(const LoadOrderError& error, checkForLoadOrderErrors(file, index)) + { + text += "

"; + text += error.toolTip(); + text += "

"; + } + text += (""); + text += file->toolTip(); + return text; + } + else + { + return file->toolTip(); } } diff --git a/components/contentselector/model/contentmodel.hpp b/components/contentselector/model/contentmodel.hpp index 7b2000b510..6af5425c77 100644 --- a/components/contentselector/model/contentmodel.hpp +++ b/components/contentselector/model/contentmodel.hpp @@ -3,6 +3,9 @@ #include #include +#include +#include +#include "loadordererror.hpp" namespace ContentSelectorModel { @@ -20,7 +23,8 @@ namespace ContentSelectorModel { Q_OBJECT public: - explicit ContentModel(QObject *parent = 0); + explicit ContentModel(QObject *parent, QIcon warningIcon); + ~ContentModel(); void setEncoding(const QString &encoding); @@ -47,12 +51,15 @@ namespace ContentSelectorModel bool isEnabled (QModelIndex index) const; bool isChecked(const QString &filepath) const; bool setCheckState(const QString &filepath, bool isChecked); - void setCheckStates (const QStringList &fileList, bool isChecked); + void setContentList(const QStringList &fileList, bool isChecked); ContentFileList checkedItems() const; void uncheckAll(); void refreshModel(); + /// Checks all plug-ins for load order errors and updates mPluginsWithLoadOrderError with plug-ins with issues + void checkForLoadOrderErrors(); + private: void addFile(EsmFile *file); @@ -61,17 +68,27 @@ namespace ContentSelectorModel void sortFiles(); + /// Checks a specific plug-in for load order errors + /// \return all errors found for specific plug-in + QList checkForLoadOrderErrors(const EsmFile *file, int row) const; + + /// \return true if plug-in has a Load Order error + bool isLoadOrderError(const EsmFile *file) const; + + QString toolTip(const EsmFile *file) const; + ContentFileList mFiles; QHash mCheckStates; + QSet mPluginsWithLoadOrderError; QTextCodec *mCodec; QString mEncoding; + QIcon mWarningIcon; public: QString mMimeType; QStringList mMimeTypes; int mColumnCount; - Qt::ItemFlags mDragDropFlags; Qt::DropActions mDropActions; }; } diff --git a/components/contentselector/model/loadordererror.cpp b/components/contentselector/model/loadordererror.cpp new file mode 100644 index 0000000000..aa69f330e3 --- /dev/null +++ b/components/contentselector/model/loadordererror.cpp @@ -0,0 +1,15 @@ +#include "loadordererror.hpp" +#include + +QString ContentSelectorModel::LoadOrderError::sErrorToolTips[ErrorCode_LoadOrder] = +{ + QString("Unable to find dependent file: %1"), + QString("Dependent file needs to be active: %1"), + QString("This file needs to load after %1") +}; + +QString ContentSelectorModel::LoadOrderError::toolTip() const +{ + assert(mErrorCode); + return sErrorToolTips[mErrorCode - 1].arg(mFileName); +} diff --git a/components/contentselector/model/loadordererror.hpp b/components/contentselector/model/loadordererror.hpp new file mode 100644 index 0000000000..2b840cf69b --- /dev/null +++ b/components/contentselector/model/loadordererror.hpp @@ -0,0 +1,37 @@ +#ifndef LOADORDERERROR_HPP +#define LOADORDERERROR_HPP + +#include + +namespace ContentSelectorModel +{ + /// \brief Details of a suspected Load Order problem a plug-in will have. This is basically a POD. + class LoadOrderError + { + public: + enum ErrorCode + { + ErrorCode_None = 0, + ErrorCode_MissingDependency = 1, + ErrorCode_InactiveDependency = 2, + ErrorCode_LoadOrder = 3 + }; + + inline LoadOrderError() : mErrorCode(ErrorCode_None) {}; + inline LoadOrderError(ErrorCode errorCode, QString fileName) + { + mErrorCode = errorCode; + mFileName = fileName; + } + inline ErrorCode errorCode() const { return mErrorCode; } + inline QString fileName() const { return mFileName; } + QString toolTip() const; + + private: + ErrorCode mErrorCode; + QString mFileName; + static QString sErrorToolTips[ErrorCode_LoadOrder]; + }; +} + +#endif // LOADORDERERROR_HPP diff --git a/components/contentselector/view/combobox.cpp b/components/contentselector/view/combobox.cpp index 1d773b62dd..18cafc2dc1 100644 --- a/components/contentselector/view/combobox.cpp +++ b/components/contentselector/view/combobox.cpp @@ -30,7 +30,7 @@ void ContentSelectorView::ComboBox::paintEvent(QPaintEvent *) // draw the icon and text if (!opt.editable && currentIndex() == -1) // <<< we adjust the text displayed when nothing is selected opt.currentText = mPlaceholderText; - painter.drawControl(QStyle::CE_ComboBoxLabel, opt); + painter.drawControl(QStyle::CE_ComboBoxLabel, opt); } void ContentSelectorView::ComboBox::setPlaceholderText(const QString &text) diff --git a/components/contentselector/view/contentselector.cpp b/components/contentselector/view/contentselector.cpp index e9599de498..ad023e5b22 100644 --- a/components/contentselector/view/contentselector.cpp +++ b/components/contentselector/view/contentselector.cpp @@ -15,7 +15,8 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : QObject(parent) { - ui.setupUi (parent); + ui.setupUi(parent); + ui.addonView->setDragDropMode(QAbstractItemView::InternalMove); buildContentModel(); buildGameFileView(); @@ -24,7 +25,8 @@ ContentSelectorView::ContentSelector::ContentSelector(QWidget *parent) : void ContentSelectorView::ContentSelector::buildContentModel() { - mContentModel = new ContentSelectorModel::ContentModel(); + QIcon warningIcon(ui.addonView->style()->standardIcon(QStyle::SP_MessageBoxWarning).pixmap(QSize(16, 15))); + mContentModel = new ContentSelectorModel::ContentModel(this, warningIcon); } void ContentSelectorView::ContentSelector::buildGameFileView() @@ -58,36 +60,25 @@ void ContentSelectorView::ContentSelector::buildAddonView() ui.addonView->setModel(mAddonProxyModel); - connect(ui.addonView, SIGNAL(clicked(const QModelIndex &)), this, SLOT(slotAddonTableItemClicked(const QModelIndex &))); + connect(ui.addonView, SIGNAL(activated(const QModelIndex&)), this, SLOT(slotAddonTableItemActivated(const QModelIndex&))); + connect(mContentModel, SIGNAL(dataChanged(const QModelIndex&, const QModelIndex&)), this, SIGNAL(signalAddonDataChanged(QModelIndex,QModelIndex))); } void ContentSelectorView::ContentSelector::setProfileContent(const QStringList &fileList) { clearCheckStates(); - bool foundGamefile = false; foreach (const QString &filepath, fileList) { - if (!foundGamefile) + const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath); + if (file && file->isGameFile()) { - const ContentSelectorModel::EsmFile *file = mContentModel->item(filepath); - - foundGamefile = (file->isGameFile()); - - if (foundGamefile) - { - setGameFile (filepath); - break; - } + setGameFile (filepath); + break; } } -/* if (!foundGameFile) - { - //throw gamefile error here. - }*/ - - setCheckStates (fileList); + setContentList(fileList); } void ContentSelectorView::ContentSelector::setGameFile(const QString &filename) @@ -115,14 +106,14 @@ void ContentSelectorView::ContentSelector::clearCheckStates() mContentModel->uncheckAll(); } -void ContentSelectorView::ContentSelector::setCheckStates(const QStringList &list) +void ContentSelectorView::ContentSelector::setContentList(const QStringList &list) { if (list.isEmpty()) { slotCurrentGameFileIndexChanged (ui.gameFileView->currentIndex()); } else - mContentModel->setCheckStates (list, true); + mContentModel->setContentList(list, true); } ContentSelectorModel::ContentFileList @@ -173,6 +164,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i oldIndex = index; model->setData(model->index(index, 0), true, Qt::UserRole + 1); + mContentModel->checkForLoadOrderErrors(); } if (proxy) @@ -181,7 +173,7 @@ void ContentSelectorView::ContentSelector::slotCurrentGameFileIndexChanged(int i emit signalCurrentGamefileIndexChanged (index); } -void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QModelIndex &index) +void ContentSelectorView::ContentSelector::slotAddonTableItemActivated(const QModelIndex &index) { QModelIndex sourceIndex = mAddonProxyModel->mapToSource (index); @@ -194,10 +186,4 @@ void ContentSelectorView::ContentSelector::slotAddonTableItemClicked(const QMode checkState = Qt::Checked; mContentModel->setData(sourceIndex, checkState, Qt::CheckStateRole); - - if (checkState == Qt::Checked) - emit signalAddonFileSelected (index.row()); - else - emit signalAddonFileUnselected (index.row()); - } diff --git a/components/contentselector/view/contentselector.hpp b/components/contentselector/view/contentselector.hpp index a25eb20ae3..8651dac2ef 100644 --- a/components/contentselector/view/contentselector.hpp +++ b/components/contentselector/view/contentselector.hpp @@ -32,7 +32,7 @@ namespace ContentSelectorView void setProfileContent (const QStringList &fileList); void clearCheckStates(); - void setCheckStates (const QStringList &list); + void setContentList(const QStringList &list); ContentSelectorModel::ContentFileList selectedFiles() const; @@ -55,13 +55,13 @@ namespace ContentSelectorView signals: void signalCurrentGamefileIndexChanged (int); - void signalAddonFileSelected (int); - void signalAddonFileUnselected (int); + + void signalAddonDataChanged (const QModelIndex& topleft, const QModelIndex& bottomright); private slots: void slotCurrentGameFileIndexChanged(int index); - void slotAddonTableItemClicked(const QModelIndex &index); + void slotAddonTableItemActivated(const QModelIndex& index); }; } diff --git a/components/esm/aipackage.cpp b/components/esm/aipackage.cpp index 209a1fe264..8ad64b7975 100644 --- a/components/esm/aipackage.cpp +++ b/components/esm/aipackage.cpp @@ -11,36 +11,55 @@ namespace ESM mServices = 0; } + void AIPackageList::add(ESMReader &esm) + { + AIPackage pack; + if (esm.retSubName() == AI_CNDT) { + mList.back().mCellName = esm.getHString(); + } else if (esm.retSubName() == AI_Wander) { + pack.mType = AI_Wander; + esm.getHExact(&pack.mWander, 14); + mList.push_back(pack); + } else if (esm.retSubName() == AI_Travel) { + pack.mType = AI_Travel; + esm.getHExact(&pack.mTravel, 16); + mList.push_back(pack); + } else if (esm.retSubName() == AI_Escort || + esm.retSubName() == AI_Follow) + { + pack.mType = + (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; + esm.getHExact(&pack.mTarget, 48); + mList.push_back(pack); + } else if (esm.retSubName() == AI_Activate) { + pack.mType = AI_Activate; + esm.getHExact(&pack.mActivate, 33); + mList.push_back(pack); + } else { // not AI package related data, so leave + return; + } + + } + void AIPackageList::load(ESMReader &esm) { mList.clear(); while (esm.hasMoreSubs()) { // initialize every iteration - AIPackage pack; - esm.getSubName(); - if (esm.retSubName() == 0x54444e43) { // CNDT - mList.back().mCellName = esm.getHString(); - } else if (esm.retSubName() == AI_Wander) { - pack.mType = AI_Wander; - esm.getHExact(&pack.mWander, 14); - mList.push_back(pack); - } else if (esm.retSubName() == AI_Travel) { - pack.mType = AI_Travel; - esm.getHExact(&pack.mTravel, 16); - mList.push_back(pack); - } else if (esm.retSubName() == AI_Escort || - esm.retSubName() == AI_Follow) + esm.getSubName(); + switch (esm.retSubName().val) { - pack.mType = - (esm.retSubName() == AI_Escort) ? AI_Escort : AI_Follow; - esm.getHExact(&pack.mTarget, 48); - mList.push_back(pack); - } else if (esm.retSubName() == AI_Activate) { - pack.mType = AI_Activate; - esm.getHExact(&pack.mActivate, 33); - mList.push_back(pack); - } else { // not AI package related data, so leave - return; + case AI_Wander: + case AI_Activate: + case AI_Escort: + case AI_Follow: + case AI_Travel: + case AI_CNDT: + + add(esm); + break; + default: + return; } } } diff --git a/components/esm/aipackage.hpp b/components/esm/aipackage.hpp index cbe82f16ed..30bd2ce04c 100644 --- a/components/esm/aipackage.hpp +++ b/components/esm/aipackage.hpp @@ -63,7 +63,8 @@ namespace ESM AI_Travel = 0x545f4941, AI_Follow = 0x465f4941, AI_Escort = 0x455f4941, - AI_Activate = 0x415f4941 + AI_Activate = 0x415f4941, + AI_CNDT = 0x54444e43 }; /// \note Used for storaging packages in a single container @@ -90,11 +91,12 @@ namespace ESM { std::vector mList; - /// \note This breaks consistency of subrecords reading: - /// after calling it subrecord name is already read, so - /// it needs to use retSubName() if needed. But, hey, there - /// is only one field left (XSCL) and only two records uses AI + /// Add a single AIPackage, assumes subrecord name was already read + void add(ESMReader &esm); + + /// TODO: remove this method. The ESM format does not guarantee that all AI packages follow one another void load(ESMReader &esm); + void save(ESMWriter &esm) const; }; } diff --git a/components/esm/aisequence.cpp b/components/esm/aisequence.cpp index 0e3e541024..5b36f9a4cd 100644 --- a/components/esm/aisequence.cpp +++ b/components/esm/aisequence.cpp @@ -16,12 +16,20 @@ namespace AiSequence { esm.getHNT (mData, "DATA"); esm.getHNT(mStartTime, "STAR"); + mStoredInitialActorPosition = false; + if (esm.isNextSub("POS_")) + { + mStoredInitialActorPosition = true; + esm.getHT(mInitialActorPosition); + } } void AiWander::save(ESMWriter &esm) const { esm.writeHNT ("DATA", mData); esm.writeHNT ("STAR", mStartTime); + if (mStoredInitialActorPosition) + esm.writeHNT ("POS_", mInitialActorPosition); } void AiTravel::load(ESMReader &esm) @@ -60,6 +68,8 @@ namespace AiSequence esm.getHNT (mAlwaysFollow, "ALWY"); mCommanded = false; esm.getHNOT (mCommanded, "CMND"); + mActive = false; + esm.getHNOT (mActive, "ACTV"); } void AiFollow::save(ESMWriter &esm) const @@ -71,6 +81,8 @@ namespace AiSequence esm.writeHNString ("CELL", mCellId); esm.writeHNT ("ALWY", mAlwaysFollow); esm.writeHNT ("CMND", mCommanded); + if (mActive) + esm.writeHNT("ACTV", mActive); } void AiActivate::load(ESMReader &esm) diff --git a/components/esm/aisequence.hpp b/components/esm/aisequence.hpp index da16bf867a..2560fbe7df 100644 --- a/components/esm/aisequence.hpp +++ b/components/esm/aisequence.hpp @@ -6,6 +6,8 @@ #include "defs.hpp" +#include "util.hpp" + namespace ESM { class ESMReader; @@ -61,6 +63,9 @@ namespace ESM AiWanderData mData; ESM::TimeStamp mStartTime; + bool mStoredInitialActorPosition; + ESM::Vector3 mInitialActorPosition; + /// \todo add more AiWander state void load(ESMReader &esm); @@ -98,6 +103,8 @@ namespace ESM bool mAlwaysFollow; bool mCommanded; + bool mActive; + void load(ESMReader &esm); void save(ESMWriter &esm) const; }; diff --git a/components/esm/cellref.cpp b/components/esm/cellref.cpp index 29d26d013f..0dd2987b5c 100644 --- a/components/esm/cellref.cpp +++ b/components/esm/cellref.cpp @@ -5,6 +5,12 @@ #include "esmwriter.hpp" void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) +{ + loadId(esm, wideRefNum); + loadData(esm); +} + +void ESM::CellRef::loadId(ESMReader &esm, bool wideRefNum) { // According to Hrnchamd, this does not belong to the actual ref. Instead, it is a marker indicating that // the following refs are part of a "temp refs" section. A temp ref is not being tracked by the moved references system. @@ -19,7 +25,10 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) esm.getHNT (mRefNum.mIndex, "FRMR"); mRefID = esm.getHNString ("NAME"); +} +void ESM::CellRef::loadData(ESMReader &esm) +{ // Again, UNAM sometimes appears after NAME and sometimes later. // Or perhaps this UNAM means something different? mReferenceBlocked = -1; @@ -37,12 +46,12 @@ void ESM::CellRef::load (ESMReader& esm, bool wideRefNum) esm.getHNOT (mFactionRank, "INDX"); mGoldValue = 1; - mCharge = -1; + mChargeInt = -1; mEnchantmentCharge = -1; esm.getHNOT (mEnchantmentCharge, "XCHG"); - esm.getHNOT (mCharge, "INTV"); + esm.getHNOT (mChargeInt, "INTV"); esm.getHNOT (mGoldValue, "NAM9"); @@ -97,8 +106,8 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons if (mEnchantmentCharge != -1) esm.writeHNT("XCHG", mEnchantmentCharge); - if (mCharge != -1) - esm.writeHNT("INTV", mCharge); + if (mChargeInt != -1) + esm.writeHNT("INTV", mChargeInt); if (mGoldValue != 1) { esm.writeHNT("NAM9", mGoldValue); @@ -129,8 +138,7 @@ void ESM::CellRef::save (ESMWriter &esm, bool wideRefNum, bool inInventory) cons void ESM::CellRef::blank() { - mRefNum.mIndex = 0; - mRefNum.mContentFile = -1; + mRefNum.unset(); mRefID.clear(); mScale = 1; mOwner.clear(); @@ -138,14 +146,14 @@ void ESM::CellRef::blank() mSoul.clear(); mFaction.clear(); mFactionRank = -2; - mCharge = 0; - mEnchantmentCharge = 0; + mChargeInt = -1; + mEnchantmentCharge = -1; mGoldValue = 0; mDestCell.clear(); mLockLevel = 0; mKey.clear(); mTrap.clear(); - mReferenceBlocked = 0; + mReferenceBlocked = -1; mTeleport = false; for (int i=0; i<3; ++i) diff --git a/components/esm/cellref.hpp b/components/esm/cellref.hpp index 9c57061b00..01b6a8473d 100644 --- a/components/esm/cellref.hpp +++ b/components/esm/cellref.hpp @@ -13,8 +13,12 @@ namespace ESM struct RefNum { - int mIndex; - int mContentFile; // -1 no content file + unsigned int mIndex; + int mContentFile; + + enum { RefNum_NoContentFile = -1 }; + inline bool hasContentFile() const { return mContentFile != RefNum_NoContentFile; } + inline void unset() { mIndex = 0; mContentFile = RefNum_NoContentFile; } }; /* Cell reference. This represents ONE object (of many) inside the @@ -55,7 +59,13 @@ namespace ESM // For weapon or armor, this is the remaining item health. // For tools (lockpicks, probes, repair hammer) it is the remaining uses. - int mCharge; + // For lights it is remaining time. + // This could be -1 if the charge was not touched yet (i.e. full). + union + { + int mChargeInt; // Used by everything except lights + float mChargeFloat; // Used only by lights + }; // Remaining enchantment charge. This could be -1 if the charge was not touched yet (i.e. full). float mEnchantmentCharge; @@ -85,8 +95,14 @@ namespace ESM // Position and rotation of this object within the cell Position mPos; + /// Calls loadId and loadData void load (ESMReader& esm, bool wideRefNum = false); + void loadId (ESMReader& esm, bool wideRefNum = false); + + /// Implicitly called by load + void loadData (ESMReader& esm); + void save (ESMWriter &esm, bool wideRefNum = false, bool inInventory = false) const; void blank(); diff --git a/components/esm/creaturestate.cpp b/components/esm/creaturestate.cpp index 9e9b561026..c15becd981 100644 --- a/components/esm/creaturestate.cpp +++ b/components/esm/creaturestate.cpp @@ -5,16 +5,28 @@ void ESM::CreatureState::load (ESMReader &esm) { ObjectState::load (esm); - mInventory.load (esm); + if (mHasCustomState) + { + mInventory.load (esm); - mCreatureStats.load (esm); + mCreatureStats.load (esm); + } } void ESM::CreatureState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); - mInventory.save (esm); + if (mHasCustomState) + { + mInventory.save (esm); - mCreatureStats.save (esm); -} \ No newline at end of file + mCreatureStats.save (esm); + } +} + +void ESM::CreatureState::blank() +{ + ObjectState::blank(); + mCreatureStats.blank(); +} diff --git a/components/esm/creaturestate.hpp b/components/esm/creaturestate.hpp index 604c2f3a70..9a3d41daae 100644 --- a/components/esm/creaturestate.hpp +++ b/components/esm/creaturestate.hpp @@ -14,6 +14,9 @@ namespace ESM InventoryState mInventory; CreatureStats mCreatureStats; + /// Initialize to default state + void blank(); + virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; }; diff --git a/components/esm/creaturestats.cpp b/components/esm/creaturestats.cpp index 21803e02f9..75c1c28bc2 100644 --- a/components/esm/creaturestats.cpp +++ b/components/esm/creaturestats.cpp @@ -24,8 +24,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mMurdered = false; esm.getHNOT (mMurdered, "MURD"); - mFriendlyHits = 0; - esm.getHNOT (mFriendlyHits, "FRHT"); + if (esm.isNextSub("FRHT")) + esm.skipHSub(); // Friendly hits, no longer used mTalkedTo = false; esm.getHNOT (mTalkedTo, "TALK"); @@ -68,6 +68,8 @@ void ESM::CreatureStats::load (ESMReader &esm) mLastHitObject = esm.getHNOString ("LHIT"); + mLastHitAttemptObject = esm.getHNOString ("LHAT"); + mRecalcDynamicStats = false; esm.getHNOT (mRecalcDynamicStats, "CALC"); @@ -92,9 +94,10 @@ void ESM::CreatureStats::load (ESMReader &esm) { int magicEffect; esm.getHT(magicEffect); + std::string source = esm.getHNOString("SOUR"); int actorId; esm.getHNT (actorId, "ACID"); - mSummonedCreatureMap[magicEffect] = actorId; + mSummonedCreatureMap[std::make_pair(magicEffect, source)] = actorId; } while (esm.isNextSub("GRAV")) @@ -137,9 +140,6 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (mMurdered) esm.writeHNT ("MURD", mMurdered); - if (mFriendlyHits) - esm.writeHNT ("FRHT", mFriendlyHits); - if (mTalkedTo) esm.writeHNT ("TALK", mTalkedTo); @@ -179,6 +179,9 @@ void ESM::CreatureStats::save (ESMWriter &esm) const if (!mLastHitObject.empty()) esm.writeHNString ("LHIT", mLastHitObject); + if (!mLastHitAttemptObject.empty()) + esm.writeHNString ("LHAT", mLastHitAttemptObject); + if (mRecalcDynamicStats) esm.writeHNT ("CALC", mRecalcDynamicStats); @@ -199,9 +202,10 @@ void ESM::CreatureStats::save (ESMWriter &esm) const mAiSequence.save(esm); mMagicEffects.save(esm); - for (std::map::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) + for (std::map, int>::const_iterator it = mSummonedCreatureMap.begin(); it != mSummonedCreatureMap.end(); ++it) { - esm.writeHNT ("SUMM", it->first); + esm.writeHNT ("SUMM", it->first.first); + esm.writeHNString ("SOUR", it->first.second); esm.writeHNT ("ACID", it->second); } @@ -211,6 +215,37 @@ void ESM::CreatureStats::save (ESMWriter &esm) const } esm.writeHNT("AISE", mHasAiSettings); - for (int i=0; i<4; ++i) - mAiSettings[i].save(esm); + if (mHasAiSettings) + { + for (int i=0; i<4; ++i) + mAiSettings[i].save(esm); + } +} + +void ESM::CreatureStats::blank() +{ + mTradeTime.mHour = 0; + mTradeTime.mDay = 0; + mGoldPool = 0; + mActorId = -1; + mHasAiSettings = false; + mDead = false; + mDied = false; + mMurdered = false; + mTalkedTo = false; + mAlarmed = false; + mAttacked = false; + mAttackingOrSpell = false; + mKnockdown = false; + mKnockdownOneFrame = false; + mKnockdownOverOneFrame = false; + mHitRecovery = false; + mBlock = false; + mMovementFlags = 0; + mAttackStrength = 0.f; + mFallHeight = 0.f; + mRecalcDynamicStats = false; + mDrawState = 0; + mDeathAnimation = 0; + mLevel = 1; } diff --git a/components/esm/creaturestats.hpp b/components/esm/creaturestats.hpp index 8f4d4df7b8..2a03136d08 100644 --- a/components/esm/creaturestats.hpp +++ b/components/esm/creaturestats.hpp @@ -32,7 +32,7 @@ namespace ESM bool mHasAiSettings; StatState mAiSettings[4]; - std::map mSummonedCreatureMap; + std::map, int> mSummonedCreatureMap; std::vector mSummonGraveyard; ESM::TimeStamp mTradeTime; @@ -42,7 +42,6 @@ namespace ESM bool mDead; bool mDied; bool mMurdered; - int mFriendlyHits; bool mTalkedTo; bool mAlarmed; bool mAttacked; @@ -56,6 +55,7 @@ namespace ESM float mAttackStrength; float mFallHeight; std::string mLastHitObject; + std::string mLastHitAttemptObject; bool mRecalcDynamicStats; int mDrawState; unsigned char mDeathAnimation; @@ -65,6 +65,9 @@ namespace ESM SpellState mSpells; ActiveSpells mActiveSpells; + /// Initialize to default state + void blank(); + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/custommarkerstate.cpp b/components/esm/custommarkerstate.cpp new file mode 100644 index 0000000000..dc81c123d4 --- /dev/null +++ b/components/esm/custommarkerstate.cpp @@ -0,0 +1,26 @@ +#include "custommarkerstate.hpp" + +#include "esmwriter.hpp" +#include "esmreader.hpp" + +namespace ESM +{ + +void CustomMarker::save(ESM::ESMWriter &esm) const +{ + esm.writeHNT("POSX", mWorldX); + esm.writeHNT("POSY", mWorldY); + mCell.save(esm); + if (!mNote.empty()) + esm.writeHNString("NOTE", mNote); +} + +void CustomMarker::load(ESM::ESMReader &esm) +{ + esm.getHNT(mWorldX, "POSX"); + esm.getHNT(mWorldY, "POSY"); + mCell.load(esm); + mNote = esm.getHNOString("NOTE"); +} + +} diff --git a/components/esm/custommarkerstate.hpp b/components/esm/custommarkerstate.hpp new file mode 100644 index 0000000000..fc9286bfe2 --- /dev/null +++ b/components/esm/custommarkerstate.hpp @@ -0,0 +1,30 @@ +#ifndef OPENMW_ESM_CUSTOMMARKERSTATE_H +#define OPENMW_ESM_CUSTOMMARKERSTATE_H + +#include "cellid.hpp" + +namespace ESM +{ + +// format 0, saved games only +struct CustomMarker +{ + float mWorldX; + float mWorldY; + + ESM::CellId mCell; + + std::string mNote; + + bool operator == (const CustomMarker& other) + { + return mNote == other.mNote && mCell == other.mCell && mWorldX == other.mWorldX && mWorldY == other.mWorldY; + } + + void load (ESM::ESMReader& reader); + void save (ESM::ESMWriter& writer) const; +}; + +} + +#endif diff --git a/components/esm/defs.hpp b/components/esm/defs.hpp index d5ba919b0a..7ef8102c29 100644 --- a/components/esm/defs.hpp +++ b/components/esm/defs.hpp @@ -96,15 +96,17 @@ enum RecNameInts 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, - REC_WTHR = 0x52485457, + REC_SAVE = FourCC<'S','A','V','E'>::value, + REC_JOUR_LEGACY = FourCC<0xa4,'U','O','R'>::value, // "\xa4UOR", rather than "JOUR", little oversight when magic numbers were + // calculated by hand, needs to be supported for older files now + REC_JOUR = FourCC<'J','O','U','R'>::value, + REC_QUES = FourCC<'Q','U','E','S'>::value, + REC_GSCR = FourCC<'G','S','C','R'>::value, + REC_PLAY = FourCC<'P','L','A','Y'>::value, + REC_CSTA = FourCC<'C','S','T','A'>::value, + REC_GMAP = FourCC<'G','M','A','P'>::value, + REC_DIAS = FourCC<'D','I','A','S'>::value, + REC_WTHR = FourCC<'W','T','H','R'>::value, REC_KEYS = FourCC<'K','E','Y','S'>::value, REC_DYNA = FourCC<'D','Y','N','A'>::value, REC_ASPL = FourCC<'A','S','P','L'>::value, @@ -114,9 +116,11 @@ enum RecNameInts REC_DCOU = FourCC<'D','C','O','U'>::value, REC_MARK = FourCC<'M','A','R','K'>::value, REC_ENAB = FourCC<'E','N','A','B'>::value, + REC_CAM_ = FourCC<'C','A','M','_'>::value, + REC_STLN = FourCC<'S','T','L','N'>::value, // format 1 - REC_FILT = 0x544C4946, + REC_FILT = FourCC<'F','I','L','T'>::value, REC_DBGP = FourCC<'D','B','G','P'>::value ///< only used in project files }; diff --git a/components/esm/dialoguestate.cpp b/components/esm/dialoguestate.cpp index 14301ac198..f302e36dc8 100644 --- a/components/esm/dialoguestate.cpp +++ b/components/esm/dialoguestate.cpp @@ -13,13 +13,20 @@ void ESM::DialogueState::load (ESMReader &esm) { std::string faction = esm.getHString(); - while (esm.isNextSub ("REAC")) + while (esm.isNextSub("REA2")) { std::string faction2 = esm.getHString(); int reaction; esm.getHNT(reaction, "INTV"); + mChangedFactionReaction[faction][faction2] = reaction; + } - mModFactionReaction[faction][faction2] = reaction; + // no longer used + while (esm.isNextSub ("REAC")) + { + esm.skipHSub(); + esm.getSubName(); + esm.skipHSub(); } } } @@ -32,15 +39,15 @@ void ESM::DialogueState::save (ESMWriter &esm) const esm.writeHNString ("TOPI", *iter); } - for (std::map >::const_iterator iter = mModFactionReaction.begin(); - iter != mModFactionReaction.end(); ++iter) + for (std::map >::const_iterator iter = mChangedFactionReaction.begin(); + iter != mChangedFactionReaction.end(); ++iter) { esm.writeHNString ("FACT", iter->first); for (std::map::const_iterator reactIter = iter->second.begin(); reactIter != iter->second.end(); ++reactIter) { - esm.writeHNString ("REAC", reactIter->first); + esm.writeHNString ("REA2", reactIter->first); esm.writeHNT ("INTV", reactIter->second); } } diff --git a/components/esm/dialoguestate.hpp b/components/esm/dialoguestate.hpp index 5e5f602a30..d7cdb941c2 100644 --- a/components/esm/dialoguestate.hpp +++ b/components/esm/dialoguestate.hpp @@ -14,9 +14,11 @@ namespace ESM struct DialogueState { + // must be lower case topic IDs std::vector mKnownTopics; - std::map > mModFactionReaction; + // must be lower case faction IDs + std::map > mChangedFactionReaction; void load (ESMReader &esm); void save (ESMWriter &esm) const; diff --git a/components/esm/esmcommon.hpp b/components/esm/esmcommon.hpp index d3e6e7feae..54b18aeaf3 100644 --- a/components/esm/esmcommon.hpp +++ b/components/esm/esmcommon.hpp @@ -23,7 +23,7 @@ template union NAME_T { char name[LEN]; - int32_t val; + uint32_t val; bool operator==(const char *str) const { @@ -40,8 +40,8 @@ union NAME_T } bool operator!=(const std::string &str) const { return !((*this)==str); } - bool operator==(int v) const { return v == val; } - bool operator!=(int v) const { return v != val; } + bool operator==(uint32_t v) const { return v == val; } + bool operator!=(uint32_t v) const { return v != val; } std::string toString() const { return std::string(name, strnlen(name, LEN)); } @@ -53,18 +53,6 @@ typedef NAME_T<32> NAME32; typedef NAME_T<64> NAME64; typedef NAME_T<256> NAME256; -#pragma pack(push) -#pragma pack(1) -// Data that is only present in save game files -struct SaveData -{ - float pos[6]; // Player position and rotation - NAME64 cell; // Cell name - float unk2; // Unknown value - possibly game time? - NAME32 player; // Player name -}; -#pragma pack(pop) - /* This struct defines a file 'context' which can be saved and later restored by an ESMReader instance. It will save the position within a file, and when restored will let you read from that position as diff --git a/components/esm/esmreader.cpp b/components/esm/esmreader.cpp index 6facee381c..bbe475ff74 100644 --- a/components/esm/esmreader.cpp +++ b/components/esm/esmreader.cpp @@ -123,7 +123,7 @@ std::string ESMReader::getHString() // Skip the following zero byte mCtx.leftRec--; char c; - mEsm->read(&c, 1); + getExact(&c, 1); return ""; } @@ -134,7 +134,11 @@ void ESMReader::getHExact(void*p, int size) { getSubHeader(); if (size != static_cast (mCtx.leftSub)) - fail("getHExact() size mismatch"); + { + std::stringstream error; + error << "getHExact(): size mismatch (requested " << size << ", got " << mCtx.leftSub << ")"; + fail(error.str()); + } getExact(p, size); } @@ -182,7 +186,7 @@ void ESMReader::getSubName() } // reading the subrecord data anyway. - mEsm->read(mCtx.subName.name, 4); + getExact(mCtx.subName.name, 4); mCtx.leftRec -= 4; } @@ -190,7 +194,7 @@ bool ESMReader::isEmptyOrGetName() { if (mCtx.leftRec) { - mEsm->read(mCtx.subName.name, 4); + getExact(mCtx.subName.name, 4); mCtx.leftRec -= 4; return false; } @@ -210,6 +214,17 @@ void ESMReader::skipHSubSize(int size) fail("skipHSubSize() mismatch"); } +void ESMReader::skipHSubUntil(const char *name) +{ + while (hasMoreSubs() && !isNextSub(name)) + { + mCtx.subCached = false; + skipHSub(); + } + if (hasMoreSubs()) + mCtx.subCached = true; +} + void ESMReader::getSubHeader() { if (mCtx.leftRec < 4) @@ -279,9 +294,16 @@ void ESMReader::getRecHeader(uint32_t &flags) void ESMReader::getExact(void*x, int size) { - int t = mEsm->read(x, size); - if (t != size) - fail("Read error"); + try + { + int t = mEsm->read(x, size); + if (t != size) + fail("Read error"); + } + catch (std::exception& e) + { + fail(std::string("Read error: ") + e.what()); + } } std::string ESMReader::getString(int size) @@ -299,8 +321,7 @@ std::string ESMReader::getString(int size) char *ptr = &mBuffer[0]; getExact(ptr, size); - if (size>0 && ptr[size-1]==0) - --size; + size = strnlen(ptr, size); // Convert to UTF8 and return if (mEncoder) diff --git a/components/esm/esmreader.hpp b/components/esm/esmreader.hpp index 1549e15f53..ebbc935f6f 100644 --- a/components/esm/esmreader.hpp +++ b/components/esm/esmreader.hpp @@ -32,10 +32,11 @@ public: int getVer() const { return mHeader.mData.version; } int getRecordCount() const { return mHeader.mData.records; } - float getFVer() const { if(mHeader.mData.version == VER_12) return 1.2; else return 1.3; } + float getFVer() const { return (mHeader.mData.version == VER_12) ? 1.2f : 1.3f; } const std::string getAuthor() const { return mHeader.mData.author.toString(); } const std::string getDesc() const { return mHeader.mData.desc.toString(); } const std::vector &getGameFiles() const { return mHeader.mMaster; } + const Header& getHeader() const { return mHeader; } int getFormat() const; const NAME &retSubName() const { return mCtx.subName; } uint32_t getSubSize() const { return mCtx.leftSub; } @@ -136,7 +137,11 @@ public: { getSubHeader(); if (mCtx.leftSub != sizeof(X)) - fail("getHT(): subrecord size mismatch"); + { + std::stringstream error; + error << "getHT(): subrecord size mismatch (requested " << sizeof(X) << ", got " << mCtx.leftSub << ")"; + fail(error.str()); + } getT(x); } @@ -194,6 +199,9 @@ public: // Skip sub record and check its size void skipHSubSize(int size); + // Skip all subrecords until the given subrecord or no more subrecords remaining + void skipHSubUntil(const char* name); + /* Sub-record header. This updates leftRec beyond the current sub-record as well. leftSub contains size of current sub-record. */ diff --git a/components/esm/esmwriter.cpp b/components/esm/esmwriter.cpp index 544f8bbedf..14951608d2 100644 --- a/components/esm/esmwriter.cpp +++ b/components/esm/esmwriter.cpp @@ -4,6 +4,8 @@ #include #include +#include + namespace ESM { ESMWriter::ESMWriter() diff --git a/components/esm/esmwriter.hpp b/components/esm/esmwriter.hpp index e57c6e45d8..30cec58b46 100644 --- a/components/esm/esmwriter.hpp +++ b/components/esm/esmwriter.hpp @@ -4,11 +4,14 @@ #include #include -#include - #include "esmcommon.hpp" #include "loadtes3.hpp" +namespace ToUTF8 +{ + class Utf8Encoder; +} + namespace ESM { class ESMWriter diff --git a/components/esm/globalscript.hpp b/components/esm/globalscript.hpp index 43c859e095..8b7e627953 100644 --- a/components/esm/globalscript.hpp +++ b/components/esm/globalscript.hpp @@ -12,7 +12,7 @@ namespace ESM struct GlobalScript { - std::string mId; + std::string mId; /// \note must be lowercase Locals mLocals; int mRunning; std::string mTargetId; // for targeted scripts diff --git a/components/esm/inventorystate.cpp b/components/esm/inventorystate.cpp index 2154faa830..4eaaa9f9f1 100644 --- a/components/esm/inventorystate.cpp +++ b/components/esm/inventorystate.cpp @@ -4,52 +4,33 @@ #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) { + int index = 0; while (esm.isNextSub ("IOBJ")) { - unsigned int id = 0; - esm.getHT (id); + int unused; // no longer used + esm.getHT(unused); - if (id==ESM::REC_LIGH) + ObjectState state; + + // obsolete + if (esm.isNextSub("SLOT")) { - LightState state; int slot; - read (esm, state, slot); - if (state.mCount == 0) - continue; - mLights.push_back (std::make_pair (state, slot)); - } - else - { - ObjectState state; - int slot; - read (esm, state, slot); - if (state.mCount == 0) - continue; - mItems.push_back (std::make_pair (state, std::make_pair (id, slot))); + esm.getHT(slot); + mEquipmentSlots[index] = slot; } + + state.mRef.loadId(esm, true); + state.load (esm); + + if (state.mCount == 0) + continue; + + mItems.push_back (state); + + ++index; } while (esm.isNextSub("LEVM")) @@ -59,20 +40,72 @@ void ESM::InventoryState::load (ESMReader &esm) esm.getHNT (count, "COUN"); mLevelledItemMap[id] = count; } + + while (esm.isNextSub("MAGI")) + { + std::string id = esm.getHString(); + + std::vector > params; + while (esm.isNextSub("RAND")) + { + float rand, multiplier; + esm.getHT (rand); + esm.getHNT (multiplier, "MULT"); + params.push_back(std::make_pair(rand, multiplier)); + } + mPermanentMagicEffectMagnitudes[id] = params; + } + + while (esm.isNextSub("EQUI")) + { + esm.getSubHeader(); + int index; + esm.getT(index); + int slot; + esm.getT(slot); + mEquipmentSlots[index] = slot; + } + + mSelectedEnchantItem = -1; + esm.getHNOT(mSelectedEnchantItem, "SELE"); } void ESM::InventoryState::save (ESMWriter &esm) const { - for (std::vector > >::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter) - write (esm, iter->first, iter->second.first, iter->second.second); + for (std::vector::const_iterator iter (mItems.begin()); iter!=mItems.end(); ++iter) + { + int unused = 0; + esm.writeHNT ("IOBJ", unused); - for (std::vector >::const_iterator iter (mLights.begin()); - iter!=mLights.end(); ++iter) - write (esm, iter->first, ESM::REC_LIGH, iter->second); + iter->save (esm, true); + } for (std::map::const_iterator it = mLevelledItemMap.begin(); it != mLevelledItemMap.end(); ++it) { esm.writeHNString ("LEVM", it->first); esm.writeHNT ("COUN", it->second); } + + for (TEffectMagnitudes::const_iterator it = mPermanentMagicEffectMagnitudes.begin(); it != mPermanentMagicEffectMagnitudes.end(); ++it) + { + esm.writeHNString("MAGI", it->first); + + const std::vector >& params = it->second; + for (std::vector >::const_iterator pIt = params.begin(); pIt != params.end(); ++pIt) + { + esm.writeHNT ("RAND", pIt->first); + esm.writeHNT ("MULT", pIt->second); + } + } + + for (std::map::const_iterator it = mEquipmentSlots.begin(); it != mEquipmentSlots.end(); ++it) + { + esm.startSubRecord("EQUI"); + esm.writeT(it->first); + esm.writeT(it->second); + esm.endRecord("EQUI"); + } + + if (mSelectedEnchantItem != -1) + esm.writeHNT ("SELE", mSelectedEnchantItem); } diff --git a/components/esm/inventorystate.hpp b/components/esm/inventorystate.hpp index 4aa79f575b..d5c317beb9 100644 --- a/components/esm/inventorystate.hpp +++ b/components/esm/inventorystate.hpp @@ -4,7 +4,6 @@ #include #include "objectstate.hpp" -#include "lightstate.hpp" namespace ESM { @@ -16,14 +15,19 @@ namespace ESM /// \brief State for inventories and containers struct InventoryState { - // anything but lights (type, slot) - std::vector > > mItems; + std::vector mItems; - // lights (slot) - std::vector > mLights; + // + std::map mEquipmentSlots; std::map mLevelledItemMap; + typedef std::map > > TEffectMagnitudes; + TEffectMagnitudes mPermanentMagicEffectMagnitudes; + + int mSelectedEnchantItem; // For inventories only + + InventoryState() : mSelectedEnchantItem(-1) {} virtual ~InventoryState() {} virtual void load (ESMReader &esm); diff --git a/components/esm/lightstate.cpp b/components/esm/lightstate.cpp deleted file mode 100644 index 1ef0408237..0000000000 --- a/components/esm/lightstate.cpp +++ /dev/null @@ -1,21 +0,0 @@ - -#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 deleted file mode 100644 index a22735e079..0000000000 --- a/components/esm/lightstate.hpp +++ /dev/null @@ -1,19 +0,0 @@ -#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/loadarmo.cpp b/components/esm/loadarmo.cpp index f8c3a4718a..066551d6fe 100644 --- a/components/esm/loadarmo.cpp +++ b/components/esm/loadarmo.cpp @@ -7,52 +7,87 @@ namespace ESM { -void PartReferenceList::load(ESMReader &esm) -{ - mParts.clear(); - while (esm.isNextSub("INDX")) + void PartReferenceList::add(ESMReader &esm) { PartReference pr; esm.getHT(pr.mPart); // The INDX byte pr.mMale = esm.getHNOString("BNAM"); pr.mFemale = esm.getHNOString("CNAM"); mParts.push_back(pr); - } -} -void PartReferenceList::save(ESMWriter &esm) const -{ - for (std::vector::const_iterator it = mParts.begin(); it != mParts.end(); ++it) + } + + void PartReferenceList::load(ESMReader &esm) { - esm.writeHNT("INDX", it->mPart); - esm.writeHNOString("BNAM", it->mMale); - esm.writeHNOString("CNAM", it->mFemale); + mParts.clear(); + while (esm.isNextSub("INDX")) + { + add(esm); + } } -} -unsigned int Armor::sRecordId = REC_ARMO; + void PartReferenceList::save(ESMWriter &esm) const + { + for (std::vector::const_iterator it = mParts.begin(); it != mParts.end(); ++it) + { + esm.writeHNT("INDX", it->mPart); + esm.writeHNOString("BNAM", it->mMale); + esm.writeHNOString("CNAM", it->mFemale); + } + } -void Armor::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - mScript = esm.getHNOString("SCRI"); - esm.getHNT(mData, "AODT", 24); - mIcon = esm.getHNOString("ITEX"); - mParts.load(esm); - mEnchant = esm.getHNOString("ENAM"); -} + unsigned int Armor::sRecordId = REC_ARMO; -void Armor::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNT("AODT", mData, 24); - esm.writeHNOCString("ITEX", mIcon); - mParts.save(esm); - esm.writeHNOCString("ENAM", mEnchant); -} + void Armor::load(ESMReader &esm) + { + mParts.mParts.clear(); + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'A','O','D','T'>::value: + esm.getHT(mData, 24); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEnchant = esm.getHString(); + break; + case ESM::FourCC<'I','N','D','X'>::value: + mParts.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing CTDT subrecord"); + } + + void Armor::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNT("AODT", mData, 24); + esm.writeHNOCString("ITEX", mIcon); + mParts.save(esm); + esm.writeHNOCString("ENAM", mEnchant); + } void Armor::blank() { diff --git a/components/esm/loadarmo.hpp b/components/esm/loadarmo.hpp index 6be9dd9717..356dfc1c5e 100644 --- a/components/esm/loadarmo.hpp +++ b/components/esm/loadarmo.hpp @@ -55,6 +55,10 @@ struct PartReferenceList { std::vector mParts; + /// Load one part, assumes the subrecord name was already read + void add(ESMReader &esm); + + /// TODO: remove this method. The ESM format does not guarantee that all Part subrecords follow one another. void load(ESMReader &esm); void save(ESMWriter &esm) const; }; diff --git a/components/esm/loadbook.cpp b/components/esm/loadbook.cpp index c8b7e94789..47f52fc31a 100644 --- a/components/esm/loadbook.cpp +++ b/components/esm/loadbook.cpp @@ -8,26 +8,54 @@ namespace ESM { unsigned int Book::sRecordId = REC_BOOK; -void Book::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "BKDT", 20); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); - mText = esm.getHNOString("TEXT"); - mEnchant = esm.getHNOString("ENAM"); -} -void Book::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("BKDT", mData, 20); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); - esm.writeHNOString("TEXT", mText); - esm.writeHNOCString("ENAM", mEnchant); -} + void Book::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'B','K','D','T'>::value: + esm.getHT(mData, 20); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEnchant = esm.getHString(); + break; + case ESM::FourCC<'T','E','X','T'>::value: + mText = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing BKDT subrecord"); + } + void Book::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("BKDT", mData, 20); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + esm.writeHNOString("TEXT", mText); + esm.writeHNOCString("ENAM", mEnchant); + } void Book::blank() { diff --git a/components/esm/loadcell.cpp b/components/esm/loadcell.cpp index 347f3fde4a..e4f847dec2 100644 --- a/components/esm/loadcell.cpp +++ b/components/esm/loadcell.cpp @@ -242,6 +242,8 @@ bool Cell::getNextMVRF(ESMReader &esm, MovedCellRef &mref) else { id.mWorldspace = Misc::StringUtils::lowerCase (mName); + id.mIndex.mX = 0; + id.mIndex.mY = 0; } return id; diff --git a/components/esm/loadcell.hpp b/components/esm/loadcell.hpp index a24b106d40..d982bbfd40 100644 --- a/components/esm/loadcell.hpp +++ b/components/esm/loadcell.hpp @@ -18,7 +18,7 @@ namespace ESM { class ESMReader; class ESMWriter; - class CellId; +struct 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 @@ -78,7 +78,13 @@ struct Cell float mFogDensity; }; - Cell() : mWater(0) {} + Cell() : mWater(0), + mName(""), + mRegion(""), + mWaterInt(false), + mMapColor(0), + mRefNumCounter(0) + {} // Interior cells are indexed by this (it's the 'id'), for exterior // cells it is optional. @@ -131,7 +137,7 @@ struct Cell bool hasWater() const { - return (mData.mFlags&HasWater); + return (mData.mFlags&HasWater) != 0; } // Restore the given reader to the stored position. Will try to open diff --git a/components/esm/loadclot.cpp b/components/esm/loadclot.cpp index 17ecdf3ae5..5f49b5e709 100644 --- a/components/esm/loadclot.cpp +++ b/components/esm/loadclot.cpp @@ -10,17 +10,42 @@ namespace ESM void Clothing::load(ESMReader &esm) { - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "CTDT", 12); - - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); - - mParts.load(esm); - - - mEnchant = esm.getHNOString("ENAM"); + mParts.mParts.clear(); + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'C','T','D','T'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEnchant = esm.getHString(); + break; + case ESM::FourCC<'I','N','D','X'>::value: + mParts.add(esm); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing CTDT subrecord"); } void Clothing::save(ESMWriter &esm) const diff --git a/components/esm/loadcont.cpp b/components/esm/loadcont.cpp index 51a385f069..f48f069306 100644 --- a/components/esm/loadcont.cpp +++ b/components/esm/loadcont.cpp @@ -7,14 +7,19 @@ namespace ESM { +void InventoryList::add(ESMReader &esm) +{ + ContItem ci; + esm.getHT(ci, 36); + mList.push_back(ci); +} + void InventoryList::load(ESMReader &esm) { mList.clear(); - ContItem ci; while (esm.isNextSub("NPCO")) { - esm.getHT(ci, 36); - mList.push_back(ci); + add(esm); } } diff --git a/components/esm/loadcont.hpp b/components/esm/loadcont.hpp index 2808b67b56..93db94759d 100644 --- a/components/esm/loadcont.hpp +++ b/components/esm/loadcont.hpp @@ -26,6 +26,10 @@ struct InventoryList { std::vector mList; + /// Load one item, assumes subrecord name is already read + void add(ESMReader &esm); + + /// TODO: remove this method, the ESM format doesn't guarantee that all ContItems follow one another void load(ESMReader &esm); void save(ESMWriter &esm) const; }; diff --git a/components/esm/loadcrec.hpp b/components/esm/loadcrec.hpp deleted file mode 100644 index 280739acad..0000000000 --- a/components/esm/loadcrec.hpp +++ /dev/null @@ -1,49 +0,0 @@ -#ifndef OPENMW_ESM_CREC_H -#define OPENMW_ESM_CREC_H - -#include - -// TODO create implementation files and remove this one -#include "esmreader.hpp" - -namespace ESM { - -class ESMReader; -class ESMWriter; - -/* These two are only used in save games. They are not decoded yet. - */ - -/// Changes a creature -struct LoadCREC -{ - static unsigned int sRecordId; - - std::string mId; - - void load(ESMReader &esm) - { - esm.skipRecord(); - } - - void save(ESMWriter &esm) const - { - } -}; - -/// Changes an item list / container -struct LoadCNTC -{ - std::string mId; - - void load(ESMReader &esm) - { - esm.skipRecord(); - } - - void save(ESMWriter &esm) const - { - } -}; -} -#endif diff --git a/components/esm/loaddoor.cpp b/components/esm/loaddoor.cpp index c56b063379..f446eed611 100644 --- a/components/esm/loaddoor.cpp +++ b/components/esm/loaddoor.cpp @@ -8,23 +8,43 @@ namespace ESM { unsigned int Door::sRecordId = REC_DOOR; -void Door::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - mScript = esm.getHNOString("SCRI"); - mOpenSound = esm.getHNOString("SNAM"); - mCloseSound = esm.getHNOString("ANAM"); -} + void Door::load(ESMReader &esm) + { + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'S','N','A','M'>::value: + mOpenSound = esm.getHString(); + break; + case ESM::FourCC<'A','N','A','M'>::value: + mCloseSound = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + } -void Door::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("SNAM", mOpenSound); - esm.writeHNOCString("ANAM", mCloseSound); -} + void Door::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("SNAM", mOpenSound); + esm.writeHNOCString("ANAM", mCloseSound); + } void Door::blank() { diff --git a/components/esm/loadinfo.hpp b/components/esm/loadinfo.hpp index 0c0d662a8b..59b1af31a2 100644 --- a/components/esm/loadinfo.hpp +++ b/components/esm/loadinfo.hpp @@ -32,7 +32,11 @@ struct DialInfo struct DATAstruct { int mUnknown1; - int mDisposition; + union + { + int mDisposition; // Used for dialogue responses + int mJournalIndex; // Used for journal entries + }; signed char mRank; // Rank of NPC signed char mGender; // See Gender enum signed char mPCrank; // Player rank diff --git a/components/esm/loadland.cpp b/components/esm/loadland.cpp index 91b062596b..ae73eee521 100644 --- a/components/esm/loadland.cpp +++ b/components/esm/loadland.cpp @@ -72,7 +72,6 @@ Land::Land() , mDataLoaded(false) , mLandData(NULL) , mPlugin(0) - , mHasData(false) { } @@ -97,8 +96,6 @@ void Land::load(ESMReader &esm) // Store the file position mContext = esm.getContext(); - mHasData = false; - // Skip these here. Load the actual data when the cell is loaded. if (esm.isNextSub("VNML")) { @@ -126,10 +123,6 @@ void Land::load(ESMReader &esm) mDataTypes |= DATA_VTEX; } - // We need all three of VNML, VHGT and VTEX in order to use the - // landscape. (Though Morrowind seems to accept terrain without VTEX/VCLR entries) - mHasData = mDataTypes & (DATA_VNML|DATA_VHGT|DATA_WNAM); - mDataLoaded = 0; mLandData = NULL; } @@ -144,13 +137,12 @@ void Land::save(ESMWriter &esm) const esm.writeHNT("DATA", mFlags); } -/// \todo remove memory allocation when only defaults needed void Land::loadData(int flags) { // Try to load only available data - int actual = flags & mDataTypes; + flags = flags & mDataTypes; // Return if all required data is loaded - if (flags == 0 || (actual != 0 && (mDataLoaded & actual) == actual)) { + if ((mDataLoaded & flags) == flags) { return; } // Create storage if nothing is loaded @@ -160,15 +152,13 @@ void Land::loadData(int flags) } mEsm->restoreContext(mContext); - memset(mLandData->mNormals, 0, sizeof(mLandData->mNormals)); - if (mEsm->isNextSub("VNML")) { - condLoad(actual, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); + condLoad(flags, DATA_VNML, mLandData->mNormals, sizeof(mLandData->mNormals)); } if (mEsm->isNextSub("VHGT")) { static VHGT vhgt; - if (condLoad(actual, DATA_VHGT, &vhgt, sizeof(vhgt))) { + if (condLoad(flags, DATA_VHGT, &vhgt, sizeof(vhgt))) { float rowOffset = vhgt.mHeightOffset; for (int y = 0; y < LAND_SIZE; y++) { rowOffset += vhgt.mHeightData[y * LAND_SIZE]; @@ -184,30 +174,18 @@ void Land::loadData(int flags) mLandData->mUnk1 = vhgt.mUnk1; mLandData->mUnk2 = vhgt.mUnk2; } - } else if ((flags & DATA_VHGT) && (mDataLoaded & DATA_VHGT) == 0) { - for (int i = 0; i < LAND_NUM_VERTS; ++i) { - mLandData->mHeights[i] = -256.0f * HEIGHT_SCALE; - } - mDataLoaded |= DATA_VHGT; } if (mEsm->isNextSub("WNAM")) { - condLoad(actual, DATA_WNAM, mLandData->mWnam, 81); - } - if (mEsm->isNextSub("VCLR")) { - mLandData->mUsingColours = true; - condLoad(actual, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); - } else { - mLandData->mUsingColours = false; + condLoad(flags, DATA_WNAM, mLandData->mWnam, 81); } + if (mEsm->isNextSub("VCLR")) + condLoad(flags, DATA_VCLR, mLandData->mColours, 3 * LAND_NUM_VERTS); if (mEsm->isNextSub("VTEX")) { static uint16_t vtex[LAND_NUM_TEXTURES]; - if (condLoad(actual, DATA_VTEX, vtex, sizeof(vtex))) { + if (condLoad(flags, DATA_VTEX, vtex, sizeof(vtex))) { LandData::transposeTextureData(vtex, mLandData->mTextures); } - } else if ((flags & DATA_VTEX) && (mDataLoaded & DATA_VTEX) == 0) { - memset(mLandData->mTextures, 0, sizeof(mLandData->mTextures)); - mDataLoaded |= DATA_VTEX; } } @@ -232,4 +210,9 @@ bool Land::condLoad(int flags, int dataFlag, void *ptr, unsigned int size) return false; } +bool Land::isDataLoaded(int flags) const +{ + return (mDataLoaded & flags) == (flags & mDataTypes); +} + } diff --git a/components/esm/loadland.hpp b/components/esm/loadland.hpp index 0020669815..e510616aff 100644 --- a/components/esm/loadland.hpp +++ b/components/esm/loadland.hpp @@ -32,7 +32,6 @@ struct Land ESMReader* mEsm; ESM_Context mContext; - bool mHasData; int mDataTypes; int mDataLoaded; @@ -81,16 +80,12 @@ struct Land VNML mNormals[LAND_NUM_VERTS * 3]; uint16_t mTextures[LAND_NUM_TEXTURES]; - bool mUsingColours; char mColours[3 * LAND_NUM_VERTS]; int mDataTypes; - // WNAM appears to contain the global map image for this cell. Probably a palette-based format, - // since there's only 1 byte for each pixel. - // Currently unused (global map is drawn on the fly in OpenMW, takes ~1/2 second at startup for Morrowind.esm). - // The problem with using the original data is that we would need to exactly replicate the TES CS's algorithm - // for drawing the global map in OpenCS, in order to get seamless edges when creating landmass mods. - uint8_t mWnam[81]; + // low-LOD heightmap (used for rendering the global map) + signed char mWnam[81]; + short mUnk1; uint8_t mUnk2; @@ -116,10 +111,8 @@ struct Land void unloadData(); /// Check if given data type is loaded - /// \todo reimplement this - bool isDataLoaded(int flags) { - return (mDataLoaded & flags) == flags; - } + /// @note We only check data types that *can* be loaded (present in mDataTypes) + bool isDataLoaded(int flags) const; private: Land(const Land& land); diff --git a/components/esm/loadlevlist.cpp b/components/esm/loadlevlist.cpp index 6385b9a718..2cbfac0b40 100644 --- a/components/esm/loadlevlist.cpp +++ b/components/esm/loadlevlist.cpp @@ -7,7 +7,7 @@ namespace ESM { -void LeveledListBase::load(ESMReader &esm) +void LevelledListBase::load(ESMReader &esm) { esm.getHNT(mFlags, "DATA"); esm.getHNT(mChanceNone, "NNAM"); @@ -19,13 +19,17 @@ void LeveledListBase::load(ESMReader &esm) mList.resize(len); } else + { + esm.skipRecord(); return; + } - // TODO: Merge with an existing lists here. This can be done - // simply by adding the lists together, making sure that they are - // sorted by level. A better way might be to exclude repeated - // items. Also, some times we don't want to merge lists, just - // overwrite. Figure out a way to give the user this option. + // If this levelled list was already loaded by a previous content file, + // we overwrite the list. Merging lists should probably be left to external tools, + // with the limited amount of information there is in the records, all merging methods + // will be flawed in some way. For a proper fix the ESM format would have to be changed + // to actually track list changes instead of including the whole list for every file + // that does something with that list. for (size_t i = 0; i < mList.size(); i++) { @@ -34,7 +38,7 @@ void LeveledListBase::load(ESMReader &esm) esm.getHNT(li.mLevel, "INTV"); } } -void LeveledListBase::save(ESMWriter &esm) const +void LevelledListBase::save(ESMWriter &esm) const { esm.writeHNT("DATA", mFlags); esm.writeHNT("NNAM", mChanceNone); @@ -47,7 +51,7 @@ void LeveledListBase::save(ESMWriter &esm) const } } - void LeveledListBase::blank() + void LevelledListBase::blank() { mFlags = 0; mChanceNone = 0; diff --git a/components/esm/loadlevlist.hpp b/components/esm/loadlevlist.hpp index a4e1b85c2d..bcea2b234c 100644 --- a/components/esm/loadlevlist.hpp +++ b/components/esm/loadlevlist.hpp @@ -11,14 +11,14 @@ class ESMReader; class ESMWriter; /* - * Leveled lists. Since these have identical layout, I only bothered + * Levelled lists. Since these have identical layout, I only bothered * to implement it once. * - * We should later implement the ability to merge leveled lists from + * We should later implement the ability to merge levelled lists from * several files. */ -struct LeveledListBase +struct LevelledListBase { int mFlags; unsigned char mChanceNone; // Chance that none are selected (0-100) @@ -43,7 +43,7 @@ struct LeveledListBase ///< Set record to default state (does not touch the ID). }; -struct CreatureLevList: LeveledListBase +struct CreatureLevList: LevelledListBase { static unsigned int sRecordId; @@ -61,7 +61,7 @@ struct CreatureLevList: LeveledListBase } }; -struct ItemLevList: LeveledListBase +struct ItemLevList: LevelledListBase { static unsigned int sRecordId; @@ -72,7 +72,7 @@ struct ItemLevList: LeveledListBase // list is instantiated, instead of // giving several identical items // (used when a container has more - // than one instance of one leveled + // than one instance of one levelled // list.) AllLevels = 0x02 // Calculate from all levels <= player // level, not just the closest below diff --git a/components/esm/loadligh.hpp b/components/esm/loadligh.hpp index ffc2916099..2c83248f8a 100644 --- a/components/esm/loadligh.hpp +++ b/components/esm/loadligh.hpp @@ -37,7 +37,7 @@ struct Light int mValue; int mTime; // Duration int mRadius; - int mColor; // 4-byte rgba value + unsigned int mColor; // 4-byte rgba value int mFlags; }; // Size = 24 bytes diff --git a/components/esm/loadmgef.cpp b/components/esm/loadmgef.cpp index cbdca3e317..3ec07a2a91 100644 --- a/components/esm/loadmgef.cpp +++ b/components/esm/loadmgef.cpp @@ -191,33 +191,64 @@ namespace ESM void MagicEffect::load(ESMReader &esm) { - esm.getHNT(mIndex, "INDX"); + esm.getHNT(mIndex, "INDX"); mId = indexToId (mIndex); - esm.getHNT(mData, "MEDT", 36); - if (esm.getFormat() == 0) - { - // don't allow mods to change fixed flags in the legacy format - mData.mFlags &= (AllowSpellmaking | AllowEnchanting | NegativeLight); - if (mIndex>=0 && mIndex=0 && mIndex::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'P','T','E','X'>::value: + mParticle = esm.getHString(); + break; + case ESM::FourCC<'B','S','N','D'>::value: + mBoltSound = esm.getHString(); + break; + case ESM::FourCC<'C','S','N','D'>::value: + mCastSound = esm.getHString(); + break; + case ESM::FourCC<'H','S','N','D'>::value: + mHitSound = esm.getHString(); + break; + case ESM::FourCC<'A','S','N','D'>::value: + mAreaSound = esm.getHString(); + break; + case ESM::FourCC<'C','V','F','X'>::value: + mCasting = esm.getHString(); + break; + case ESM::FourCC<'B','V','F','X'>::value: + mBolt = esm.getHString(); + break; + case ESM::FourCC<'H','V','F','X'>::value: + mHit = esm.getHString(); + break; + case ESM::FourCC<'A','V','F','X'>::value: + mArea = esm.getHString(); + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord " + esm.retSubName().toString()); + } + } } void MagicEffect::save(ESMWriter &esm) const { diff --git a/components/esm/loadmisc.cpp b/components/esm/loadmisc.cpp index 2ca09e8aec..81c094f2bc 100644 --- a/components/esm/loadmisc.cpp +++ b/components/esm/loadmisc.cpp @@ -8,22 +8,45 @@ namespace ESM { unsigned int Miscellaneous::sRecordId = REC_MISC; -void Miscellaneous::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "MCDT", 12); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); -} -void Miscellaneous::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("MCDT", mData, 12); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); -} + void Miscellaneous::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'M','C','D','T'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + } + } + if (!hasData) + esm.fail("Missing MCDT subrecord"); + } + + void Miscellaneous::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("MCDT", mData, 12); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + } void Miscellaneous::blank() { diff --git a/components/esm/loadnpc.cpp b/components/esm/loadnpc.cpp index ef4b5211bb..3d617241b6 100644 --- a/components/esm/loadnpc.cpp +++ b/components/esm/loadnpc.cpp @@ -8,93 +8,137 @@ namespace ESM { unsigned int NPC::sRecordId = REC_NPC_; -void NPC::load(ESMReader &esm) -{ - mPersistent = esm.getRecordFlags() & 0x0400; - - mModel = esm.getHNOString("MODL"); - mName = esm.getHNOString("FNAM"); - - mRace = esm.getHNString("RNAM"); - mClass = esm.getHNString("CNAM"); - mFaction = esm.getHNString("ANAM"); - mHead = esm.getHNString("BNAM"); - mHair = esm.getHNString("KNAM"); - - mScript = esm.getHNOString("SCRI"); - - esm.getSubNameIs("NPDT"); - esm.getSubHeader(); - if (esm.getSubSize() == 52) + void NPC::load(ESMReader &esm) { - mNpdtType = NPC_DEFAULT; - esm.getExact(&mNpdt52, 52); - } - else if (esm.getSubSize() == 12) - { - mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; - esm.getExact(&mNpdt12, 12); - } - else - esm.fail("NPC_NPDT must be 12 or 52 bytes long"); + mPersistent = esm.getRecordFlags() & 0x0400; - esm.getHNT(mFlags, "FLAG"); + mSpells.mList.clear(); + mInventory.mList.clear(); + mTransport.clear(); + mAiPackage.mList.clear(); - mInventory.load(esm); - mSpells.load(esm); - - if (esm.isNextSub("AIDT")) - { - esm.getHExact(&mAiData, sizeof(mAiData)); - mHasAI= true; - } - else + bool hasNpdt = false; + bool hasFlags = false; mHasAI = false; - - mTransport.clear(); - while (esm.isNextSub("DODT") || esm.isNextSub("DNAM")) { - if (esm.retSubName() == 0x54444f44) { // DODT struct - Dest dodt; - esm.getHExact(&dodt.mPos, 24); - mTransport.push_back(dodt); - } else if (esm.retSubName() == 0x4d414e44) { // DNAM struct - mTransport.back().mCellName = esm.getHString(); + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','N','A','M'>::value: + mRace = esm.getHString(); + break; + case ESM::FourCC<'C','N','A','M'>::value: + mClass = esm.getHString(); + break; + case ESM::FourCC<'A','N','A','M'>::value: + mFaction = esm.getHString(); + break; + case ESM::FourCC<'B','N','A','M'>::value: + mHead = esm.getHString(); + break; + case ESM::FourCC<'K','N','A','M'>::value: + mHair = esm.getHString(); + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'N','P','D','T'>::value: + hasNpdt = true; + esm.getSubHeader(); + if (esm.getSubSize() == 52) + { + mNpdtType = NPC_DEFAULT; + esm.getExact(&mNpdt52, 52); + } + else if (esm.getSubSize() == 12) + { + mNpdtType = NPC_WITH_AUTOCALCULATED_STATS; + esm.getExact(&mNpdt12, 12); + } + else + esm.fail("NPC_NPDT must be 12 or 52 bytes long"); + break; + case ESM::FourCC<'F','L','A','G'>::value: + hasFlags = true; + esm.getHT(mFlags); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mSpells.add(esm); + break; + case ESM::FourCC<'N','P','C','O'>::value: + mInventory.add(esm); + break; + case ESM::FourCC<'A','I','D','T'>::value: + esm.getHExact(&mAiData, sizeof(mAiData)); + mHasAI= true; + break; + case ESM::FourCC<'D','O','D','T'>::value: + { + Dest dodt; + esm.getHExact(&dodt.mPos, 24); + mTransport.push_back(dodt); + break; + } + case ESM::FourCC<'D','N','A','M'>::value: + mTransport.back().mCellName = esm.getHString(); + break; + case AI_Wander: + case AI_Activate: + case AI_Escort: + case AI_Follow: + case AI_Travel: + case AI_CNDT: + mAiPackage.add(esm); + break; + default: + esm.fail("Unknown subrecord " + esm.retSubName().toString()); + } } + if (!hasNpdt) + esm.fail("Missing NPDT subrecord"); + if (!hasFlags) + esm.fail("Missing FLAG subrecord"); } - mAiPackage.load(esm); -} -void NPC::save(ESMWriter &esm) const -{ - esm.writeHNOCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNCString("RNAM", mRace); - esm.writeHNCString("CNAM", mClass); - esm.writeHNCString("ANAM", mFaction); - esm.writeHNCString("BNAM", mHead); - esm.writeHNCString("KNAM", mHair); - esm.writeHNOCString("SCRI", mScript); + void NPC::save(ESMWriter &esm) const + { + esm.writeHNOCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNCString("RNAM", mRace); + esm.writeHNCString("CNAM", mClass); + esm.writeHNCString("ANAM", mFaction); + esm.writeHNCString("BNAM", mHead); + esm.writeHNCString("KNAM", mHair); + esm.writeHNOCString("SCRI", mScript); - if (mNpdtType == NPC_DEFAULT) - esm.writeHNT("NPDT", mNpdt52, 52); - else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) - esm.writeHNT("NPDT", mNpdt12, 12); + if (mNpdtType == NPC_DEFAULT) + esm.writeHNT("NPDT", mNpdt52, 52); + else if (mNpdtType == NPC_WITH_AUTOCALCULATED_STATS) + esm.writeHNT("NPDT", mNpdt12, 12); - esm.writeHNT("FLAG", mFlags); + esm.writeHNT("FLAG", mFlags); - mInventory.save(esm); - mSpells.save(esm); - if (mHasAI) { - esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + mInventory.save(esm); + mSpells.save(esm); + if (mHasAI) { + esm.writeHNT("AIDT", mAiData, sizeof(mAiData)); + } + + typedef std::vector::const_iterator DestIter; + for (DestIter it = mTransport.begin(); it != mTransport.end(); ++it) { + esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); + esm.writeHNOCString("DNAM", it->mCellName); + } + mAiPackage.save(esm); } - typedef std::vector::const_iterator DestIter; - for (DestIter it = mTransport.begin(); it != mTransport.end(); ++it) { - esm.writeHNT("DODT", it->mPos, sizeof(it->mPos)); - esm.writeHNOCString("DNAM", it->mCellName); - } - mAiPackage.save(esm); -} - bool NPC::isMale() const { return (mFlags & Female) == 0; } @@ -144,4 +188,14 @@ void NPC::save(ESMWriter &esm) const mHair.clear(); mHead.clear(); } + + int NPC::getFactionRank() const + { + if (mFaction.empty()) + return -1; + else if (mNpdtType == ESM::NPC::NPC_WITH_AUTOCALCULATED_STATS) + return mNpdt12.mRank; + else // NPC_DEFAULT + return mNpdt52.mRank; + } } diff --git a/components/esm/loadnpc.hpp b/components/esm/loadnpc.hpp index 0e90108c32..9dc3be513a 100644 --- a/components/esm/loadnpc.hpp +++ b/components/esm/loadnpc.hpp @@ -110,6 +110,8 @@ struct NPC NPDTstruct52 mNpdt52; NPDTstruct12 mNpdt12; //for autocalculated characters + int getFactionRank() const; /// wrapper for mNpdt*, -1 = no rank + int mFlags; bool mPersistent; diff --git a/components/esm/loadnpcc.hpp b/components/esm/loadnpcc.hpp deleted file mode 100644 index c87c2545f7..0000000000 --- a/components/esm/loadnpcc.hpp +++ /dev/null @@ -1,94 +0,0 @@ -#ifndef OPENMW_ESM_NPCC_H -#define OPENMW_ESM_NPCC_H - -#include - -// TODO: create implementation files to remove this -#include "esmreader.hpp" - -namespace ESM { - -class ESMReader; -class ESMWriter; - -/* - * NPC change information (found in savegame files only). We can't - * read these yet. - * - * Some general observations about savegames: - * - * Magical items/potions/spells/etc are added normally as new ALCH, - * SPEL, etc. records, with unique numeric identifiers. - * - * Books with ability enhancements are listed in the save if they have - * been read. - * - * GLOB records set global variables. - * - * SCPT records do not define new scripts, but assign values to the - * variables of existing ones. - * - * STLN - stolen items, ONAM is the owner - * - * GAME - contains a GMDT (game data) of unknown format - * - * VFXM, SPLM, KLST - no clue - * - * PCDT - seems to contain a lot of DNAMs, strings? - * - * FMAP - MAPH and MAPD, probably map data. - * - * JOUR - the entire journal in html - * - * QUES - seems to contain all the quests in the game, not just the - * ones you have done or begun. - * - * REGN - lists all regions in the game, even unvisited ones. - * - * The DIAL/INFO blocks contain changes to characters' dialog status. - * - * Dammit there's a lot of stuff in there! Should really have - * suspected as much. The strategy further is to completely ignore - * save files for the time being. - * - * Several records have a "change" variant, like NPCC, CNTC - * (contents), and CREC (creature.) These seem to alter specific - * instances of creatures, npcs, etc. I have not identified most of - * their subrecords yet. - * - * Several NPCC records have names that begin with "chargen ", I don't - * know if it means something special yet. - * - * The CNTC blocks seem to be instances of leveled lists. When a - * container is supposed to contain this leveled list of this type, - * but is referenced elsewhere in the file by an INDX, the CNTC with - * the corresponding leveled list identifier and INDX will determine - * the container contents instead. - * - * Some classes of objects seem to be altered, and these include an - * INDX, which is probably an index used by specific references other - * places within the save file. I guess this means 'use this class for - * these objects, not the general class.' All the indices I have - * encountered so far are zero, but they have been for different - * classes (different containers, really) so possibly we start from - * zero for each class. This looks like a mess, but is probably still - * easier than to duplicate everything. I think WRITING this format - * will be harder than reading it. - */ - -struct LoadNPCC -{ - static unsigned int sRecordId; - - std::string mId; - - void load(ESMReader &esm) - { - esm.skipRecord(); - } - void save(ESMWriter &esm) const - { - } -}; -} -#endif diff --git a/components/esm/loadpgrd.cpp b/components/esm/loadpgrd.cpp index 7fdc9a43ca..61f56b5117 100644 --- a/components/esm/loadpgrd.cpp +++ b/components/esm/loadpgrd.cpp @@ -8,18 +8,28 @@ namespace ESM { unsigned int Pathgrid::sRecordId = REC_PGRD; - Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) { + Pathgrid::Point& Pathgrid::Point::operator=(const float rhs[3]) + { mX = rhs[0]; mY = rhs[1]; mZ = rhs[2]; + mAutogenerated = 0; + mConnectionNum = 0; + mUnknown = 0; return *this; } - Pathgrid::Point::Point(const float rhs[3]) { + Pathgrid::Point::Point(const float rhs[3]) + : mAutogenerated(0), + mConnectionNum(0), + mUnknown(0) + { mX = rhs[0]; mY = rhs[1]; mZ = rhs[2]; } - Pathgrid::Point::Point():mX(0),mY(0),mZ(0) { + Pathgrid::Point::Point():mX(0),mY(0),mZ(0),mAutogenerated(0), + mConnectionNum(0),mUnknown(0) + { } void Pathgrid::load(ESMReader &esm) diff --git a/components/esm/loadrace.cpp b/components/esm/loadrace.cpp index 17f2e02679..211fd5ffb3 100644 --- a/components/esm/loadrace.cpp +++ b/components/esm/loadrace.cpp @@ -20,10 +20,34 @@ namespace ESM void Race::load(ESMReader &esm) { - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "RADT", 140); - mPowers.load(esm); - mDescription = esm.getHNOString("DESC"); + mPowers.mList.clear(); + + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'R','A','D','T'>::value: + esm.getHT(mData, 140); + hasData = true; + break; + case ESM::FourCC<'D','E','S','C'>::value: + mDescription = esm.getHString(); + break; + case ESM::FourCC<'N','P','C','S'>::value: + mPowers.add(esm); + break; + default: + esm.fail("Unknown subrecord " + esm.retSubName().toString()); + } + } + if (!hasData) + esm.fail("Missing RADT subrecord"); } void Race::save(ESMWriter &esm) const { diff --git a/components/esm/loadscpt.cpp b/components/esm/loadscpt.cpp index 07561c2eaf..2df8e66cee 100644 --- a/components/esm/loadscpt.cpp +++ b/components/esm/loadscpt.cpp @@ -7,12 +7,6 @@ namespace ESM { -struct SCHD -{ - NAME32 mName; - Script::SCHDstruct mData; -}; - unsigned int Script::sRecordId = REC_SCPT; void Script::load(ESMReader &esm) diff --git a/components/esm/loadscpt.hpp b/components/esm/loadscpt.hpp index 38160e7f41..d5b87b5dc2 100644 --- a/components/esm/loadscpt.hpp +++ b/components/esm/loadscpt.hpp @@ -26,6 +26,11 @@ public: /// Data from script-precompling in the editor. /// \warning Do not use them. OpenCS currently does not precompile scripts. int mNumShorts, mNumLongs, mNumFloats, mScriptDataSize, mStringTableSize; + }; + struct SCHD + { + NAME32 mName; + Script::SCHDstruct mData; }; // 52 bytes std::string mId; diff --git a/components/esm/loadspel.cpp b/components/esm/loadspel.cpp index 2c98d796d3..716f47e715 100644 --- a/components/esm/loadspel.cpp +++ b/components/esm/loadspel.cpp @@ -10,9 +10,30 @@ namespace ESM void Spell::load(ESMReader &esm) { - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "SPDT", 12); - mEffects.load(esm); + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t val = esm.retSubName().val; + + switch (val) + { + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'S','P','D','T'>::value: + esm.getHT(mData, 12); + hasData = true; + break; + case ESM::FourCC<'E','N','A','M'>::value: + ENAMstruct s; + esm.getHT(s, 24); + mEffects.mList.push_back(s); + break; + } + } + if (!hasData) + esm.fail("Missing SPDT subrecord"); } void Spell::save(ESMWriter &esm) const diff --git a/components/esm/loadtes3.cpp b/components/esm/loadtes3.cpp index d3d5250493..9c0c55b8f6 100644 --- a/components/esm/loadtes3.cpp +++ b/components/esm/loadtes3.cpp @@ -45,6 +45,25 @@ void ESM::Header::load (ESMReader &esm) m.size = esm.getHNLong ("DATA"); mMaster.push_back (m); } + + if (esm.isNextSub("GMDT")) + { + esm.getHT(mGameData); + } + if (esm.isNextSub("SCRD")) + { + esm.getSubHeader(); + mSCRD.resize(esm.getSubSize()); + if (mSCRD.size()) + esm.getExact(&mSCRD[0], mSCRD.size()); + } + if (esm.isNextSub("SCRS")) + { + esm.getSubHeader(); + mSCRS.resize(esm.getSubSize()); + if (mSCRS.size()) + esm.getExact(&mSCRS[0], mSCRS.size()); + } } void ESM::Header::save (ESMWriter &esm) diff --git a/components/esm/loadtes3.hpp b/components/esm/loadtes3.hpp index eb5e14daf4..2c63e36917 100644 --- a/components/esm/loadtes3.hpp +++ b/components/esm/loadtes3.hpp @@ -39,6 +39,20 @@ namespace ESM int index; // Position of the parent file in the global list of loaded files }; + struct GMDT + { + float mCurrentHealth; + float mMaximumHealth; + float mHour; + unsigned char unknown1[12]; + NAME64 mCurrentCell; + unsigned char unknown2[4]; + NAME32 mPlayerName; + }; + GMDT mGameData; // Used in .ess savegames only + std::vector mSCRD; // Used in .ess savegames only, screenshot? + std::vector mSCRS; // Used in .ess savegames only, screenshot? + Data mData; int mFormat; std::vector mMaster; diff --git a/components/esm/loadweap.cpp b/components/esm/loadweap.cpp index 1d0b149df1..981a5815a2 100644 --- a/components/esm/loadweap.cpp +++ b/components/esm/loadweap.cpp @@ -8,24 +8,50 @@ namespace ESM { unsigned int Weapon::sRecordId = REC_WEAP; -void Weapon::load(ESMReader &esm) -{ - mModel = esm.getHNString("MODL"); - mName = esm.getHNOString("FNAM"); - esm.getHNT(mData, "WPDT", 32); - mScript = esm.getHNOString("SCRI"); - mIcon = esm.getHNOString("ITEX"); - mEnchant = esm.getHNOString("ENAM"); -} -void Weapon::save(ESMWriter &esm) const -{ - esm.writeHNCString("MODL", mModel); - esm.writeHNOCString("FNAM", mName); - esm.writeHNT("WPDT", mData, 32); - esm.writeHNOCString("SCRI", mScript); - esm.writeHNOCString("ITEX", mIcon); - esm.writeHNOCString("ENAM", mEnchant); -} + void Weapon::load(ESMReader &esm) + { + bool hasData = false; + while (esm.hasMoreSubs()) + { + esm.getSubName(); + uint32_t name = esm.retSubName().val; + switch (name) + { + case ESM::FourCC<'M','O','D','L'>::value: + mModel = esm.getHString(); + break; + case ESM::FourCC<'F','N','A','M'>::value: + mName = esm.getHString(); + break; + case ESM::FourCC<'W','P','D','T'>::value: + esm.getHT(mData, 32); + hasData = true; + break; + case ESM::FourCC<'S','C','R','I'>::value: + mScript = esm.getHString(); + break; + case ESM::FourCC<'I','T','E','X'>::value: + mIcon = esm.getHString(); + break; + case ESM::FourCC<'E','N','A','M'>::value: + mEnchant = esm.getHString(); + break; + default: + esm.fail("Unknown subrecord"); + } + } + if (!hasData) + esm.fail("Missing WPDT subrecord"); + } + void Weapon::save(ESMWriter &esm) const + { + esm.writeHNCString("MODL", mModel); + esm.writeHNOCString("FNAM", mName); + esm.writeHNT("WPDT", mData, 32); + esm.writeHNOCString("SCRI", mScript); + esm.writeHNOCString("ITEX", mIcon); + esm.writeHNOCString("ENAM", mEnchant); + } void Weapon::blank() { diff --git a/components/esm/locals.cpp b/components/esm/locals.cpp index 9c470a0253..f0cfd49f03 100644 --- a/components/esm/locals.cpp +++ b/components/esm/locals.cpp @@ -11,7 +11,7 @@ void ESM::Locals::load (ESMReader &esm) std::string id = esm.getHString(); Variant value; - value.read (esm, Variant::Format_Info); + value.read (esm, Variant::Format_Local); mVariables.push_back (std::make_pair (id, value)); } @@ -23,6 +23,6 @@ void ESM::Locals::save (ESMWriter &esm) const iter!=mVariables.end(); ++iter) { esm.writeHNString ("LOCA", iter->first); - iter->second.write (esm, Variant::Format_Info); + iter->second.write (esm, Variant::Format_Local); } -} \ No newline at end of file +} diff --git a/components/esm/npcstate.cpp b/components/esm/npcstate.cpp index e59ec3e268..724d673265 100644 --- a/components/esm/npcstate.cpp +++ b/components/esm/npcstate.cpp @@ -5,20 +5,34 @@ void ESM::NpcState::load (ESMReader &esm) { ObjectState::load (esm); - mInventory.load (esm); + if (mHasCustomState) + { + mInventory.load (esm); - mNpcStats.load (esm); + mNpcStats.load (esm); - mCreatureStats.load (esm); + mCreatureStats.load (esm); + } } void ESM::NpcState::save (ESMWriter &esm, bool inInventory) const { ObjectState::save (esm, inInventory); - mInventory.save (esm); + if (mHasCustomState) + { + mInventory.save (esm); - mNpcStats.save (esm); + mNpcStats.save (esm); - mCreatureStats.save (esm); -} \ No newline at end of file + mCreatureStats.save (esm); + } +} + +void ESM::NpcState::blank() +{ + ObjectState::blank(); + mNpcStats.blank(); + mCreatureStats.blank(); + mHasCustomState = true; +} diff --git a/components/esm/npcstate.hpp b/components/esm/npcstate.hpp index 39858d5533..b90cd85e6a 100644 --- a/components/esm/npcstate.hpp +++ b/components/esm/npcstate.hpp @@ -16,6 +16,9 @@ namespace ESM NpcStats mNpcStats; CreatureStats mCreatureStats; + /// Initialize to default state + void blank(); + virtual void load (ESMReader &esm); virtual void save (ESMWriter &esm, bool inInventory = false) const; }; diff --git a/components/esm/npcstats.cpp b/components/esm/npcstats.cpp index 3e6aed99dd..cc1d6b3ddc 100644 --- a/components/esm/npcstats.cpp +++ b/components/esm/npcstats.cpp @@ -57,12 +57,13 @@ void ESM::NpcStats::load (ESMReader &esm) mWerewolfKills = 0; esm.getHNOT (mWerewolfKills, "WKIL"); - mProfit = 0; - esm.getHNOT (mProfit, "PROF"); + // No longer used + if (esm.isNextSub("PROF")) + esm.skipHSub(); // int profit // No longer used. Now part of CreatureStats. - float attackStrength = 0; - esm.getHNOT (attackStrength, "ASTR"); + if (esm.isNextSub("ASTR")) + esm.skipHSub(); // attackStrength mLevelProgress = 0; esm.getHNOT (mLevelProgress, "LPRO"); @@ -75,8 +76,9 @@ void ESM::NpcStats::load (ESMReader &esm) mTimeToStartDrowning = 0; esm.getHNOT (mTimeToStartDrowning, "DRTI"); - mLastDrowningHit = 0; - esm.getHNOT (mLastDrowningHit, "DRLH"); + // No longer used + float lastDrowningHit = 0; + esm.getHNOT (lastDrowningHit, "DRLH"); // No longer used float levelHealthBonus = 0; @@ -131,9 +133,6 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mWerewolfKills) esm.writeHNT ("WKIL", mWerewolfKills); - if (mProfit) - esm.writeHNT ("PROF", mProfit); - if (mLevelProgress) esm.writeHNT ("LPRO", mLevelProgress); @@ -146,9 +145,20 @@ void ESM::NpcStats::save (ESMWriter &esm) const if (mTimeToStartDrowning) esm.writeHNT ("DRTI", mTimeToStartDrowning); - if (mLastDrowningHit) - esm.writeHNT ("DRLH", mLastDrowningHit); - if (mCrimeId != -1) esm.writeHNT ("CRID", mCrimeId); } + +void ESM::NpcStats::blank() +{ + mIsWerewolf = false; + mDisposition = 0; + mBounty = 0; + mReputation = 0; + mWerewolfKills = 0; + mLevelProgress = 0; + for (int i=0; i<8; ++i) + mSkillIncrease[i] = 0; + mTimeToStartDrowning = 20; + mCrimeId = -1; +} diff --git a/components/esm/npcstats.hpp b/components/esm/npcstats.hpp index 0061fc05f4..0138ab2098 100644 --- a/components/esm/npcstats.hpp +++ b/components/esm/npcstats.hpp @@ -34,20 +34,21 @@ namespace ESM StatState mWerewolfAttributes[8]; bool mIsWerewolf; - std::map mFactions; + std::map mFactions; // lower case IDs int mDisposition; Skill mSkills[27]; int mBounty; int mReputation; int mWerewolfKills; - int mProfit; int mLevelProgress; int mSkillIncrease[8]; - std::vector mUsedIds; + std::vector mUsedIds; // lower case IDs float mTimeToStartDrowning; - float mLastDrowningHit; int mCrimeId; + /// Initialize to default state + void blank(); + void load (ESMReader &esm); void save (ESMWriter &esm) const; }; diff --git a/components/esm/objectstate.cpp b/components/esm/objectstate.cpp index be00f3ef6e..9ef1ccf80d 100644 --- a/components/esm/objectstate.cpp +++ b/components/esm/objectstate.cpp @@ -6,7 +6,7 @@ void ESM::ObjectState::load (ESMReader &esm) { - mRef.load (esm, true); + mRef.loadData(esm); mHasLocals = 0; esm.getHNOT (mHasLocals, "HLOC"); @@ -23,6 +23,14 @@ void ESM::ObjectState::load (ESMReader &esm) esm.getHNOT (mPosition, "POS_", 24); esm.getHNOT (mLocalRotation, "LROT", 12); + + // obsolete + int unused; + esm.getHNOT(unused, "LTIM"); + + // FIXME: assuming "false" as default would make more sense, but also break compatibility with older save files + mHasCustomState = true; + esm.getHNOT (mHasCustomState, "HCUS"); } void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const @@ -46,6 +54,24 @@ void ESM::ObjectState::save (ESMWriter &esm, bool inInventory) const esm.writeHNT ("POS_", mPosition, 24); esm.writeHNT ("LROT", mLocalRotation, 12); } + + if (!mHasCustomState) + esm.writeHNT ("HCUS", false); } -ESM::ObjectState::~ObjectState() {} \ No newline at end of file +void ESM::ObjectState::blank() +{ + mRef.blank(); + mHasLocals = 0; + mEnabled = false; + mCount = 1; + for (int i=0;i<3;++i) + { + mPosition.pos[i] = 0; + mPosition.rot[i] = 0; + mLocalRotation[i] = 0; + } + mHasCustomState = true; +} + +ESM::ObjectState::~ObjectState() {} diff --git a/components/esm/objectstate.hpp b/components/esm/objectstate.hpp index 9c9ca5f2e8..d1077733a5 100644 --- a/components/esm/objectstate.hpp +++ b/components/esm/objectstate.hpp @@ -26,11 +26,22 @@ namespace ESM ESM::Position mPosition; float mLocalRotation[3]; + // Is there any class-specific state following the ObjectState + bool mHasCustomState; + + ObjectState() : mHasCustomState(true) + {} + + /// @note Does not load the CellRef ID, it should already be loaded before calling this method virtual void load (ESMReader &esm); + virtual void save (ESMWriter &esm, bool inInventory = false) const; + /// Initialize to default state + void blank(); + virtual ~ObjectState(); }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/player.cpp b/components/esm/player.cpp index 52b44c945f..70c4b79e25 100644 --- a/components/esm/player.cpp +++ b/components/esm/player.cpp @@ -6,6 +6,7 @@ void ESM::Player::load (ESMReader &esm) { + mObject.mRef.loadId(esm, true); mObject.load (esm); mCellId.load (esm); diff --git a/components/esm/projectilestate.hpp b/components/esm/projectilestate.hpp index 6e36efb5b4..51cd5d8c40 100644 --- a/components/esm/projectilestate.hpp +++ b/components/esm/projectilestate.hpp @@ -8,48 +8,13 @@ #include "effectlist.hpp" +#include "util.hpp" + namespace ESM { // format 0, savegames only - struct Quaternion - { - float mValues[4]; - - Quaternion() {} - Quaternion (Ogre::Quaternion q) - { - mValues[0] = q.w; - mValues[1] = q.x; - mValues[2] = q.y; - mValues[3] = q.z; - } - - operator Ogre::Quaternion () const - { - return Ogre::Quaternion(mValues[0], mValues[1], mValues[2], mValues[3]); - } - }; - - struct Vector3 - { - float mValues[3]; - - Vector3() {} - Vector3 (Ogre::Vector3 v) - { - mValues[0] = v.x; - mValues[1] = v.y; - mValues[2] = v.z; - } - - operator Ogre::Vector3 () const - { - return Ogre::Vector3(&mValues[0]); - } - }; - struct BaseProjectileState { std::string mId; diff --git a/components/esm/queststate.hpp b/components/esm/queststate.hpp index 1769336f2f..4966cdc908 100644 --- a/components/esm/queststate.hpp +++ b/components/esm/queststate.hpp @@ -12,7 +12,7 @@ namespace ESM struct QuestState { - std::string mTopic; + std::string mTopic; // lower case id int mState; unsigned char mFinished; @@ -21,4 +21,4 @@ namespace ESM }; } -#endif \ No newline at end of file +#endif diff --git a/components/esm/records.hpp b/components/esm/records.hpp index 7a0452eb3f..5c183b6f6d 100644 --- a/components/esm/records.hpp +++ b/components/esm/records.hpp @@ -14,7 +14,6 @@ #include "loadclot.hpp" #include "loadcont.hpp" #include "loadcrea.hpp" -#include "loadcrec.hpp" #include "loadinfo.hpp" #include "loaddial.hpp" #include "loaddoor.hpp" @@ -33,7 +32,6 @@ #include "loadmgef.hpp" #include "loadmisc.hpp" #include "loadnpc.hpp" -#include "loadnpcc.hpp" #include "loadpgrd.hpp" #include "loadrace.hpp" #include "loadregn.hpp" diff --git a/components/esm/spelllist.cpp b/components/esm/spelllist.cpp index 8ec386db40..98bd53ae6a 100644 --- a/components/esm/spelllist.cpp +++ b/components/esm/spelllist.cpp @@ -9,10 +9,15 @@ void SpellList::load(ESMReader &esm) { mList.clear(); while (esm.isNextSub("NPCS")) { - mList.push_back(esm.getHString()); + add(esm); } } +void SpellList::add(ESMReader &esm) +{ + mList.push_back(esm.getHString()); +} + void SpellList::save(ESMWriter &esm) const { for (std::vector::const_iterator it = mList.begin(); it != mList.end(); ++it) { diff --git a/components/esm/spelllist.hpp b/components/esm/spelllist.hpp index bcd6ba7984..9493199a80 100644 --- a/components/esm/spelllist.hpp +++ b/components/esm/spelllist.hpp @@ -19,6 +19,11 @@ namespace ESM /// Is this spell ID in mList? bool exists(const std::string& spell) const; + /// Load one spell, assumes the subrecord name was already read + void add(ESMReader &esm); + + /// Load all spells + /// TODO: remove this method, the ESM format doesn't guarantee that all spell subrecords follow one another void load(ESMReader &esm); void save(ESMWriter &esm) const; }; diff --git a/components/esm/spellstate.hpp b/components/esm/spellstate.hpp index 2ab27e908b..028e6a3878 100644 --- a/components/esm/spellstate.hpp +++ b/components/esm/spellstate.hpp @@ -12,6 +12,7 @@ namespace ESM class ESMReader; class ESMWriter; + // NOTE: spell ids must be lower case struct SpellState { struct CorprusStats diff --git a/components/esm/stolenitems.cpp b/components/esm/stolenitems.cpp new file mode 100644 index 0000000000..c51b0b99b0 --- /dev/null +++ b/components/esm/stolenitems.cpp @@ -0,0 +1,47 @@ +#include "stolenitems.hpp" + +#include +#include + +namespace ESM +{ + + void StolenItems::write(ESMWriter &esm) const + { + for (StolenItemsMap::const_iterator it = mStolenItems.begin(); it != mStolenItems.end(); ++it) + { + esm.writeHNString("NAME", it->first); + for (std::map, int>::const_iterator ownerIt = it->second.begin(); + ownerIt != it->second.end(); ++ownerIt) + { + if (ownerIt->first.second) + esm.writeHNString("FNAM", ownerIt->first.first); + else + esm.writeHNString("ONAM", ownerIt->first.first); + esm.writeHNT("COUN", ownerIt->second); + } + } + } + + void StolenItems::load(ESMReader &esm) + { + while (esm.isNextSub("NAME")) + { + std::string itemid = esm.getHString(); + + std::map, int> ownerMap; + while (esm.isNextSub("FNAM") || esm.isNextSub("ONAM")) + { + std::string subname = esm.retSubName().toString(); + std::string owner = esm.getHString(); + bool isFaction = (subname == "FNAM"); + int count; + esm.getHNT(count, "COUN"); + ownerMap.insert(std::make_pair(std::make_pair(owner, isFaction), count)); + } + + mStolenItems[itemid] = ownerMap; + } + } + +} diff --git a/components/esm/stolenitems.hpp b/components/esm/stolenitems.hpp new file mode 100644 index 0000000000..928fbbf757 --- /dev/null +++ b/components/esm/stolenitems.hpp @@ -0,0 +1,24 @@ +#ifndef OPENMW_COMPONENTS_ESM_STOLENITEMS_H +#define OPENMW_COMPONENTS_ESM_STOLENITEMS_H + +#include +#include + +namespace ESM +{ + class ESMReader; + class ESMWriter; + + // format 0, saved games only + struct StolenItems + { + typedef std::map, int> > StolenItemsMap; + StolenItemsMap mStolenItems; + + void load(ESM::ESMReader& esm); + void write(ESM::ESMWriter& esm) const; + }; + +} + +#endif diff --git a/components/esm/util.hpp b/components/esm/util.hpp new file mode 100644 index 0000000000..bb7f3cf7cd --- /dev/null +++ b/components/esm/util.hpp @@ -0,0 +1,51 @@ +#ifndef OPENMW_ESM_UTIL_H +#define OPENMW_ESM_UTIL_H + +#include +#include + +namespace ESM +{ + +// format 0, savegames only + +struct Quaternion +{ + float mValues[4]; + + Quaternion() {} + Quaternion (Ogre::Quaternion q) + { + mValues[0] = q.w; + mValues[1] = q.x; + mValues[2] = q.y; + mValues[3] = q.z; + } + + operator Ogre::Quaternion () const + { + return Ogre::Quaternion(mValues[0], mValues[1], mValues[2], mValues[3]); + } +}; + +struct Vector3 +{ + float mValues[3]; + + Vector3() {} + Vector3 (Ogre::Vector3 v) + { + mValues[0] = v.x; + mValues[1] = v.y; + mValues[2] = v.z; + } + + operator Ogre::Vector3 () const + { + return Ogre::Vector3(&mValues[0]); + } +}; + +} + +#endif diff --git a/components/esm/variant.cpp b/components/esm/variant.cpp index 4127a9d627..c65eed5e09 100644 --- a/components/esm/variant.cpp +++ b/components/esm/variant.cpp @@ -13,6 +13,7 @@ namespace const uint32_t STRV = ESM::FourCC<'S','T','R','V'>::value; const uint32_t INTV = ESM::FourCC<'I','N','T','V'>::value; const uint32_t FLTV = ESM::FourCC<'F','L','T','V'>::value; + const uint32_t STTV = ESM::FourCC<'S','T','T','V'>::value; } ESM::Variant::Variant() : mType (VT_None), mData (0) {} @@ -141,7 +142,7 @@ void ESM::Variant::read (ESMReader& esm, Format format) esm.fail ("invalid subrecord: " + name.toString()); } } - else // info + else if (format == Format_Info) { esm.getSubName(); NAME name = esm.retSubName(); @@ -157,6 +158,26 @@ void ESM::Variant::read (ESMReader& esm, Format format) else esm.fail ("invalid subrecord: " + name.toString()); } + else if (format == Format_Local) + { + esm.getSubName(); + NAME name = esm.retSubName(); + + if (name==INTV) + { + type = VT_Int; + } + else if (name==FLTV) + { + type = VT_Float; + } + else if (name==STTV) + { + type = VT_Short; + } + else + esm.fail ("invalid subrecord: " + name.toString()); + } setType (type); @@ -179,6 +200,9 @@ void ESM::Variant::write (ESMWriter& esm, Format format) const if (format==Format_Info) throw std::runtime_error ("can not serialise variant of type none to info format"); + if (format==Format_Local) + throw std::runtime_error ("can not serialise variant of type none to local format"); + // nothing to do here for GMST format } else diff --git a/components/esm/variant.hpp b/components/esm/variant.hpp index d6c1a5489f..5f179a7bdc 100644 --- a/components/esm/variant.hpp +++ b/components/esm/variant.hpp @@ -33,7 +33,8 @@ namespace ESM { Format_Global, Format_Gmst, - Format_Info // also used for local variables in saved game files + Format_Info, + Format_Local // local script variables in save game files }; Variant(); diff --git a/components/esm/variantimp.cpp b/components/esm/variantimp.cpp index 1bacdc0770..fd068fc196 100644 --- a/components/esm/variantimp.cpp +++ b/components/esm/variantimp.cpp @@ -81,6 +81,9 @@ void ESM::VariantStringData::read (ESMReader& esm, Variant::Format format, VarTy if (format==Variant::Format_Info) esm.fail ("info variables of type string not supported"); + if (format==Variant::Format_Local) + esm.fail ("local variables of type string not supported"); + // GMST mValue = esm.getHString(); } @@ -173,6 +176,21 @@ void ESM::VariantIntegerData::read (ESMReader& esm, Variant::Format format, VarT esm.getHT (mValue); } + else if (format==Variant::Format_Local) + { + if (type==VT_Short) + { + short value; + esm.getHT(value); + mValue = value; + } + else if (type==VT_Int) + { + esm.getHT(mValue); + } + else + esm.fail("unsupported local variable integer type"); + } } void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, VarType type) const @@ -204,6 +222,15 @@ void ESM::VariantIntegerData::write (ESMWriter& esm, Variant::Format format, Var esm.writeHNT ("INTV", mValue); } + else if (format==Variant::Format_Local) + { + if (type==VT_Short) + esm.writeHNT ("STTV", (short)mValue); + else if (type == VT_Int) + esm.writeHNT ("INTV", mValue); + else + throw std::runtime_error("unsupported local variable integer type"); + } } bool ESM::VariantIntegerData::isEqual (const VariantDataBase& value) const @@ -252,7 +279,7 @@ void ESM::VariantFloatData::read (ESMReader& esm, Variant::Format format, VarTyp { esm.getHNT (mValue, "FLTV"); } - else if (format==Variant::Format_Gmst || format==Variant::Format_Info) + else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) { esm.getHT (mValue); } @@ -268,7 +295,7 @@ void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarTy esm.writeHNString ("FNAM", "f"); esm.writeHNT ("FLTV", mValue); } - else if (format==Variant::Format_Gmst || format==Variant::Format_Info) + else if (format==Variant::Format_Gmst || format==Variant::Format_Info || format==Variant::Format_Local) { esm.writeHNT ("FLTV", mValue); } @@ -277,4 +304,4 @@ void ESM::VariantFloatData::write (ESMWriter& esm, Variant::Format format, VarTy bool ESM::VariantFloatData::isEqual (const VariantDataBase& value) const { return dynamic_cast (value).mValue==mValue; -} \ No newline at end of file +} diff --git a/components/esmterrain/storage.cpp b/components/esmterrain/storage.cpp index 3c76cc3b45..d4a0a0df2c 100644 --- a/components/esmterrain/storage.cpp +++ b/components/esmterrain/storage.cpp @@ -31,7 +31,7 @@ namespace ESMTerrain int cellY = origin.y; const ESM::Land* land = getLand(cellX, cellY); - if (!land) + if (!land || !(land->mDataTypes&ESM::Land::DATA_VHGT)) return false; min = std::numeric_limits::max(); @@ -73,7 +73,7 @@ namespace ESMTerrain row += ESM::Land::LAND_SIZE-1; } ESM::Land* land = getLand(cellX, cellY); - if (land && land->mHasData) + if (land && land->mDataTypes&ESM::Land::DATA_VNML) { 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]; @@ -108,7 +108,7 @@ namespace ESMTerrain row = 0; } ESM::Land* land = getLand(cellX, cellY); - if (land && land->mLandData->mUsingColours) + if (land && land->mDataTypes&ESM::Land::DATA_VCLR) { 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; @@ -157,9 +157,8 @@ namespace ESMTerrain for (int cellX = startX; cellX < startX + std::ceil(size); ++cellX) { ESM::Land* land = getLand(cellX, cellY); - if (land && !land->mHasData) + if (land && !(land->mDataTypes&ESM::Land::DATA_VHGT)) land = NULL; - bool hasColors = land && land->mLandData->mUsingColours; int rowStart = 0; int colStart = 0; @@ -183,7 +182,7 @@ namespace ESMTerrain else positions[vertX*numVerts*3 + vertY*3 + 2] = -2048; - if (land) + if (land && land->mDataTypes&ESM::Land::DATA_VNML) { 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]; @@ -207,7 +206,7 @@ namespace ESMTerrain normals[vertX*numVerts*3 + vertY*3 + 1] = normal.y; normals[vertX*numVerts*3 + vertY*3 + 2] = normal.z; - if (hasColors) + if (land && land->mDataTypes&ESM::Land::DATA_VCLR) { 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; @@ -263,7 +262,7 @@ namespace ESMTerrain assert(ymDataTypes&ESM::Land::DATA_VTEX)) { int tex = land->mLandData->mTextures[y * ESM::Land::LAND_TEXTURE_SIZE + x]; if (tex == 0) @@ -402,8 +401,8 @@ namespace ESMTerrain int endX = startX + 1; int endY = startY + 1; - assert(endX < ESM::Land::LAND_SIZE); - assert(endY < ESM::Land::LAND_SIZE); + endX = std::min(endX, ESM::Land::LAND_SIZE-1); + endY = std::min(endY, ESM::Land::LAND_SIZE-1); // now get points in terrain space (effectively rounding them to boundaries) float startXTS = startX * invFactor; diff --git a/components/esmterrain/storage.hpp b/components/esmterrain/storage.hpp index d25f7552b7..e184cbc4cd 100644 --- a/components/esmterrain/storage.hpp +++ b/components/esmterrain/storage.hpp @@ -38,6 +38,8 @@ namespace ESMTerrain /// Fill vertex buffers for a terrain chunk. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. + /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis). + /// The specified positions should be in local space, i.e. relative to the center of the 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 diff --git a/components/files/configurationmanager.cpp b/components/files/configurationmanager.cpp index 942f47d4e1..e321b58143 100644 --- a/components/files/configurationmanager.cpp +++ b/components/files/configurationmanager.cpp @@ -27,8 +27,9 @@ const char* const localToken = "?local?"; const char* const userDataToken = "?userdata?"; const char* const globalToken = "?global?"; -ConfigurationManager::ConfigurationManager() +ConfigurationManager::ConfigurationManager(bool silent) : mFixedPath(applicationName) + , mSilent(silent) { setupTokensMapping(); @@ -129,7 +130,8 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, cfgFile /= std::string(openmwCfgFile); if (boost::filesystem::is_regular_file(cfgFile)) { - std::cout << "Loading config file: " << cfgFile.string() << "... "; + if (!mSilent) + std::cout << "Loading config file: " << cfgFile.string() << "... "; boost::filesystem::ifstream configFileStream(cfgFile); if (configFileStream.is_open()) @@ -137,11 +139,13 @@ void ConfigurationManager::loadConfig(const boost::filesystem::path& path, boost::program_options::store(boost::program_options::parse_config_file( configFileStream, description, true), variables); - std::cout << "done." << std::endl; + if (!mSilent) + std::cout << "done." << std::endl; } else { - std::cout << "failed." << std::endl; + if (!mSilent) + std::cout << "failed." << std::endl; } } } diff --git a/components/files/configurationmanager.hpp b/components/files/configurationmanager.hpp index 35144fe04f..b0b7fea9a9 100644 --- a/components/files/configurationmanager.hpp +++ b/components/files/configurationmanager.hpp @@ -25,7 +25,7 @@ namespace Files */ struct ConfigurationManager { - ConfigurationManager(); + ConfigurationManager(bool silent=false); /// @param silent Emit log messages to cout? virtual ~ConfigurationManager(); void readConfiguration(boost::program_options::variables_map& variables, @@ -69,6 +69,8 @@ struct ConfigurationManager boost::filesystem::path mLogPath; TokensMappingContainer mTokensMapping; + + bool mSilent; }; } /* namespace Cfg */ diff --git a/components/files/constrainedfiledatastream.cpp b/components/files/constrainedfiledatastream.cpp index 66f6fde97c..d4d7c231a4 100644 --- a/components/files/constrainedfiledatastream.cpp +++ b/components/files/constrainedfiledatastream.cpp @@ -11,162 +11,172 @@ namespace { class ConstrainedDataStream : public Ogre::DataStream { public: - static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any - static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call + static const size_t sBufferSize = 4096; // somewhat arbitrary though 64KB buffers didn't seem to improve performance any + static const size_t sBufferThreshold = 1024; // reads larger than this bypass buffering as cost of memcpy outweighs cost of system call ConstrainedDataStream(const Ogre::String &fname, size_t start, size_t length) - { - mFile.open (fname.c_str ()); - mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; + : Ogre::DataStream(fname) + { + mFile.open (fname.c_str ()); + mSize = length != 0xFFFFFFFF ? length : mFile.size () - start; - mPos = 0; - mOrigin = start; - mExtent = start + mSize; + mPos = 0; + mOrigin = start; + mExtent = start + mSize; - mBufferOrigin = 0; - mBufferExtent = 0; - } + mBufferOrigin = 0; + mBufferExtent = 0; + } - size_t read(void* buf, size_t count) - { - assert (mPos <= mSize); + size_t read(void* buf, size_t count) + { + try + { + assert (mPos <= mSize); - uint8_t * out = reinterpret_cast (buf); + uint8_t * out = reinterpret_cast (buf); - size_t posBeg = mOrigin + mPos; - size_t posEnd = posBeg + count; + size_t posBeg = mOrigin + mPos; + size_t posEnd = posBeg + count; - if (posEnd > mExtent) - posEnd = mExtent; + if (posEnd > mExtent) + posEnd = mExtent; - size_t posCur = posBeg; + size_t posCur = posBeg; - while (posCur != posEnd) - { - size_t readLeft = posEnd - posCur; + while (posCur != posEnd) + { + size_t readLeft = posEnd - posCur; - if (posCur < mBufferOrigin || posCur >= mBufferExtent) - { - if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent)) - { - assert (mFile.tell () == mBufferExtent); + if (posCur < mBufferOrigin || posCur >= mBufferExtent) + { + if (readLeft >= sBufferThreshold || (posCur == mOrigin && posEnd == mExtent)) + { + assert (mFile.tell () == mBufferExtent); - if (posCur != mBufferExtent) - mFile.seek (posCur); + if (posCur != mBufferExtent) + mFile.seek (posCur); - posCur += mFile.read (out, readLeft); + posCur += mFile.read (out, readLeft); - mBufferOrigin = mBufferExtent = posCur; + mBufferOrigin = mBufferExtent = posCur; - mPos = posCur - mOrigin; + mPos = posCur - mOrigin; - return posCur - posBeg; - } - else - { - size_t newBufferOrigin; + return posCur - posBeg; + } + else + { + size_t newBufferOrigin; - if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize)) - newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0); - else - newBufferOrigin = posCur; + if ((posCur < mBufferOrigin) && (mBufferOrigin - posCur < sBufferSize)) + newBufferOrigin = std::max (mOrigin, mBufferOrigin > sBufferSize ? mBufferOrigin - sBufferSize : 0); + else + newBufferOrigin = posCur; - fill (newBufferOrigin); - } - } + fill (newBufferOrigin); + } + } - size_t xfer = std::min (readLeft, mBufferExtent - posCur); + size_t xfer = std::min (readLeft, mBufferExtent - posCur); - memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer); + memcpy (out, mBuffer + (posCur - mBufferOrigin), xfer); - posCur += xfer; - out += xfer; - } + posCur += xfer; + out += xfer; + } - count = posEnd - posBeg; - mPos += count; - return count; - } + count = posEnd - posBeg; + mPos += count; + return count; + } + catch (std::exception& e) + { + std::stringstream error; + error << "Failed to read '" << mName << "': " << e.what(); + throw std::runtime_error(error.str()); + } + } void skip(long count) { - assert (mPos <= mSize); + assert (mPos <= mSize); if((count >= 0 && (size_t)count <= mSize-mPos) || (count < 0 && (size_t)-count <= mPos)) - mPos += count; + mPos += count; } void seek(size_t pos) { - assert (mPos <= mSize); + assert (mPos <= mSize); if (pos < mSize) - mPos = pos; + mPos = pos; } virtual size_t tell() const { - assert (mPos <= mSize); + assert (mPos <= mSize); - return mPos; - } + return mPos; + } virtual bool eof() const { - assert (mPos <= mSize); + assert (mPos <= mSize); - return mPos == mSize; - } + return mPos == mSize; + } virtual void close() { - mFile.close(); - } + mFile.close(); + } private: - void fill (size_t newOrigin) - { - assert (mFile.tell () == mBufferExtent); + void fill (size_t newOrigin) + { + assert (mFile.tell () == mBufferExtent); - size_t newExtent = newOrigin + sBufferSize; + size_t newExtent = newOrigin + sBufferSize; - if (newExtent > mExtent) - newExtent = mExtent; + if (newExtent > mExtent) + newExtent = mExtent; - size_t oldExtent = mBufferExtent; + size_t oldExtent = mBufferExtent; - if (newOrigin != oldExtent) - mFile.seek (newOrigin); + if (newOrigin != oldExtent) + mFile.seek (newOrigin); - mBufferOrigin = mBufferExtent = newOrigin; + mBufferOrigin = mBufferExtent = newOrigin; - size_t amountRequested = newExtent - newOrigin; + size_t amountRequested = newExtent - newOrigin; - size_t amountRead = mFile.read (mBuffer, amountRequested); + size_t amountRead = mFile.read (mBuffer, amountRequested); - if (amountRead != amountRequested) - throw std::runtime_error ("An unexpected condition occurred while reading from a file."); + if (amountRead != amountRequested) + throw std::runtime_error ("An unexpected condition occurred while reading from a file."); - mBufferExtent = newExtent; - } + mBufferExtent = newExtent; + } - LowLevelFile mFile; + LowLevelFile mFile; - size_t mOrigin; - size_t mExtent; - size_t mPos; + size_t mOrigin; + size_t mExtent; + size_t mPos; - uint8_t mBuffer [sBufferSize]; - size_t mBufferOrigin; - size_t mBufferExtent; + uint8_t mBuffer [sBufferSize]; + size_t mBufferOrigin; + size_t mBufferExtent; }; } // end of unnamed namespace Ogre::DataStreamPtr openConstrainedFileDataStream (char const * filename, size_t offset, size_t length) { - return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length)); + return Ogre::DataStreamPtr(new ConstrainedDataStream(filename, offset, length)); } diff --git a/components/files/lowlevelfile.cpp b/components/files/lowlevelfile.cpp index 4cdeec043a..8456c7a26d 100644 --- a/components/files/lowlevelfile.cpp +++ b/components/files/lowlevelfile.cpp @@ -13,196 +13,196 @@ #if FILE_API == FILE_API_STDIO /* * - * Implementation of LowLevelFile methods using c stdio + * Implementation of LowLevelFile methods using c stdio * */ LowLevelFile::LowLevelFile () { - mHandle = NULL; + mHandle = NULL; } LowLevelFile::~LowLevelFile () { - if (mHandle != NULL) - fclose (mHandle); + if (mHandle != NULL) + fclose (mHandle); } void LowLevelFile::open (char const * filename) { - assert (mHandle == NULL); + assert (mHandle == NULL); - mHandle = fopen (filename, "rb"); + mHandle = fopen (filename, "rb"); - if (mHandle == NULL) - { - std::ostringstream os; - os << "Failed to open '" << filename << "' for reading."; - throw std::runtime_error (os.str ()); - } + if (mHandle == NULL) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } } void LowLevelFile::close () { - assert (mHandle != NULL); + assert (mHandle != NULL); - fclose (mHandle); + fclose (mHandle); - mHandle = NULL; + mHandle = NULL; } size_t LowLevelFile::size () { - assert (mHandle != NULL); + assert (mHandle != NULL); - long oldPosition = ftell (mHandle); + long oldPosition = ftell (mHandle); - if (oldPosition == -1) - throw std::runtime_error ("A query operation on a file failed."); + if (oldPosition == -1) + throw std::runtime_error ("A query operation on a file failed."); - if (fseek (mHandle, 0, SEEK_END) != 0) - throw std::runtime_error ("A query operation on a file failed."); + if (fseek (mHandle, 0, SEEK_END) != 0) + throw std::runtime_error ("A query operation on a file failed."); - long Size = ftell (mHandle); + long Size = ftell (mHandle); - if (Size == -1) - throw std::runtime_error ("A query operation on a file failed."); + if (Size == -1) + throw std::runtime_error ("A query operation on a file failed."); - if (fseek (mHandle, oldPosition, SEEK_SET) != 0) - throw std::runtime_error ("A query operation on a file failed."); + if (fseek (mHandle, oldPosition, SEEK_SET) != 0) + throw std::runtime_error ("A query operation on a file failed."); - return size_t (Size); + return size_t (Size); } void LowLevelFile::seek (size_t Position) { - assert (mHandle != NULL); + assert (mHandle != NULL); - if (fseek (mHandle, Position, SEEK_SET) != 0) - throw std::runtime_error ("A seek operation on a file failed."); + if (fseek (mHandle, Position, SEEK_SET) != 0) + throw std::runtime_error ("A seek operation on a file failed."); } size_t LowLevelFile::tell () { - assert (mHandle != NULL); + assert (mHandle != NULL); - long Position = ftell (mHandle); + long Position = ftell (mHandle); - if (Position == -1) - throw std::runtime_error ("A query operation on a file failed."); + if (Position == -1) + throw std::runtime_error ("A query operation on a file failed."); - return size_t (Position); + return size_t (Position); } size_t LowLevelFile::read (void * data, size_t size) { - assert (mHandle != NULL); + assert (mHandle != NULL); - int amount = fread (data, 1, size, mHandle); + int amount = fread (data, 1, size, mHandle); - if (amount == 0 && ferror (mHandle)) - throw std::runtime_error ("A read operation on a file failed."); + if (amount == 0 && ferror (mHandle)) + throw std::runtime_error ("A read operation on a file failed."); - return amount; + return amount; } #elif FILE_API == FILE_API_POSIX /* * - * Implementation of LowLevelFile methods using posix IO calls + * Implementation of LowLevelFile methods using posix IO calls * */ LowLevelFile::LowLevelFile () { - mHandle = -1; + mHandle = -1; } LowLevelFile::~LowLevelFile () { - if (mHandle != -1) - ::close (mHandle); + if (mHandle != -1) + ::close (mHandle); } void LowLevelFile::open (char const * filename) { - assert (mHandle == -1); + assert (mHandle == -1); #ifdef O_BINARY - static const int openFlags = O_RDONLY | O_BINARY; + static const int openFlags = O_RDONLY | O_BINARY; #else - static const int openFlags = O_RDONLY; + static const int openFlags = O_RDONLY; #endif - mHandle = ::open (filename, openFlags, 0); + mHandle = ::open (filename, openFlags, 0); - if (mHandle == -1) - { - std::ostringstream os; - os << "Failed to open '" << filename << "' for reading."; - throw std::runtime_error (os.str ()); - } + if (mHandle == -1) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } } void LowLevelFile::close () { - assert (mHandle != -1); + assert (mHandle != -1); - ::close (mHandle); + ::close (mHandle); - mHandle = -1; + mHandle = -1; } size_t LowLevelFile::size () { - assert (mHandle != -1); + assert (mHandle != -1); - size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); + size_t oldPosition = ::lseek (mHandle, 0, SEEK_CUR); - if (oldPosition == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + if (oldPosition == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); - size_t Size = ::lseek (mHandle, 0, SEEK_END); + size_t Size = ::lseek (mHandle, 0, SEEK_END); - if (Size == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + if (Size == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); - if (lseek (mHandle, oldPosition, SEEK_SET) == -1) - throw std::runtime_error ("A query operation on a file failed."); + if (lseek (mHandle, oldPosition, SEEK_SET) == -1) + throw std::runtime_error ("A query operation on a file failed."); - return Size; + return Size; } void LowLevelFile::seek (size_t Position) { - assert (mHandle != -1); + assert (mHandle != -1); - if (::lseek (mHandle, Position, SEEK_SET) == -1) - throw std::runtime_error ("A seek operation on a file failed."); + if (::lseek (mHandle, Position, SEEK_SET) == -1) + throw std::runtime_error ("A seek operation on a file failed."); } size_t LowLevelFile::tell () { - assert (mHandle != -1); + assert (mHandle != -1); - size_t Position = ::lseek (mHandle, 0, SEEK_CUR); + size_t Position = ::lseek (mHandle, 0, SEEK_CUR); - if (Position == size_t (-1)) - throw std::runtime_error ("A query operation on a file failed."); + if (Position == size_t (-1)) + throw std::runtime_error ("A query operation on a file failed."); - return Position; + return Position; } size_t LowLevelFile::read (void * data, size_t size) { - assert (mHandle != -1); + assert (mHandle != -1); - int amount = ::read (mHandle, data, size); + int amount = ::read (mHandle, data, size); - if (amount == -1) - throw std::runtime_error ("A read operation on a file failed."); + if (amount == -1) + throw std::runtime_error ("A read operation on a file failed."); - return amount; + return amount; } #elif FILE_API == FILE_API_WIN32 @@ -210,93 +210,93 @@ size_t LowLevelFile::read (void * data, size_t size) #include /* * - * Implementation of LowLevelFile methods using Win32 API calls + * Implementation of LowLevelFile methods using Win32 API calls * */ LowLevelFile::LowLevelFile () { - mHandle = INVALID_HANDLE_VALUE; + mHandle = INVALID_HANDLE_VALUE; } LowLevelFile::~LowLevelFile () { - if (mHandle != INVALID_HANDLE_VALUE) - CloseHandle (mHandle); + if (mHandle != INVALID_HANDLE_VALUE) + CloseHandle (mHandle); } void LowLevelFile::open (char const * filename) { - assert (mHandle == INVALID_HANDLE_VALUE); + assert (mHandle == INVALID_HANDLE_VALUE); - std::wstring wname = boost::locale::conv::utf_to_utf(filename); - HANDLE handle = CreateFileW (wname.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); + std::wstring wname = boost::locale::conv::utf_to_utf(filename); + HANDLE handle = CreateFileW (wname.c_str(), GENERIC_READ, FILE_SHARE_READ, 0, OPEN_EXISTING, 0, 0); - if (handle == INVALID_HANDLE_VALUE) - { - std::ostringstream os; - os << "Failed to open '" << filename << "' for reading."; - throw std::runtime_error (os.str ()); - } + if (handle == INVALID_HANDLE_VALUE) + { + std::ostringstream os; + os << "Failed to open '" << filename << "' for reading."; + throw std::runtime_error (os.str ()); + } - mHandle = handle; + mHandle = handle; } void LowLevelFile::close () { - assert (mHandle != INVALID_HANDLE_VALUE); + assert (mHandle != INVALID_HANDLE_VALUE); - CloseHandle (mHandle); + CloseHandle (mHandle); - mHandle = INVALID_HANDLE_VALUE; + mHandle = INVALID_HANDLE_VALUE; } size_t LowLevelFile::size () { - assert (mHandle != INVALID_HANDLE_VALUE); + assert (mHandle != INVALID_HANDLE_VALUE); - BY_HANDLE_FILE_INFORMATION info; + BY_HANDLE_FILE_INFORMATION info; - if (!GetFileInformationByHandle (mHandle, &info)) - throw std::runtime_error ("A query operation on a file failed."); + if (!GetFileInformationByHandle (mHandle, &info)) + throw std::runtime_error ("A query operation on a file failed."); - if (info.nFileSizeHigh != 0) - throw std::runtime_error ("Files greater that 4GB are not supported."); + if (info.nFileSizeHigh != 0) + throw std::runtime_error ("Files greater that 4GB are not supported."); - return info.nFileSizeLow; + return info.nFileSizeLow; } void LowLevelFile::seek (size_t Position) { - assert (mHandle != INVALID_HANDLE_VALUE); + assert (mHandle != INVALID_HANDLE_VALUE); - if (SetFilePointer (mHandle, Position, NULL, SEEK_SET) == INVALID_SET_FILE_POINTER) - if (GetLastError () != NO_ERROR) - throw std::runtime_error ("A seek operation on a file failed."); + if (SetFilePointer (mHandle, Position, NULL, SEEK_SET) == INVALID_SET_FILE_POINTER) + if (GetLastError () != NO_ERROR) + throw std::runtime_error ("A seek operation on a file failed."); } size_t LowLevelFile::tell () { - assert (mHandle != INVALID_HANDLE_VALUE); + assert (mHandle != INVALID_HANDLE_VALUE); - DWORD value = SetFilePointer (mHandle, 0, NULL, SEEK_CUR); + DWORD value = SetFilePointer (mHandle, 0, NULL, SEEK_CUR); - if (value == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR) - throw std::runtime_error ("A query operation on a file failed."); + if (value == INVALID_SET_FILE_POINTER && GetLastError () != NO_ERROR) + throw std::runtime_error ("A query operation on a file failed."); - return value; + return value; } size_t LowLevelFile::read (void * data, size_t size) { - assert (mHandle != INVALID_HANDLE_VALUE); + assert (mHandle != INVALID_HANDLE_VALUE); - DWORD read; + DWORD read; - if (!ReadFile (mHandle, data, size, &read, NULL)) - throw std::runtime_error ("A read operation on a file failed."); + if (!ReadFile (mHandle, data, size, &read, NULL)) + throw std::runtime_error ("A read operation on a file failed."); - return read; + return read; } #endif diff --git a/components/files/lowlevelfile.hpp b/components/files/lowlevelfile.hpp index f49c466a51..d94238ad6e 100644 --- a/components/files/lowlevelfile.hpp +++ b/components/files/lowlevelfile.hpp @@ -5,16 +5,16 @@ #include -#define FILE_API_STDIO 0 -#define FILE_API_POSIX 1 -#define FILE_API_WIN32 2 +#define FILE_API_STDIO 0 +#define FILE_API_POSIX 1 +#define FILE_API_WIN32 2 #if OGRE_PLATFORM == OGRE_PLATFORM_LINUX -#define FILE_API FILE_API_POSIX +#define FILE_API FILE_API_POSIX #elif OGRE_PLATFORM == OGRE_PLATFORM_WIN32 -#define FILE_API FILE_API_WIN32 +#define FILE_API FILE_API_WIN32 #else -#define FILE_API FILE_API_STDIO +#define FILE_API FILE_API_STDIO #endif #if FILE_API == FILE_API_STDIO @@ -30,26 +30,26 @@ class LowLevelFile { public: - LowLevelFile (); - ~LowLevelFile (); + LowLevelFile (); + ~LowLevelFile (); - void open (char const * filename); - void close (); + void open (char const * filename); + void close (); - size_t size (); + size_t size (); - void seek (size_t Position); - size_t tell (); + void seek (size_t Position); + size_t tell (); - size_t read (void * data, size_t size); + size_t read (void * data, size_t size); private: #if FILE_API == FILE_API_STDIO - FILE* mHandle; + FILE* mHandle; #elif FILE_API == FILE_API_POSIX - int mHandle; + int mHandle; #elif FILE_API == FILE_API_WIN32 - HANDLE mHandle; + HANDLE mHandle; #endif }; diff --git a/components/fontloader/fontloader.cpp b/components/fontloader/fontloader.cpp index e01e4b7bc2..17c630fd9f 100644 --- a/components/fontloader/fontloader.cpp +++ b/components/fontloader/fontloader.cpp @@ -1,5 +1,7 @@ #include "fontloader.hpp" +#include + #include #include @@ -121,6 +123,15 @@ namespace return encoder.getUtf8(std::string(1, c)); } + void fail (Ogre::DataStreamPtr file, const std::string& fileName, const std::string& message) + { + std::stringstream error; + error << "Font loading error: " << message; + error << "\n File: " << fileName; + error << "\n Offset: 0x" << std::hex << file->tell(); + throw std::runtime_error(error.str()); + } + } namespace Gui @@ -173,20 +184,30 @@ namespace Gui Ogre::DataStreamPtr file = Ogre::ResourceGroupManager::getSingleton().openResource(fileName); float fontSize; - int one; - file->read(&fontSize, sizeof(fontSize)); + if (file->read(&fontSize, sizeof(fontSize)) < sizeof(fontSize)) + fail(file, fileName, "File too small to be a valid font"); - file->read(&one, sizeof(int)); - assert(one == 1); - file->read(&one, sizeof(int)); - assert(one == 1); + int one; + if (file->read(&one, sizeof(int)) < sizeof(int)) + fail(file, fileName, "File too small to be a valid font"); + + if (one != 1) + fail(file, fileName, "Unexpected value"); + + if (file->read(&one, sizeof(int)) < sizeof(int)) + fail(file, fileName, "File too small to be a valid font"); + + if (one != 1) + fail(file, fileName, "Unexpected value"); char name_[284]; - file->read(name_, sizeof(name_)); + if (file->read(name_, sizeof(name_)) < sizeof(name_)) + fail(file, fileName, "File too small to be a valid font"); std::string name(name_); GlyphInfo data[256]; - file->read(data, sizeof(data)); + if (file->read(data, sizeof(data)) < sizeof(data)) + fail(file, fileName, "File too small to be a valid font"); file->close(); // Create the font texture @@ -194,12 +215,19 @@ namespace Gui Ogre::DataStreamPtr bitmapFile = Ogre::ResourceGroupManager::getSingleton().openResource(bitmapFilename); int width, height; - bitmapFile->read(&width, sizeof(int)); - bitmapFile->read(&height, sizeof(int)); + if (bitmapFile->read(&width, sizeof(int)) < sizeof(int)) + fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); + + if (bitmapFile->read(&height, sizeof(int)) < sizeof(int)) + fail(bitmapFile, bitmapFilename, "File too small to be a valid bitmap"); + + if (width <= 0 || height <= 0) + fail(bitmapFile, bitmapFilename, "Width and height must be positive"); std::vector textureData; textureData.resize(width*height*4); - bitmapFile->read(&textureData[0], width*height*4); + if (bitmapFile->read(&textureData[0], width*height*4) < (size_t)(width*height*4)) + fail(bitmapFile, bitmapFilename, "Bitmap does not contain the specified number of pixels"); bitmapFile->close(); std::string resourceName; diff --git a/components/interpreter/defines.cpp b/components/interpreter/defines.cpp index 94916ee851..4881274f03 100644 --- a/components/interpreter/defines.cpp +++ b/components/interpreter/defines.cpp @@ -47,10 +47,10 @@ namespace Interpreter{ retval << context.getActionBinding("#{sReady_Magic}"); } else if((found = check(temp, "actionprevweapon", &i, &start))){ - retval << "PLACEHOLDER_ACTION_PREV_WEAPON"; + retval << context.getActionBinding("#{sPrevWeapon}"); } else if((found = check(temp, "actionnextweapon", &i, &start))){ - retval << "PLACEHOLDER_ACTION_PREV_WEAPON"; + retval << context.getActionBinding("#{sNextWeapon}"); } else if((found = check(temp, "actiontogglerun", &i, &start))){ retval << context.getActionBinding("#{sAuto_Run}"); @@ -62,10 +62,10 @@ namespace Interpreter{ retval << context.getActionBinding("#{sReady_Weapon}"); } else if((found = check(temp, "actionprevspell", &i, &start))){ - retval << "PLACEHOLDER_ACTION_PREV_SPELL"; + retval << context.getActionBinding("#{sPrevSpell}"); } else if((found = check(temp, "actionnextspell", &i, &start))){ - retval << "PLACEHOLDER_ACTION_NEXT_SPELL"; + retval << context.getActionBinding("#{sNextSpell}"); } else if((found = check(temp, "actionrestmenu", &i, &start))){ retval << context.getActionBinding("#{sRestKey}"); diff --git a/components/misc/resourcehelpers.cpp b/components/misc/resourcehelpers.cpp index 9eaf441ef0..dc08b352a6 100644 --- a/components/misc/resourcehelpers.cpp +++ b/components/misc/resourcehelpers.cpp @@ -4,6 +4,29 @@ #include +namespace +{ + + + struct MatchPathSeparator + { + bool operator()( char ch ) const + { + return ch == '\\' || ch == '/'; + } + }; + + std::string + getBasename( std::string const& pathname ) + { + return std::string( + std::find_if( pathname.rbegin(), pathname.rend(), + MatchPathSeparator() ).base(), + pathname.end() ); + } + +} + bool Misc::ResourceHelpers::changeExtensionToDds(std::string &path) { Ogre::String::size_type pos = path.rfind('.'); @@ -40,14 +63,24 @@ std::string Misc::ResourceHelpers::correctResourcePath(const std::string &topLev // since we know all (GOTY edition or less) textures end // in .dds, we change the extension - if (changeExtensionToDds(correctedPath)) + bool changedToDds = changeExtensionToDds(correctedPath); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(correctedPath)) + return correctedPath; + // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) + // verify, and revert if false (this call succeeds quickly, but fails slowly) + if (changedToDds && Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(origExt)) + return origExt; + + // fall back to a resource in the top level directory if it exists + std::string fallback = topLevelDirectory + "\\" + getBasename(correctedPath); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(fallback)) + return fallback; + + if (changedToDds) { - // if it turns out that the above wasn't true in all cases (not for vanilla, but maybe mods) - // verify, and revert if false (this call succeeds quickly, but fails slowly) - if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(correctedPath)) - { - return origExt; - } + fallback = topLevelDirectory + "\\" + getBasename(origExt); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(fallback)) + return fallback; } return correctedPath; @@ -88,3 +121,20 @@ std::string Misc::ResourceHelpers::correctBookartPath(const std::string &resPath return image; } + +std::string Misc::ResourceHelpers::correctActorModelPath(const std::string &resPath) +{ + std::string mdlname = resPath; + std::string::size_type p = mdlname.rfind('\\'); + if(p == std::string::npos) + p = mdlname.rfind('/'); + if(p != std::string::npos) + mdlname.insert(mdlname.begin()+p+1, 'x'); + else + mdlname.insert(mdlname.begin(), 'x'); + if(!Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(mdlname)) + { + return resPath; + } + return mdlname; +} diff --git a/components/misc/resourcehelpers.hpp b/components/misc/resourcehelpers.hpp index 3cf0f4c279..2ce3dce1e9 100644 --- a/components/misc/resourcehelpers.hpp +++ b/components/misc/resourcehelpers.hpp @@ -13,6 +13,8 @@ namespace Misc std::string correctIconPath(const std::string &resPath); std::string correctBookartPath(const std::string &resPath); std::string correctBookartPath(const std::string &resPath, int width, int height); + /// Uses "xfoo.nif" instead of "foo.nif" if available + std::string correctActorModelPath(const std::string &resPath); } } diff --git a/components/misc/stringops.cpp b/components/misc/stringops.cpp index 0f801e5549..723c1c1e0b 100644 --- a/components/misc/stringops.cpp +++ b/components/misc/stringops.cpp @@ -1,14 +1,5 @@ #include "stringops.hpp" -#include -#include -#include - -#include -#include - - - namespace Misc { diff --git a/components/nif/niffile.cpp b/components/nif/niffile.cpp index c689e27b3a..4f3ee95cbe 100644 --- a/components/nif/niffile.cpp +++ b/components/nif/niffile.cpp @@ -57,6 +57,7 @@ static std::map makeFactory() newFactory.insert(makeEntry("NiCamera", &construct , RC_NiCamera )); newFactory.insert(makeEntry("RootCollisionNode", &construct , RC_RootCollisionNode )); newFactory.insert(makeEntry("NiTexturingProperty", &construct , RC_NiTexturingProperty )); + newFactory.insert(makeEntry("NiFogProperty", &construct , RC_NiFogProperty )); newFactory.insert(makeEntry("NiMaterialProperty", &construct , RC_NiMaterialProperty )); newFactory.insert(makeEntry("NiZBufferProperty", &construct , RC_NiZBufferProperty )); newFactory.insert(makeEntry("NiAlphaProperty", &construct , RC_NiAlphaProperty )); @@ -131,9 +132,9 @@ void NIFFile::parse() NIFStream nif (this, Ogre::ResourceGroupManager::getSingleton().openResource(filename)); // Check the header string - std::string head = nif.getString(40); + std::string head = nif.getVersionString(); if(head.compare(0, 22, "NetImmerse File Format") != 0) - fail("Invalid NIF header"); + fail("Invalid NIF header: " + head); // Get BCD version ver = nif.getUInt(); diff --git a/components/nif/niffile.hpp b/components/nif/niffile.hpp index 2ef2a6fda2..ceb9984fb8 100644 --- a/components/nif/niffile.hpp +++ b/components/nif/niffile.hpp @@ -46,14 +46,14 @@ public: /// Used if file parsing fails void fail(const std::string &msg) { - std::string err = "NIFFile Error: " + msg; + std::string err = " NIFFile Error: " + msg; err += "\nFile: " + filename; throw std::runtime_error(err); } /// Used when something goes wrong, but not catastrophically so void warn(const std::string &msg) { - std::cerr << "NIFFile Warning: " << msg <size(); + if(fileSize != 0 && fileSize < length) + file->fail("Attempted to read a string with " + Ogre::StringConverter::toString(length) + " characters , but file is only "+Ogre::StringConverter::toString(fileSize)+ " bytes!"); + std::vector str (length+1, 0); if(inp->read(&str[0], length) != length) @@ -96,6 +101,10 @@ std::string NIFStream::getString() size_t size = read_le32(); return getString(size); } +std::string NIFStream::getVersionString() +{ + return inp->getLine(); +} void NIFStream::getShorts(std::vector &vec, size_t size) { diff --git a/components/nif/nifstream.hpp b/components/nif/nifstream.hpp index 3d6a1319a8..6c5e83eebd 100644 --- a/components/nif/nifstream.hpp +++ b/components/nif/nifstream.hpp @@ -37,31 +37,7 @@ public: NIFStream (NIFFile * file, Ogre::DataStreamPtr inp): file (file), inp (inp) {} - /************************************************* - Parser functions - ****************************************************/ - - template - struct GetHandler - { - typedef T (NIFStream::*fn_t)(); - - static const fn_t sValue; // this is specialized per supported type in the .cpp file - - static T read (NIFStream* nif) - { - return (nif->*sValue) (); - } - }; - - template - void read (NIFStream* nif, T & Value) - { - Value = GetHandler ::read (nif); - } - void skip(size_t size) { inp->skip(size); } - void read (void * data, size_t size) { inp->read (data, size); } char getChar() { return read_byte(); } short getShort() { return read_le16(); } @@ -81,6 +57,8 @@ public: std::string getString(size_t length); ///Read in a string of the length specified in the file std::string getString(); + ///This is special since the version string doesn't start with a number, and ends with "\n" + std::string getVersionString(); void getShorts(std::vector &vec, size_t size); void getFloats(std::vector &vec, size_t size); diff --git a/components/nif/property.hpp b/components/nif/property.hpp index 2c7747a3ec..77f61d0684 100644 --- a/components/nif/property.hpp +++ b/components/nif/property.hpp @@ -155,6 +155,22 @@ public: } }; +class NiFogProperty : public Property +{ +public: + float mFogDepth; + Ogre::Vector3 mColour; + + + void read(NIFStream *nif) + { + Property::read(nif); + + mFogDepth = nif->getFloat(); + mColour = nif->getVector3(); + } +}; + // These contain no other data than the 'flags' field in Property class NiShadeProperty : public Property { }; class NiDitherProperty : public Property { }; diff --git a/components/nif/record.hpp b/components/nif/record.hpp index 079b335f05..07d7540f85 100644 --- a/components/nif/record.hpp +++ b/components/nif/record.hpp @@ -44,6 +44,7 @@ enum RecordType RC_NiBSParticleNode, RC_NiCamera, RC_NiTexturingProperty, + RC_NiFogProperty, RC_NiMaterialProperty, RC_NiZBufferProperty, RC_NiAlphaProperty, diff --git a/components/nifbullet/bulletnifloader.cpp b/components/nifbullet/bulletnifloader.cpp index 3abe0c1716..35d5527268 100644 --- a/components/nifbullet/bulletnifloader.cpp +++ b/components/nifbullet/bulletnifloader.cpp @@ -48,6 +48,57 @@ http://www.gnu.org/licenses/ . typedef unsigned char ubyte; +// Extract a list of keyframe-controlled nodes from a .kf file +// FIXME: this is a similar copy of OgreNifLoader::loadKf +void extractControlledNodes(Nif::NIFFilePtr kfFile, std::set& controlled) +{ + if(kfFile->numRoots() < 1) + { + kfFile->warn("Found no root nodes in "+kfFile->getFilename()+"."); + return; + } + + const Nif::Record *r = kfFile->getRoot(0); + assert(r != NULL); + + if(r->recType != Nif::RC_NiSequenceStreamHelper) + { + kfFile->warn("First root was not a NiSequenceStreamHelper, but a "+ + r->recName+"."); + return; + } + const Nif::NiSequenceStreamHelper *seq = static_cast(r); + + Nif::ExtraPtr extra = seq->extra; + if(extra.empty() || extra->recType != Nif::RC_NiTextKeyExtraData) + { + kfFile->warn("First extra data was not a NiTextKeyExtraData, but a "+ + (extra.empty() ? std::string("nil") : extra->recName)+"."); + return; + } + + extra = extra->extra; + Nif::ControllerPtr ctrl = seq->controller; + for(;!extra.empty() && !ctrl.empty();(extra=extra->extra),(ctrl=ctrl->next)) + { + if(extra->recType != Nif::RC_NiStringExtraData || ctrl->recType != Nif::RC_NiKeyframeController) + { + kfFile->warn("Unexpected extra data "+extra->recName+" with controller "+ctrl->recName); + continue; + } + + if (!(ctrl->flags & Nif::NiNode::ControllerFlag_Active)) + continue; + + const Nif::NiStringExtraData *strdata = static_cast(extra.getPtr()); + const Nif::NiKeyframeController *key = static_cast(ctrl.getPtr()); + + if(key->data.empty()) + continue; + controlled.insert(strdata->string); + } +} + namespace NifBullet { @@ -72,10 +123,6 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mCompoundShape = NULL; mStaticMesh = NULL; - // Load the NIF. TODO: Wrap this in a try-catch block once we're out - // of the early stages of development. Right now we WANT to catch - // every error as early and intrusively as possible, as it's most - // likely a sign of incomplete code rather than faulty input. Nif::NIFFilePtr pnif (Nif::Cache::getInstance().load(mResourceName.substr(0, mResourceName.length()-7))); Nif::NIFFile & nif = *pnif.get (); if (nif.numRoots() < 1) @@ -84,6 +131,19 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) return; } + // Have to load controlled nodes from the .kf + // FIXME: the .kf has to be loaded both for rendering and physics, ideally it should be opened once and then reused + mControlledNodes.clear(); + std::string kfname = mResourceName.substr(0, mResourceName.length()-7); + Misc::StringUtils::toLower(kfname); + if(kfname.size() > 4 && kfname.compare(kfname.size()-4, 4, ".nif") == 0) + kfname.replace(kfname.size()-4, 4, ".kf"); + if (Ogre::ResourceGroupManager::getSingleton().resourceExistsInAnyGroup(kfname)) + { + Nif::NIFFilePtr kf (Nif::Cache::getInstance().load(kfname)); + extractControlledNodes(kf, mControlledNodes); + } + Nif::Record *r = nif.getRoot(0); assert(r != NULL); @@ -95,10 +155,10 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) return; } - mShape->mHasCollisionNode = hasRootCollisionNode(node); + mShape->mAutogenerated = hasAutoGeneratedCollision(node); //do a first pass - handleNode(node,0,false,false,false); + handleNode(node,0,false,false); if(mBoundingBox != NULL) { @@ -152,12 +212,9 @@ void ManualBulletShapeLoader::loadResource(Ogre::Resource *resource) mShape->mRaycastingShape = new TriangleMeshShape(mStaticMesh,true); } -bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) +bool ManualBulletShapeLoader::hasAutoGeneratedCollision(Nif::Node const * rootNode) { - if(node->recType == Nif::RC_RootCollisionNode) - return true; - - const Nif::NiNode *ninode = dynamic_cast(node); + const Nif::NiNode *ninode = dynamic_cast(rootNode); if(ninode) { const Nif::NodeList &list = ninode->children; @@ -165,18 +222,17 @@ bool ManualBulletShapeLoader::hasRootCollisionNode(Nif::Node const * node) { if(!list[i].empty()) { - if(hasRootCollisionNode(list[i].getPtr())) - return true; + if(list[i].getPtr()->recType == Nif::RC_RootCollisionNode) + return false; } } } - - return false; + return true; } void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, bool isCollisionNode, - bool raycasting, bool isMarker, bool isAnimated) + bool raycasting, bool isAnimated) { // Accumulate the flags from all the child nodes. This works for all // the flags we currently use, at least. @@ -186,6 +242,9 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, && (node->controller->flags & Nif::NiNode::ControllerFlag_Active)) isAnimated = true; + if (mControlledNodes.find(node->name) != mControlledNodes.end()) + isAnimated = true; + if (!raycasting) isCollisionNode = isCollisionNode || (node->recType == Nif::RC_RootCollisionNode); else @@ -195,13 +254,6 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, if(node->recType == Nif::RC_AvoidNode) flags |= 0x800; - // Marker objects - /// \todo don't do this in the editor - std::string nodename = node->name; - Misc::StringUtils::toLower(nodename); - if (nodename.find("marker") != std::string::npos) - isMarker = true; - // Check for extra data Nif::Extra const *e = node; while (!e->extra.empty()) @@ -222,16 +274,14 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, // No collision. Use an internal flag setting to mark this. flags |= 0x800; } - else if (sd->string == "MRK") + else if (sd->string == "MRK" && !mShowMarkers) // Marker objects. These are only visible in the - // editor. Until and unless we add an editor component to - // the engine, just skip this entire node. - isMarker = true; + // editor. + return; } } - if ( (isCollisionNode || (!mShape->mHasCollisionNode && !raycasting)) - && (!isMarker || (mShape->mHasCollisionNode && !raycasting))) + if (isCollisionNode || (mShape->mAutogenerated && !raycasting)) { // NOTE: a trishape with hasBounds=true, but no BBoxCollision flag should NOT go through handleNiTriShape! // It must be ignored completely. @@ -260,7 +310,7 @@ void ManualBulletShapeLoader::handleNode(const Nif::Node *node, int flags, for(size_t i = 0;i < list.length();i++) { if(!list[i].empty()) - handleNode(list[i].getPtr(), flags, isCollisionNode, raycasting, isMarker, isAnimated); + handleNode(list[i].getPtr(), flags, isCollisionNode, raycasting, isAnimated); } } } @@ -315,16 +365,23 @@ void ManualBulletShapeLoader::handleNiTriShape(const Nif::NiTriShape *shape, int TriangleMeshShape* childShape = new TriangleMeshShape(childMesh,true); - childShape->setLocalScaling(btVector3(transform[0][0], transform[1][1], transform[2][2])); - + float scale = shape->trafo.scale; + const Nif::Node* parent = shape; + while (parent->parent) + { + parent = parent->parent; + scale *= parent->trafo.scale; + } Ogre::Quaternion q = transform.extractQuaternion(); Ogre::Vector3 v = transform.getTrans(); + childShape->setLocalScaling(btVector3(scale, scale, scale)); + btTransform trans(btQuaternion(q.x, q.y, q.z, q.w), btVector3(v.x, v.y, v.z)); if (raycasting) - mShape->mAnimatedRaycastingShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes())); + mShape->mAnimatedRaycastingShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); else - mShape->mAnimatedShapes.insert(std::make_pair(shape->name, mCompoundShape->getNumChildShapes())); + mShape->mAnimatedShapes.insert(std::make_pair(shape->recIndex, mCompoundShape->getNumChildShapes())); mCompoundShape->addChildShape(trans, childShape); } diff --git a/components/nifbullet/bulletnifloader.hpp b/components/nifbullet/bulletnifloader.hpp index a9ee968b96..56d98834da 100644 --- a/components/nifbullet/bulletnifloader.hpp +++ b/components/nifbullet/bulletnifloader.hpp @@ -67,11 +67,12 @@ struct TriangleMeshShape : public btBvhTriangleMeshShape class ManualBulletShapeLoader : public OEngine::Physic::BulletShapeLoader { public: - ManualBulletShapeLoader() + ManualBulletShapeLoader(bool showMarkers=false) : mShape(NULL) , mStaticMesh(NULL) , mCompoundShape(NULL) , mBoundingBox(NULL) + , mShowMarkers(showMarkers) { } @@ -107,12 +108,12 @@ private: *Parse a node. */ void handleNode(Nif::Node const *node, int flags, bool isCollisionNode, - bool raycasting, bool isMarker, bool isAnimated=false); + bool raycasting, bool isAnimated=false); /** *Helper function */ - bool hasRootCollisionNode(const Nif::Node *node); + bool hasAutoGeneratedCollision(const Nif::Node *rootNode); /** *convert a NiTriShape to a bullet trishape. @@ -128,6 +129,10 @@ private: btTriangleMesh* mStaticMesh; btBoxShape *mBoundingBox; + + std::set mControlledNodes; + + bool mShowMarkers; }; diff --git a/components/nifogre/mesh.cpp b/components/nifogre/mesh.cpp index 4932dd0098..85c3a7b65c 100644 --- a/components/nifogre/mesh.cpp +++ b/components/nifogre/mesh.cpp @@ -382,7 +382,7 @@ NIFMeshLoader::NIFMeshLoader(const std::string &name, const std::string &group, void NIFMeshLoader::loadResource(Ogre::Resource *resource) { - Ogre::Mesh *mesh = dynamic_cast(resource); + Ogre::Mesh *mesh = static_cast(resource); OgreAssert(mesh, "Attempting to load a mesh into a non-mesh resource!"); Nif::NIFFilePtr nif = Nif::Cache::getInstance().load(mName); @@ -395,7 +395,7 @@ void NIFMeshLoader::loadResource(Ogre::Resource *resource) } const Nif::Record *record = nif->getRecord(mShapeIndex); - createSubMesh(mesh, dynamic_cast(record)); + createSubMesh(mesh, static_cast(record)); } diff --git a/components/nifogre/ogrenifloader.cpp b/components/nifogre/ogrenifloader.cpp index 1d1e1a22d0..17df7a3cdc 100644 --- a/components/nifogre/ogrenifloader.cpp +++ b/components/nifogre/ogrenifloader.cpp @@ -483,7 +483,7 @@ public: Ogre::Quaternion xr(Ogre::Radian(xrot), Ogre::Vector3::UNIT_X); Ogre::Quaternion yr(Ogre::Radian(yrot), Ogre::Vector3::UNIT_Y); Ogre::Quaternion zr(Ogre::Radian(zrot), Ogre::Vector3::UNIT_Z); - return (xr*yr*zr); + return (zr*yr*xr); } public: @@ -689,6 +689,14 @@ public: */ class NIFObjectLoader { + static bool sShowMarkers; +public: + static void setShowMarkers(bool show) + { + sShowMarkers = show; + } +private: + static void warn(const std::string &msg) { std::cerr << "NIFObjectLoader: Warn: " << msg << std::endl; @@ -799,7 +807,7 @@ class NIFObjectLoader { if (ctrls->recType == Nif::RC_NiAlphaController) { - const Nif::NiAlphaController *alphaCtrl = dynamic_cast(ctrls.getPtr()); + const Nif::NiAlphaController *alphaCtrl = static_cast(ctrls.getPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW AlphaController::Value(movable, alphaCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); AlphaController::Function* function = OGRE_NEW AlphaController::Function(alphaCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); @@ -808,7 +816,7 @@ class NIFObjectLoader } else if (ctrls->recType == Nif::RC_NiMaterialColorController) { - const Nif::NiMaterialColorController *matCtrl = dynamic_cast(ctrls.getPtr()); + const Nif::NiMaterialColorController *matCtrl = static_cast(ctrls.getPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW MaterialColorController::Value(movable, matCtrl->data.getPtr(), &scene->mMaterialControllerMgr)); MaterialColorController::Function* function = OGRE_NEW MaterialColorController::Function(matCtrl, (animflags&Nif::NiNode::AnimFlag_AutoPlay)); scene->mMaxControllerLength = std::max(function->mStopTime, scene->mMaxControllerLength); @@ -826,7 +834,7 @@ class NIFObjectLoader { if (ctrls->recType == Nif::RC_NiFlipController) { - const Nif::NiFlipController *flipCtrl = dynamic_cast(ctrls.getPtr()); + const Nif::NiFlipController *flipCtrl = static_cast(ctrls.getPtr()); Ogre::ControllerValueRealPtr dstval(OGRE_NEW FlipController::Value( @@ -928,6 +936,8 @@ class NIFObjectLoader particledata = static_cast(partnode)->data.getPtr(); else if(partnode->recType == Nif::RC_NiRotatingParticles) particledata = static_cast(partnode)->data.getPtr(); + else + throw std::runtime_error("Unexpected particle node type"); std::string fullname = name+"@index="+Ogre::StringConverter::toString(partnode->recIndex); if(partnode->name.length() > 0) @@ -971,7 +981,10 @@ class NIFObjectLoader { const Nif::NiParticleSystemController *partctrl = static_cast(ctrl.getPtr()); - partsys->setDefaultDimensions(partctrl->size*2, partctrl->size*2); + float size = partctrl->size*2; + // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail + size = std::max(size, 0.00001f); + partsys->setDefaultDimensions(size, size); if(!partctrl->emitter.empty()) { @@ -1033,7 +1046,7 @@ class NIFObjectLoader static void createParticleInitialState(Ogre::ParticleSystem* partsys, const Nif::NiAutoNormalParticlesData* particledata, const Nif::NiParticleSystemController* partctrl) { - partsys->_update(0.f); // seems to be required to allocate mFreeParticles + partsys->_update(0.f); // seems to be required to allocate mFreeParticles. TODO: patch Ogre to handle this better int i=0; for (std::vector::const_iterator it = partctrl->particles.begin(); iactiveCount && it != partctrl->particles.end(); ++it, ++i) @@ -1073,6 +1086,7 @@ class NIFObjectLoader totalTimeToLive = std::max(0.f, particle.lifespan); timeToLive = std::max(0.f, particle.lifespan - particle.lifetime); } + partsys->_update(0.f); // now apparently needs another update, otherwise it won't render in the first frame. TODO: patch Ogre to handle this better } static void createNodeControllers(const Nif::NIFFilePtr& nif, const std::string &name, Nif::ControllerPtr ctrl, ObjectScenePtr scene, int animflags) @@ -1170,11 +1184,6 @@ class NIFObjectLoader if(node->recType == Nif::RC_RootCollisionNode) isRootCollisionNode = true; - // Marker objects: just skip the entire node branch - /// \todo don't do this in the editor - if (node->name.find("marker") != std::string::npos) - return; - if(node->recType == Nif::RC_NiBSAnimationNode) animflags |= node->flags; else if(node->recType == Nif::RC_NiBSParticleNode) @@ -1207,7 +1216,7 @@ class NIFObjectLoader const Nif::NiStringExtraData *sd = static_cast(e.getPtr()); // String markers may contain important information // affecting the entire subtree of this obj - if(sd->string == "MRK") + if(sd->string == "MRK" && !sShowMarkers) { // Marker objects. These meshes are only visible in the // editor. @@ -1380,6 +1389,7 @@ ObjectScenePtr Loader::createObjects(Ogre::SceneNode *parentNode, std::string na } ObjectScenePtr Loader::createObjects(Ogre::Entity *parent, const std::string &bonename, + const std::string& bonefilter, Ogre::SceneNode *parentNode, std::string name, const std::string &group) { @@ -1405,11 +1415,9 @@ 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; + // accepts anything named "filter*" or "tri filter*" + std::string filter = "@shape=tri "+bonefilter; + std::string filter2 = "@shape="+bonefilter; Misc::StringUtils::toLower(filter); Misc::StringUtils::toLower(filter2); for(size_t i = 0;i < scene->mEntities.size();i++) @@ -1471,5 +1479,14 @@ void Loader::createKfControllers(Ogre::Entity *skelBase, NIFObjectLoader::loadKf(skelBase->getSkeleton(), name, textKeys, ctrls); } +bool Loader::sShowMarkers = false; +bool NIFObjectLoader::sShowMarkers = false; + +void Loader::setShowMarkers(bool show) +{ + sShowMarkers = show; + NIFObjectLoader::setShowMarkers(show); +} + } // namespace NifOgre diff --git a/components/nifogre/ogrenifloader.hpp b/components/nifogre/ogrenifloader.hpp index 485495a388..c135326448 100644 --- a/components/nifogre/ogrenifloader.hpp +++ b/components/nifogre/ogrenifloader.hpp @@ -97,6 +97,7 @@ class Loader { public: static ObjectScenePtr createObjects(Ogre::Entity *parent, const std::string &bonename, + const std::string& filter, Ogre::SceneNode *parentNode, std::string name, const std::string &group="General"); @@ -109,10 +110,18 @@ public: std::string name, const std::string &group="General"); + /// Set whether or not nodes marked as "MRK" should be shown. + /// These should be hidden ingame, but visible in the editior. + /// Default: false. + static void setShowMarkers(bool show); + static void createKfControllers(Ogre::Entity *skelBase, const std::string &name, TextKeyMap &textKeys, std::vector > &ctrls); + +private: + static bool sShowMarkers; }; // FIXME: Should be with other general Ogre extensions. diff --git a/components/nifogre/particles.cpp b/components/nifogre/particles.cpp index 316e4edc20..4fec2d29eb 100644 --- a/components/nifogre/particles.cpp +++ b/components/nifogre/particles.cpp @@ -452,6 +452,8 @@ public: { Ogre::Real scale = (life_time-particle_time) / mGrowTime; assert (scale >= 0); + // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail + scale = std::max(scale, 0.00001f); width *= scale; height *= scale; } @@ -459,6 +461,8 @@ public: { Ogre::Real scale = particle_time / mFadeTime; assert (scale >= 0); + // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail + scale = std::max(scale, 0.00001f); width *= scale; height *= scale; } @@ -485,6 +489,8 @@ public: { Ogre::Real scale = (life_time-particle_time) / mGrowTime; assert (scale >= 0); + // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail + scale = std::max(scale, 0.00001f); width *= scale; height *= scale; } @@ -492,6 +498,8 @@ public: { Ogre::Real scale = particle_time / mFadeTime; assert (scale >= 0); + // HACK: don't allow zero-sized particles which can rarely cause an AABB assertion in Ogre to fail + scale = std::max(scale, 0.00001f); width *= scale; height *= scale; } diff --git a/components/settings/settings.cpp b/components/settings/settings.cpp index 5fc2ca3c11..a9a78d0351 100644 --- a/components/settings/settings.cpp +++ b/components/settings/settings.cpp @@ -1,95 +1,144 @@ #include "settings.hpp" #include -#include -#include -#include #include -#include -#include +#include +#include +#include -using namespace Settings; +namespace Settings +{ -namespace bfs = boost::filesystem; - -Ogre::ConfigFile Manager::mFile = Ogre::ConfigFile(); -Ogre::ConfigFile Manager::mDefaultFile = Ogre::ConfigFile(); +CategorySettingValueMap Manager::mDefaultSettings = CategorySettingValueMap(); +CategorySettingValueMap Manager::mUserSettings = CategorySettingValueMap(); CategorySettingVector Manager::mChangedSettings = CategorySettingVector(); -CategorySettingValueMap Manager::mNewSettings = CategorySettingValueMap(); -void Manager::loadUser (const std::string& file) + +class SettingsFileParser { - Ogre::DataStreamPtr stream = openConstrainedFileDataStream(file.c_str()); - mFile.load(stream); -} +public: + SettingsFileParser() : mLine(0) {} -void Manager::loadDefault (const std::string& file) -{ - Ogre::DataStreamPtr stream = openConstrainedFileDataStream(file.c_str()); - mDefaultFile.load(stream); -} - -void Manager::saveUser(const std::string& file) -{ - bfs::ofstream fout((bfs::path(file))); - - Ogre::ConfigFile::SectionIterator seci = mFile.getSectionIterator(); - - while (seci.hasMoreElements()) + void loadSettingsFile (const std::string& file, CategorySettingValueMap& settings) { - Ogre::String sectionName = seci.peekNextKey(); - - if (sectionName.length() > 0) - fout << '\n' << '[' << seci.peekNextKey() << ']' << '\n'; - - Ogre::ConfigFile::SettingsMultiMap *settings = seci.getNext(); - Ogre::ConfigFile::SettingsMultiMap::iterator i; - for (i = settings->begin(); i != settings->end(); ++i) + mFile = file; + boost::filesystem::ifstream stream; + stream.open(boost::filesystem::path(file)); + std::string currentCategory; + mLine = 0; + while (!stream.eof() && !stream.fail()) { - fout << i->first.c_str() << " = " << i->second.c_str() << '\n'; - } + ++mLine; + std::string line; + std::getline( stream, line ); - CategorySettingValueMap::iterator it = mNewSettings.begin(); - while (it != mNewSettings.end()) - { - if (it->first.first == sectionName) + size_t i = 0; + if (!skipWhiteSpace(i, line)) + continue; + + if (line[i] == '#') // skip comment + continue; + + if (line[i] == '[') { - fout << it->first.second << " = " << it->second << '\n'; - mNewSettings.erase(it++); + size_t end = line.find(']', i); + if (end == std::string::npos) + fail("unterminated category"); + + currentCategory = line.substr(i+1, end - (i+1)); + boost::algorithm::trim(currentCategory); + i = end+1; } - else - ++it; + + if (!skipWhiteSpace(i, line)) + continue; + + if (currentCategory.empty()) + fail("empty category name"); + + size_t settingEnd = line.find('=', i); + if (settingEnd == std::string::npos) + fail("unterminated setting name"); + + std::string setting = line.substr(i, (settingEnd-i)); + boost::algorithm::trim(setting); + + size_t valueBegin = settingEnd+1; + std::string value = line.substr(valueBegin); + boost::algorithm::trim(value); + + if (settings.insert(std::make_pair(std::make_pair(currentCategory, setting), value)).second == false) + fail(std::string("duplicate setting: [" + currentCategory + "] " + setting)); } } - std::string category = ""; - for (CategorySettingValueMap::iterator it = mNewSettings.begin(); - it != mNewSettings.end(); ++it) +private: + /// Increment i until it longer points to a whitespace character + /// in the string or has reached the end of the string. + /// @return false if we have reached the end of the string + bool skipWhiteSpace(size_t& i, std::string& str) { - if (category != it->first.first) + while (i < str.size() && std::isspace(str[i], std::locale::classic())) { - category = it->first.first; - fout << '\n' << '[' << category << ']' << '\n'; + ++i; } - fout << it->first.second << " = " << it->second << '\n'; + return i < str.size(); } - fout.close(); + void fail(const std::string& message) + { + std::stringstream error; + error << "Error on line " << mLine << " in " << mFile << ":\n" << message; + throw std::runtime_error(error.str()); + } + + std::string mFile; + int mLine; +}; + +void Manager::loadDefault(const std::string &file) +{ + SettingsFileParser parser; + parser.loadSettingsFile(file, mDefaultSettings); } -std::string Manager::getString (const std::string& setting, const std::string& category) +void Manager::loadUser(const std::string &file) { - if (mNewSettings.find(std::make_pair(category, setting)) != mNewSettings.end()) - return mNewSettings[std::make_pair(category, setting)]; + SettingsFileParser parser; + parser.loadSettingsFile(file, mUserSettings); +} - std::string defaultval = mDefaultFile.getSetting(setting, category, "NOTFOUND"); - std::string val = mFile.getSetting(setting, category, defaultval); +void Manager::saveUser(const std::string &file) +{ + boost::filesystem::ofstream stream; + stream.open(boost::filesystem::path(file)); + std::string currentCategory; + for (CategorySettingValueMap::iterator it = mUserSettings.begin(); it != mUserSettings.end(); ++it) + { + if (it->first.first != currentCategory) + { + currentCategory = it->first.first; + stream << "\n[" << currentCategory << "]\n"; + } + stream << it->first.second << " = " << it->second << "\n"; + } +} - if (val == "NOTFOUND") - throw std::runtime_error("Trying to retrieve a non-existing setting: " + setting + " Make sure the settings-default.cfg file was properly installed."); - return val; +std::string Manager::getString(const std::string &setting, const std::string &category) +{ + CategorySettingValueMap::key_type key = std::make_pair(category, setting); + CategorySettingValueMap::iterator it = mUserSettings.find(key); + if (it != mUserSettings.end()) + return it->second; + + it = mDefaultSettings.find(key); + if (it != mDefaultSettings.end()) + return it->second; + + throw std::runtime_error(std::string("Trying to retrieve a non-existing setting: ") + setting + + ".\nMake sure the settings-default.cfg file file was properly installed."); } float Manager::getFloat (const std::string& setting, const std::string& category) @@ -107,51 +156,20 @@ bool Manager::getBool (const std::string& setting, const std::string& category) return Ogre::StringConverter::parseBool( getString(setting, category) ); } -void Manager::setString (const std::string& setting, const std::string& category, const std::string& value) +void Manager::setString(const std::string &setting, const std::string &category, const std::string &value) { - CategorySetting s = std::make_pair(category, setting); + CategorySettingValueMap::key_type key = std::make_pair(category, setting); - bool found=false; - try + CategorySettingValueMap::iterator found = mUserSettings.find(key); + if (found != mUserSettings.end()) { - Ogre::ConfigFile::SettingsIterator it = mFile.getSettingsIterator(category); - while (it.hasMoreElements()) - { - Ogre::ConfigFile::SettingsMultiMap::iterator i = it.current(); - - if ((*i).first == setting) - { - if ((*i).second != value) - { - mChangedSettings.push_back(std::make_pair(category, setting)); - (*i).second = value; - } - found = true; - } - - it.getNext(); - } + if (found->second == value) + return; } - catch (Ogre::Exception&) - {} - if (!found) - { - if (mNewSettings.find(s) != mNewSettings.end()) - { - if (mNewSettings[s] != value) - { - mChangedSettings.push_back(std::make_pair(category, setting)); - mNewSettings[s] = value; - } - } - else - { - if (mDefaultFile.getSetting(setting, category) != value) - mChangedSettings.push_back(std::make_pair(category, setting)); - mNewSettings[s] = value; - } - } + mUserSettings[key] = value; + + mChangedSettings.insert(key); } void Manager::setInt (const std::string& setting, const std::string& category, const int value) @@ -159,12 +177,12 @@ void Manager::setInt (const std::string& setting, const std::string& category, c setString(setting, category, Ogre::StringConverter::toString(value)); } -void Manager::setFloat (const std::string& setting, const std::string& category, const float value) +void Manager::setFloat (const std::string &setting, const std::string &category, const float value) { setString(setting, category, Ogre::StringConverter::toString(value)); } -void Manager::setBool (const std::string& setting, const std::string& category, const bool value) +void Manager::setBool(const std::string &setting, const std::string &category, const bool value) { setString(setting, category, Ogre::StringConverter::toString(value)); } @@ -175,3 +193,5 @@ const CategorySettingVector Manager::apply() mChangedSettings.clear(); return vec; } + +} diff --git a/components/settings/settings.hpp b/components/settings/settings.hpp index 03b0b517ca..c16ff5a1ef 100644 --- a/components/settings/settings.hpp +++ b/components/settings/settings.hpp @@ -1,12 +1,14 @@ #ifndef COMPONENTS_SETTINGS_H #define COMPONENTS_SETTINGS_H -#include +#include +#include +#include namespace Settings { typedef std::pair < std::string, std::string > CategorySetting; - typedef std::vector< std::pair > CategorySettingVector; + typedef std::set< std::pair > CategorySettingVector; typedef std::map < CategorySetting, std::string > CategorySettingValueMap; /// @@ -15,15 +17,12 @@ namespace Settings class Manager { public: - static Ogre::ConfigFile mFile; - static Ogre::ConfigFile mDefaultFile; + static CategorySettingValueMap mDefaultSettings; + static CategorySettingValueMap mUserSettings; static CategorySettingVector mChangedSettings; ///< tracks all the settings that were changed since the last apply() call - static CategorySettingValueMap mNewSettings; - ///< tracks all the settings that are in the default file, but not in user file yet - void loadDefault (const std::string& file); ///< load file as the default settings (can be overridden by user settings) diff --git a/components/terrain/buffercache.cpp b/components/terrain/buffercache.cpp index a3e67af5bd..01032bcdae 100644 --- a/components/terrain/buffercache.cpp +++ b/components/terrain/buffercache.cpp @@ -1,9 +1,186 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "buffercache.hpp" #include #include "defs.hpp" +namespace +{ + +template +Ogre::HardwareIndexBufferSharedPtr createIndexBuffer(unsigned int flags, unsigned int verts, Ogre::HardwareIndexBuffer::IndexType type) +{ + // LOD level n means every 2^n-th vertex is kept + size_t lodLevel = (flags >> (4*4)); + + size_t lodDeltas[4]; + for (int i=0; i<4; ++i) + lodDeltas[i] = (flags >> (4*i)) & (0xf); + + bool anyDeltas = (lodDeltas[Terrain::North] || lodDeltas[Terrain::South] || lodDeltas[Terrain::West] || lodDeltas[Terrain::East]); + + size_t increment = 1 << lodLevel; + assert(increment < verts); + std::vector indices; + indices.reserve((verts-1)*(verts-1)*2*3 / increment); + + 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) + { + colStart += increment; + colEnd -= increment; + rowEnd -= increment; + rowStart += increment; + } + for (size_t row = rowStart; row < rowEnd; row += increment) + { + for (size_t col = colStart; col < colEnd; col += increment) + { + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row+increment); + indices.push_back(verts*col+row+increment); + + indices.push_back(verts*col+row); + indices.push_back(verts*(col+increment)+row); + indices.push_back(verts*(col+increment)+row+increment); + } + } + + size_t innerStep = increment; + if (anyDeltas) + { + // Now configure LOD transitions at the edges - this is pretty tedious, + // and some very long and boring code, but it works great + + // South + size_t row = 0; + size_t outerStep = 1 << (lodDeltas[Terrain::South] + lodLevel); + for (size_t col = 0; col < verts-1; col += outerStep) + { + indices.push_back(verts*col+row); + indices.push_back(verts*(col+outerStep)+row); + // Make sure not to touch the right edge + if (col+outerStep == verts-1) + indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); + else + 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 == verts-1-innerStep) + continue; + 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 = verts-1; + outerStep = size_t(1) << (lodDeltas[Terrain::North] + lodLevel); + for (size_t col = 0; col < verts-1; col += outerStep) + { + 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(verts*(col+innerStep)+row-innerStep); + else + 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 == verts-1-innerStep) + continue; + 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 = size_t(1) << (lodDeltas[Terrain::West] + lodLevel); + for (size_t row = 0; row < verts-1; row += outerStep) + { + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*col+row); + // Make sure not to touch the top edge + if (row+outerStep == verts-1) + indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); + else + 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 == verts-1-innerStep) + continue; + 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 = verts-1; + outerStep = size_t(1) << (lodDeltas[Terrain::East] + lodLevel); + for (size_t row = 0; row < verts-1; 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(verts*(col-innerStep)+row+innerStep); + else + 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 == verts-1-innerStep) + continue; + indices.push_back(verts*col+row+outerStep); + indices.push_back(verts*(col-innerStep)+row+i+innerStep); + indices.push_back(verts*(col-innerStep)+row+i); + } + } + } + + Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); + Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(type, + indices.size(), Ogre::HardwareBuffer::HBU_STATIC); + buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); + return buffer; +} + +} + namespace Terrain { @@ -39,7 +216,7 @@ namespace Terrain return buffer; } - Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(int flags) + Ogre::HardwareIndexBufferSharedPtr BufferCache::getIndexBuffer(unsigned int flags) { unsigned int verts = mNumVerts; @@ -48,151 +225,12 @@ namespace Terrain return mIndexBufferMap[flags]; } - // LOD level n means every 2^n-th vertex is kept - size_t lodLevel = (flags >> (4*4)); + Ogre::HardwareIndexBufferSharedPtr buffer; + if (verts*verts > (0xffffu)) + buffer = createIndexBuffer(flags, verts, Ogre::HardwareIndexBuffer::IT_32BIT); + else + buffer = createIndexBuffer(flags, verts, Ogre::HardwareIndexBuffer::IT_16BIT); - size_t lodDeltas[4]; - for (int i=0; i<4; ++i) - lodDeltas[i] = (flags >> (4*i)) & (0xf); - - bool anyDeltas = (lodDeltas[North] || lodDeltas[South] || lodDeltas[West] || lodDeltas[East]); - - size_t increment = 1 << lodLevel; - assert(increment < verts); - std::vector indices; - indices.reserve((verts-1)*(verts-1)*2*3 / increment); - - 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) - { - colStart += increment; - colEnd -= increment; - rowEnd -= increment; - rowStart += increment; - } - for (size_t row = rowStart; row < rowEnd; row += increment) - { - for (size_t col = colStart; col < colEnd; col += increment) - { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row+increment); - indices.push_back(verts*col+row+increment); - - indices.push_back(verts*col+row); - indices.push_back(verts*(col+increment)+row); - indices.push_back(verts*(col+increment)+row+increment); - } - } - - size_t innerStep = increment; - if (anyDeltas) - { - // Now configure LOD transitions at the edges - this is pretty tedious, - // and some very long and boring code, but it works great - - // South - size_t row = 0; - size_t outerStep = 1 << (lodDeltas[South] + lodLevel); - for (size_t col = 0; col < verts-1; col += outerStep) - { - indices.push_back(verts*col+row); - indices.push_back(verts*(col+outerStep)+row); - // Make sure not to touch the right edge - if (col+outerStep == verts-1) - indices.push_back(verts*(col+outerStep-innerStep)+row+innerStep); - else - 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 == verts-1-innerStep) - continue; - 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 = verts-1; - outerStep = size_t(1) << (lodDeltas[North] + lodLevel); - for (size_t col = 0; col < verts-1; col += outerStep) - { - 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(verts*(col+innerStep)+row-innerStep); - else - 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 == verts-1-innerStep) - continue; - 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 = size_t(1) << (lodDeltas[West] + lodLevel); - for (size_t row = 0; row < verts-1; row += outerStep) - { - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*col+row); - // Make sure not to touch the top edge - if (row+outerStep == verts-1) - indices.push_back(verts*(col+innerStep)+row+outerStep-innerStep); - else - 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 == verts-1-innerStep) - continue; - 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 = verts-1; - outerStep = size_t(1) << (lodDeltas[East] + lodLevel); - for (size_t row = 0; row < verts-1; 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(verts*(col-innerStep)+row+innerStep); - else - 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 == verts-1-innerStep) - continue; - indices.push_back(verts*col+row+outerStep); - indices.push_back(verts*(col-innerStep)+row+i+innerStep); - indices.push_back(verts*(col-innerStep)+row+i); - } - } - } - - Ogre::HardwareBufferManager* mgr = Ogre::HardwareBufferManager::getSingletonPtr(); - Ogre::HardwareIndexBufferSharedPtr buffer = mgr->createIndexBuffer(Ogre::HardwareIndexBuffer::IT_16BIT, - indices.size(), Ogre::HardwareBuffer::HBU_STATIC); - buffer->writeData(0, buffer->getSizeInBytes(), &indices[0], true); mIndexBufferMap[flags] = buffer; return buffer; } diff --git a/components/terrain/buffercache.hpp b/components/terrain/buffercache.hpp index f0aea9bfd8..887f0822ef 100644 --- a/components/terrain/buffercache.hpp +++ b/components/terrain/buffercache.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_BUFFERCACHE_H #define COMPONENTS_TERRAIN_BUFFERCACHE_H @@ -17,7 +38,7 @@ namespace Terrain /// @param flags first 4*4 bits are LOD deltas on each edge, respectively (4 bits each) /// next 4 bits are LOD level of the index buffer (LOD 0 = don't omit any vertices) - Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (int flags); + Ogre::HardwareIndexBufferSharedPtr getIndexBuffer (unsigned int flags); Ogre::HardwareVertexBufferSharedPtr getUVBuffer (); diff --git a/components/terrain/chunk.cpp b/components/terrain/chunk.cpp index 9c60ee0173..cce5abd363 100644 --- a/components/terrain/chunk.cpp +++ b/components/terrain/chunk.cpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "chunk.hpp" #include diff --git a/components/terrain/chunk.hpp b/components/terrain/chunk.hpp index 9b2ed76ac6..22b4f26ef4 100644 --- a/components/terrain/chunk.hpp +++ b/components/terrain/chunk.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_TERRAINBATCH_H #define COMPONENTS_TERRAIN_TERRAINBATCH_H diff --git a/components/terrain/defaultworld.cpp b/components/terrain/defaultworld.cpp index 9436582353..e14c64f3a0 100644 --- a/components/terrain/defaultworld.cpp +++ b/components/terrain/defaultworld.cpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "defaultworld.hpp" #include diff --git a/components/terrain/defaultworld.hpp b/components/terrain/defaultworld.hpp index 62441c4200..90e8cc2b6a 100644 --- a/components/terrain/defaultworld.hpp +++ b/components/terrain/defaultworld.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_H #define COMPONENTS_TERRAIN_H diff --git a/components/terrain/defs.hpp b/components/terrain/defs.hpp index 6859376532..6d173d1361 100644 --- a/components/terrain/defs.hpp +++ b/components/terrain/defs.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_DEFS_HPP #define COMPONENTS_TERRAIN_DEFS_HPP diff --git a/components/terrain/material.cpp b/components/terrain/material.cpp index a40e576ed9..4ff04ab93a 100644 --- a/components/terrain/material.cpp +++ b/components/terrain/material.cpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "material.hpp" #include diff --git a/components/terrain/material.hpp b/components/terrain/material.hpp index b9000cb1b9..f35080e7c6 100644 --- a/components/terrain/material.hpp +++ b/components/terrain/material.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_MATERIAL_H #define COMPONENTS_TERRAIN_MATERIAL_H diff --git a/components/terrain/quadtreenode.cpp b/components/terrain/quadtreenode.cpp index 57379a334d..5fce5eae91 100644 --- a/components/terrain/quadtreenode.cpp +++ b/components/terrain/quadtreenode.cpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "quadtreenode.hpp" #include @@ -415,7 +436,7 @@ void QuadTreeNode::updateIndexBuffers() // Fetch a suitable index buffer (which may be shared) size_t ourLod = getActualLodLevel(); - int flags = 0; + unsigned int flags = 0; for (int i=0; i<4; ++i) { @@ -436,7 +457,7 @@ void QuadTreeNode::updateIndexBuffers() if (lod > 0) { assert (lod - ourLod < (1 << 4)); - flags |= int(lod - ourLod) << (4*i); + flags |= static_cast(lod - ourLod) << (4*i); } } flags |= 0 /*((int)mAdditionalLod)*/ << (4*4); diff --git a/components/terrain/quadtreenode.hpp b/components/terrain/quadtreenode.hpp index 6265727011..1ecd6fcd56 100644 --- a/components/terrain/quadtreenode.hpp +++ b/components/terrain/quadtreenode.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_QUADTREENODE_H #define COMPONENTS_TERRAIN_QUADTREENODE_H @@ -98,7 +119,6 @@ namespace Terrain DefaultWorld* getTerrain() { return mTerrain; } /// Adjust LODs for the given camera position, possibly splitting up chunks or merging them. - /// @param force Always choose to render this node, even if not the perfect LOD. /// @return Did we (or all of our children) choose to render? bool update (const Ogre::Vector3& cameraPos); @@ -124,7 +144,6 @@ namespace Terrain /// call this method on their children. /// @note Do not call this before World::areLayersLoaded() == true /// @param area area in image space to put the quad - /// @param quads collect quads here so they can be deleted later void prepareForCompositeMap(Ogre::TRect area); /// Create a chunk for this node from the given data. diff --git a/components/terrain/storage.cpp b/components/terrain/storage.cpp index e69de29bb2..14009127d2 100644 --- a/components/terrain/storage.cpp +++ b/components/terrain/storage.cpp @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ diff --git a/components/terrain/storage.hpp b/components/terrain/storage.hpp index d3770ea57a..7846e91c6e 100644 --- a/components/terrain/storage.hpp +++ b/components/terrain/storage.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_STORAGE_H #define COMPONENTS_TERRAIN_STORAGE_H @@ -30,6 +51,8 @@ namespace Terrain /// Fill vertex buffers for a terrain chunk. /// @note May be called from background threads. Make sure to only call thread-safe functions from here! /// @note returned colors need to be in render-system specific format! Use RenderSystem::convertColourValue. + /// @note Vertices should be written in row-major order (a row is defined as parallel to the x-axis). + /// The specified positions should be in local space, i.e. relative to the center of the 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 diff --git a/components/terrain/terraingrid.cpp b/components/terrain/terraingrid.cpp index 6a66ac3d7c..269152e611 100644 --- a/components/terrain/terraingrid.cpp +++ b/components/terrain/terraingrid.cpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "terraingrid.hpp" #include diff --git a/components/terrain/terraingrid.hpp b/components/terrain/terraingrid.hpp index 7cbf455760..97ef6d14d3 100644 --- a/components/terrain/terraingrid.hpp +++ b/components/terrain/terraingrid.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_TERRAINGRID_H #define COMPONENTS_TERRAIN_TERRAINGRID_H diff --git a/components/terrain/world.cpp b/components/terrain/world.cpp index 93caeb8df9..5cc2647c67 100644 --- a/components/terrain/world.cpp +++ b/components/terrain/world.cpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #include "world.hpp" #include diff --git a/components/terrain/world.hpp b/components/terrain/world.hpp index beca7903ab..3e63b4c936 100644 --- a/components/terrain/world.hpp +++ b/components/terrain/world.hpp @@ -1,3 +1,24 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ #ifndef COMPONENTS_TERRAIN_WORLD_H #define COMPONENTS_TERRAIN_WORLD_H diff --git a/components/to_utf8/to_utf8.cpp b/components/to_utf8/to_utf8.cpp index c53cf62b51..cb9680441c 100644 --- a/components/to_utf8/to_utf8.cpp +++ b/components/to_utf8/to_utf8.cpp @@ -319,7 +319,9 @@ void Utf8Encoder::copyFromArray2(const char*& chp, char* &out) } } + std::ios::fmtflags f(std::cout.flags()); std::cout << "Could not find glyph " << std::hex << (int)ch << " " << (int)ch2 << " " << (int)ch3 << std::endl; + std::cout.flags(f); *(out++) = ch; // Could not find glyph, just put whatever } diff --git a/components/widgets/widgets.cpp b/components/widgets/widgets.cpp index 82839c6c96..3b6361cf88 100644 --- a/components/widgets/widgets.cpp +++ b/components/widgets/widgets.cpp @@ -7,6 +7,7 @@ #include "box.hpp" #include "imagebutton.hpp" #include "sharedstatebutton.hpp" +#include "windowcaption.hpp" namespace Gui { @@ -22,6 +23,7 @@ namespace Gui MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); MyGUI::FactoryManager::getInstance().registerFactory("Widget"); + MyGUI::FactoryManager::getInstance().registerFactory("Widget"); } } diff --git a/components/widgets/windowcaption.cpp b/components/widgets/windowcaption.cpp new file mode 100644 index 0000000000..bcb0a7c125 --- /dev/null +++ b/components/widgets/windowcaption.cpp @@ -0,0 +1,58 @@ +#include "windowcaption.hpp" + +#include + +namespace Gui +{ + + WindowCaption::WindowCaption() + : mLeft(NULL) + , mRight(NULL) + { + } + + void WindowCaption::initialiseOverride() + { + Base::initialiseOverride(); + + assignWidget(mLeft, "Left"); + assignWidget(mRight, "Right"); + + assignWidget(mClient, "Client"); + if (!mClient) + throw std::runtime_error("WindowCaption needs an EditBox Client widget in its skin"); + } + + void WindowCaption::setCaption(const MyGUI::UString &_value) + { + EditBox::setCaption(_value); + align(); + } + + void WindowCaption::setSize(const MyGUI::IntSize& _value) + { + Base::setSize(_value); + align(); + } + + void WindowCaption::setCoord(const MyGUI::IntCoord& _value) + { + Base::setCoord(_value); + align(); + } + + void WindowCaption::align() + { + MyGUI::IntSize textSize = getTextSize(); + MyGUI::Widget* caption = mClient; + caption->setSize(textSize.width + 24, caption->getHeight()); + + int barwidth = (getWidth()-caption->getWidth())/2; + caption->setPosition(barwidth, caption->getTop()); + if (mLeft) + mLeft->setCoord(0, mLeft->getTop(), barwidth, mLeft->getHeight()); + if (mRight) + mRight->setCoord(barwidth + caption->getWidth(), mRight->getTop(), barwidth, mRight->getHeight()); + } + +} diff --git a/components/widgets/windowcaption.hpp b/components/widgets/windowcaption.hpp new file mode 100644 index 0000000000..bdd4c0a2e2 --- /dev/null +++ b/components/widgets/windowcaption.hpp @@ -0,0 +1,32 @@ +#ifndef OPENMW_WIDGETS_WINDOWCAPTION_H +#define OPENMW_WIDGETS_WINDOWCAPTION_H + +#include + +namespace Gui +{ + + /// Window caption that automatically adjusts "Left" and "Right" widgets in its skin + /// based on the text size of the caption in the middle + class WindowCaption : public MyGUI::EditBox + { + MYGUI_RTTI_DERIVED(WindowCaption) + public: + WindowCaption(); + + virtual void setCaption(const MyGUI::UString &_value); + virtual void initialiseOverride(); + + virtual void setSize(const MyGUI::IntSize& _value); + virtual void setCoord(const MyGUI::IntCoord& _value); + + private: + MyGUI::Widget* mLeft; + MyGUI::Widget* mRight; + + void align(); + }; + +} + +#endif diff --git a/credits.txt b/credits.txt deleted file mode 100644 index 76efb2760a..0000000000 --- a/credits.txt +++ /dev/null @@ -1,194 +0,0 @@ -Contributors - -The OpenMW project was started in 2008 by Nicolay Korslund. -In the course of years many people have contributed to the project. - -If you feel your name is missing from this list, -please notify a developer. - - -Programmers: -Marc Zinnschlag (Zini) - Lead Programmer/Project Manager - -Adam Hogan (aurix) -Aleksandar Jovanov -Alex Haddad (rainChu) -Alex McKibben (WeirdSexy) -Alexander Nadeau (wareya) -Alexander Olofsson (Ace) -Artem Kotsynyak (greye) -Arthur Moore (EmperorArthur) -athile -Bret Curtis (psi29a) -Britt Mathis (galdor557) -cc9cii -Chris Boyce (slothlife) -Chris Robinson (KittyCat) -Cory F. Cohen (cfcohen) -Cris Mihalache (Mirceam) -darkf -Dmitry Shkurskiy (endorph) -Douglas Diniz (Dgdiniz) -Douglas Mencken (dougmencken) -dreamer-dead -Edmondo Tommasina (edmondo) -Eduard Cot (trombonecot) -Eli2 -Emanuel Guével (potatoesmaster) -eroen -Fil Krynicki (filkry) -Gašper Sedej -gugus/gus -Hallfaer Tuilinn -Internecine -Jacob Essex (Yacoby) -Jannik Heller (scrawl) -Jason Hooks (jhooks) -jeaye -Jeffrey Haines (Jyby) -Jengerer -Joel Graff (graffy) -John Blomberg (fstp) -Jordan Ayers -Jordan Milne -Julien Voisin (jvoisin/ap0) -Karl-Felix Glatzer (k1ll) -Kevin Poitra (PuppyKevin) -Lars Söderberg (Lazaroth) -lazydev -Leon Saunders (emoose) -Lukasz Gromanowski (lgro) -Manuel Edelmann (vorenon) -Marc Bouvier (CramitDeFrog) -Marcin Hulist (Gohan) -Mark Siewert (mark76) -Marco Melletti (mellotanica) -Mateusz Kołaczek (PL_kolek) -megaton -Michael Hogan (Xethik) -Michael Mc Donnell -Michael Papageorgiou (werdanith) -Michał Bień (Glorf) -Miroslav Puda (pakanek) -MiroslavR -Nathan Jeffords (blunted2night) -Nikolay Kasyanov (corristo) -nobrakal -Nolan Poe (nopoe) -Paul McElroy (Greendogo) -Pieter van der Kloet (pvdk) -Radu-Marius Popovici (rpopovici) -riothamus -Robert MacGregor (Ragora) -Rohit Nirmal -Roman Melnik (Kromgart) -Roman Proskuryakov (humbug) -sandstranger -Sandy Carter (bwrsandman) -Scott Howard -Sebastian Wick (swick) -Sergey Shambir -sir_herrbatka -Stefan Galowicz (bogglez) -Stanislav Bobrov (Jiub) -Sylvain Thesnieres (Garvek) -terrorfisch -Thomas Luppi (Digmaster) -Tom Mason (wheybags) -Torben Leif Carrington (TorbenC) -viadanna -Vincent Heuken -vocollapse - -Packagers: -Alexander Olofsson (Ace) - Windows -Bret Curtis (psi29a) - Ubuntu Linux -Edmondo Tommasina (edmondo) - Gentoo Linux -Julian Ospald (hasufell) - Gentoo Linux -Karl-Felix Glatzer (k1ll) - Linux Binaries -Kenny Armstrong (artorius) - Fedora Linux -Nikolay Kasyanov (corristo) - Mac OS X -Sandy Carter (bwrsandman) - Arch Linux - -Public Relations and Translations: -Alex McKibben (WeirdSexy) - Podcaster -Artem Kotsynyak (greye) - Russian News Writer -Jim Clauwaert (Zedd) - Public Outreach -Julien Voisin (jvoisin/ap0) - French News Writer -Tom Koenderink (Okulo) - English News Writer -Lukasz Gromanowski (lgro) - English News Writer -Mickey Lyle (raevol) - Release Manager -Pithorn - Chinese News Writer -sir_herrbatka - Polish News Writer -Dawid Lakomy (Vedyimyn) - Polish News Writer - -Website: -Lukasz Gromanowski (Lgro) - Website Administrator -Ryan Sardonic (Wry) - Wiki Editor -sir_herrbatka - Forum Administrator - -Formula Research: -Hrnchamd -Epsilon -fragonard -Greendogo -HiPhish -modred11 -Myckel -natirips -Sadler - -Artwork: -Necrod - OpenMW Logo -Mickey Lyle (raevol) - Wordpress Theme -Tom Koenderink (Okulo), SirHerrbatka, crysthala, Shnatsel - OpenMW Editor Icons - -Inactive Contributors: -Ardekantur -Armin Preiml -Berulacks -Carl Maxwell -Diggory Hardy -Dmitry Marakasov (AMDmi3) -ElderTroll -guidoj -Jan-Peter Nilsson (peppe) -Jan Borsodi -Josua Grawitter -juanmnzsk8 -Kingpix -Lordrea -Michal Sciubidlo -Nicolay Korslund -Nekochan -pchan3 -penguinroad -psi29a -sergoz -spyboot -Star-Demon -Thoronador -Yuri Krupenin - - -Additional Credits: -In this section we would like to thank people not part of OpenMW for their work. - -Thanks to Maxim Nikolaev, -for allowing us to use his excellent Morrowind fan-art on our website and in other places. - -Thanks to DokterDume, -for kindly providing us with the Moon and Star logo, -used as the application icon and project logo. - -Thanks to Kevin Ryan, -for creating the icon used for the Data Files tab of the OpenMW Launcher. - -Thanks to Georg Duffner, -for his EB Garamond fontface, see OFL.txt for his license terms. - -Thanks to Dongle, -for his Daedric fontface, see Daedric Font License.txt for his license terms. - -Thanks to DejaVu team, -for their DejaVuLGCSansMono fontface, see DejaVu Font License.txt for their license terms. diff --git a/extern/ogre-ffmpeg-videoplayer/videostate.cpp b/extern/ogre-ffmpeg-videoplayer/videostate.cpp index b365438806..0894499b23 100644 --- a/extern/ogre-ffmpeg-videoplayer/videostate.cpp +++ b/extern/ogre-ffmpeg-videoplayer/videostate.cpp @@ -175,13 +175,27 @@ void PacketQueue::clear() int VideoState::OgreResource_Read(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->stream; - return stream->read(buf, buf_size); + try + { + return stream->read(buf, buf_size); + } + catch (std::exception& e) + { + return 0; + } } int VideoState::OgreResource_Write(void *user_data, uint8_t *buf, int buf_size) { Ogre::DataStreamPtr stream = static_cast(user_data)->stream; - return stream->write(buf, buf_size); + try + { + return stream->write(buf, buf_size); + } + catch (std::exception& e) + { + return 0; + } } int64_t VideoState::OgreResource_Seek(void *user_data, int64_t offset, int whence) @@ -346,11 +360,11 @@ double VideoState::synchronize_video(AVFrame *src_frame, double pts) * buffer. We use this to store the global_pts in * a frame at the time it is allocated. */ -static uint64_t global_video_pkt_pts = static_cast(AV_NOPTS_VALUE); +static int64_t global_video_pkt_pts = AV_NOPTS_VALUE; static int our_get_buffer(struct AVCodecContext *c, AVFrame *pic) { int ret = avcodec_default_get_buffer(c, pic); - uint64_t *pts = (uint64_t*)av_malloc(sizeof(uint64_t)); + int64_t *pts = (int64_t*)av_malloc(sizeof(int64_t)); *pts = global_video_pkt_pts; pic->opaque = pts; return ret; @@ -397,10 +411,10 @@ void VideoState::video_thread_loop(VideoState *self) throw std::runtime_error("Error decoding video frame"); double pts = 0; - if((uint64_t)packet->dts != AV_NOPTS_VALUE) + if(packet->dts != AV_NOPTS_VALUE) pts = packet->dts; - else if(pFrame->opaque && *(uint64_t*)pFrame->opaque != AV_NOPTS_VALUE) - pts = *(uint64_t*)pFrame->opaque; + else if(pFrame->opaque && *(int64_t*)pFrame->opaque != AV_NOPTS_VALUE) + pts = *(int64_t*)pFrame->opaque; pts *= av_q2d((*self->video_st)->time_base); av_free_packet(packet); diff --git a/extern/oics/ICSInputControlSystem.cpp b/extern/oics/ICSInputControlSystem.cpp index 7dc026c7ef..e1bd587288 100644 --- a/extern/oics/ICSInputControlSystem.cpp +++ b/extern/oics/ICSInputControlSystem.cpp @@ -36,6 +36,9 @@ namespace ICS , mDetectingBindingControl(NULL) , mLog(log) , mXmouseAxisBinded(false), mYmouseAxisBinded(false) + , mClientHeight(1) + , mClientWidth(1) + , mDetectingBindingDirection(Control::STOP) { ICS_LOG(" - Creating InputControlSystem - "); diff --git a/extern/oics/ICSInputControlSystem_joystick.cpp b/extern/oics/ICSInputControlSystem_joystick.cpp index 8bf9317880..0fcd36bbd4 100644 --- a/extern/oics/ICSInputControlSystem_joystick.cpp +++ b/extern/oics/ICSInputControlSystem_joystick.cpp @@ -299,7 +299,7 @@ namespace ICS { if(it->second.find(axis) != it->second.end()) { - mControlsJoystickPOVBinderMap[deviceId].find(index)->second.erase( it->second.find(axis) ); + it->second.erase( it->second.find(axis) ); } } } diff --git a/extern/sdl4ogre/events.h b/extern/sdl4ogre/events.h index 0fb4d6f060..6961e81fc8 100644 --- a/extern/sdl4ogre/events.h +++ b/extern/sdl4ogre/events.h @@ -1,8 +1,8 @@ #ifndef _SFO_EVENTS_H #define _SFO_EVENTS_H -#include - +#include +#include //////////// // Events // @@ -65,7 +65,7 @@ public: virtual ~WindowListener() {} /** @remarks The window's visibility changed */ - virtual void windowVisibilityChange( bool visible ) {}; + virtual void windowVisibilityChange( bool visible ) {} /** @remarks The window got / lost input focus */ virtual void windowFocusChange( bool have_focus ) {} diff --git a/extern/sdl4ogre/sdlcursormanager.cpp b/extern/sdl4ogre/sdlcursormanager.cpp index 5ef274b7e7..7623d57dbf 100644 --- a/extern/sdl4ogre/sdlcursormanager.cpp +++ b/extern/sdl4ogre/sdlcursormanager.cpp @@ -4,6 +4,9 @@ #include #include +#include +#include + #include namespace SFO diff --git a/extern/sdl4ogre/sdlcursormanager.hpp b/extern/sdl4ogre/sdlcursormanager.hpp index 7e3e59b4a5..58324fc01c 100644 --- a/extern/sdl4ogre/sdlcursormanager.hpp +++ b/extern/sdl4ogre/sdlcursormanager.hpp @@ -1,11 +1,12 @@ #ifndef SDL4OGRE_CURSORMANAGER_H #define SDL4OGRE_CURSORMANAGER_H -#include - #include "cursormanager.hpp" #include +struct SDL_Cursor; +struct SDL_Surface; + namespace SFO { class SDLCursorManager : diff --git a/extern/sdl4ogre/sdlinputwrapper.cpp b/extern/sdl4ogre/sdlinputwrapper.cpp index db46a4af97..a6c7d3e5ca 100644 --- a/extern/sdl4ogre/sdlinputwrapper.cpp +++ b/extern/sdl4ogre/sdlinputwrapper.cpp @@ -28,7 +28,10 @@ namespace SFO mWantGrab(false), mWantRelative(false), mWantMouseVisible(false), - mAllowGrab(grab) + mAllowGrab(grab), + mWarpX(0), + mWarpY(0), + mFirstMouseMove(true) { _setupOISKeys(); } @@ -117,7 +120,9 @@ namespace SFO case SDL_CLIPBOARDUPDATE: break; // We don't need this event, clipboard is retrieved on demand default: + std::ios::fmtflags f(std::cerr.flags()); std::cerr << "Unhandled SDL event of type 0x" << std::hex << evt.type << std::endl; + std::cerr.flags(f); break; } } @@ -312,6 +317,13 @@ namespace SFO pack_evt.y = mMouseY = evt.motion.y; pack_evt.xrel = evt.motion.xrel; pack_evt.yrel = evt.motion.yrel; + if (mFirstMouseMove) + { + // first event should be treated as non-relative, since there's no point of reference + // SDL then (incorrectly) uses (0,0) as point of reference, on Linux at least... + pack_evt.xrel = pack_evt.yrel = 0; + mFirstMouseMove = false; + } } else if(evt.type == SDL_MOUSEWHEEL) { diff --git a/extern/sdl4ogre/sdlinputwrapper.hpp b/extern/sdl4ogre/sdlinputwrapper.hpp index 339e99de14..af16ab68d8 100644 --- a/extern/sdl4ogre/sdlinputwrapper.hpp +++ b/extern/sdl4ogre/sdlinputwrapper.hpp @@ -71,6 +71,8 @@ namespace SFO bool mGrabPointer; bool mMouseRelative; + bool mFirstMouseMove; + Sint32 mMouseZ; Sint32 mMouseX; Sint32 mMouseY; diff --git a/files/CMakeLists.txt b/files/CMakeLists.txt index 6ee219a8ad..aa8e3f0509 100644 --- a/files/CMakeLists.txt +++ b/files/CMakeLists.txt @@ -39,15 +39,10 @@ set(MATERIAL_FILES selection.mat selection.shader selection.shaderset - watersim_heightmap.shader - watersim_addimpulse.shader - watersim_heighttonormal.shader - watersim_common.h - watersim.mat - watersim.shaderset mygui.mat mygui.shader mygui.shaderset + ripples.particle ) copy_all_files(${CMAKE_CURRENT_SOURCE_DIR}/water "${OpenMW_BINARY_DIR}/resources/water/" "${WATER_FILES}") diff --git a/files/mac/Info.plist b/files/mac/Info.plist index bc7efd0754..f832b09c9d 100644 --- a/files/mac/Info.plist +++ b/files/mac/Info.plist @@ -7,7 +7,7 @@ CFBundleDevelopmentRegion English CFBundleExecutable - omwlauncher + openmw-launcher CFBundleInfoDictionaryVersion 6.0 CFBundleLongVersionString diff --git a/files/mac/opencs.icns b/files/mac/openmw-cs.icns similarity index 100% rename from files/mac/opencs.icns rename to files/mac/openmw-cs.icns diff --git a/files/materials/moon.shader b/files/materials/moon.shader index eb7243d3f2..0e1a4ffc8d 100644 --- a/files/materials/moon.shader +++ b/files/materials/moon.shader @@ -38,16 +38,14 @@ shUniform(float4x4, projection) @shAutoConstant(projection, projection_matrix) SH_START_PROGRAM { - - float4 tex = shSample(diffuseMap, UV); - - shOutputColour(0) = float4(materialEmissive.xyz, 1) * tex; - - shOutputColour(0).a = shSample(alphaMap, UV).a * materialDiffuse.a; - - shOutputColour(0).rgb += (1.0-tex.a) * shOutputColour(0).a * atmosphereColour.rgb; //fill dark side of moon with atmosphereColour - shOutputColour(0).rgb += (1.0-materialDiffuse.a) * atmosphereColour.rgb; //fade bump + float4 phaseTex = shSample(diffuseMap, UV); + float4 fullCircleTex = shSample(alphaMap, UV); + shOutputColour(0).a = max(phaseTex.a, fullCircleTex.a) * materialDiffuse.a; + + shOutputColour(0).xyz = fullCircleTex.xyz * atmosphereColour.xyz; + shOutputColour(0).xyz = shLerp(shOutputColour(0).xyz, phaseTex.xyz, phaseTex.a); + shOutputColour(0).xyz *= materialEmissive.xyz; } #endif diff --git a/files/materials/ripples.particle b/files/materials/ripples.particle new file mode 100644 index 0000000000..e8402b6914 --- /dev/null +++ b/files/materials/ripples.particle @@ -0,0 +1,26 @@ +particle_system openmw/Ripples +{ + material openmw/Ripple + particle_width 50 + particle_height 50 + // To make the particles move with the scene node when the waterlevel changes + local_space true + quota 300 + billboard_type perpendicular_common + common_up_vector 0 1 0 + common_direction 0 0 1 + + affector ColourFader + { + alpha -0.33 + } + + affector Scaler + { + rate 100 + } + + affector Rotator + { + } +} diff --git a/files/materials/sky.mat b/files/materials/sky.mat index ccf2a8053c..c2e8ddeb09 100644 --- a/files/materials/sky.mat +++ b/files/materials/sky.mat @@ -50,7 +50,7 @@ material openmw_moon texture_unit alphaMap { - direct_texture textures\tx_secunda_full.dds + texture_alias $alphatexture } } } diff --git a/files/materials/terrain.shader b/files/materials/terrain.shader index 8384588e4f..a4ca10fcc3 100644 --- a/files/materials/terrain.shader +++ b/files/materials/terrain.shader @@ -1,3 +1,25 @@ +/* + * Copyright (c) 2015 scrawl + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + #include "core.h" #define IS_FIRST_PASS (@shPropertyString(pass_index) == 0) diff --git a/files/materials/water.mat b/files/materials/water.mat index ade55f326f..cf03be39e5 100644 --- a/files/materials/water.mat +++ b/files/materials/water.mat @@ -75,3 +75,27 @@ material Water } } } + +material openmw/Ripple +{ + // this will be overridden by Water_RippleFrameCount fallback setting + anim_texture2 textures\water\ripple.dds 4 0.25 + pass + { + scene_blend alpha_blend + depth_write off + cull_hardware none + diffuse vertexcolour + emissive 1 1 1 + ambient 0 0 0 + texture_unit diffuseMap + { + create_in_ffp true + anim_texture2 $anim_texture2 + + // to make sure rotating doesn't cause the texture to repeat + tex_address_mode border + tex_border_colour 0 0 0 0 + } + } +} diff --git a/files/materials/water.shader b/files/materials/water.shader index a6f49e0a1a..10d63cdf42 100644 --- a/files/materials/water.shader +++ b/files/materials/water.shader @@ -4,7 +4,7 @@ #define SIMPLE_WATER @shGlobalSettingBool(simple_water) #if SIMPLE_WATER - // --------------------------------------- SIMPLE WATER --------------------------------------------------- + // --------------------------------------- SIMPLE WATER --------------------------------------------------- #define FOG @shGlobalSettingBool(fog) @@ -42,7 +42,7 @@ { shOutputColour(0).xyz = shSample(animatedTexture, UV * float2(15.0, 15.0)).xyz * float3(1.0, 1.0, 1.0); shOutputColour(0).w = 0.7; - + #if FOG float fogValue = shSaturate((depth - fogParams.y) * fogParams.w); shOutputColour(0).xyz = shLerp (shOutputColour(0).xyz, fogColor, fogValue); @@ -64,16 +64,13 @@ #include "shadows.h" #endif -#define RIPPLES 1 #define REFRACTION @shGlobalSettingBool(refraction) #ifdef SH_VERTEX_SHADER SH_BEGIN_PROGRAM shUniform(float4x4, wvp) @shAutoConstant(wvp, worldviewproj_matrix) - shVertexInput(float2, uv0) - shOutput(float2, UV) - + shOutput(float3, screenCoordsPassthrough) shOutput(float4, position) shOutput(float, depthPassthrough) @@ -98,26 +95,25 @@ SH_START_PROGRAM { shOutputPosition = shMatrixMult(wvp, shInputPosition); - UV = uv0; - - + + #if !SH_GLSL float4x4 scalemat = float4x4( 0.5, 0.0, 0.0, 0.5, 0.0, -0.5, 0.0, 0.5, 0.0, 0.0, 0.5, 0.5, 0.0, 0.0, 0.0, 1.0 ); - #else - mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, + #else + mat4 scalemat = mat4(0.5, 0.0, 0.0, 0.0, 0.0, -0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0); #endif - + float4 texcoordProj = shMatrixMult(scalemat, shOutputPosition); screenCoordsPassthrough = float3(texcoordProj.x, texcoordProj.y, texcoordProj.w); - + position = shInputPosition; - + depthPassthrough = shOutputPosition.z; @@ -138,29 +134,28 @@ #define VISIBILITY 1500.0 // how far you can look through water - #define BIG_WAVES_X 0.3 // strength of big waves - #define BIG_WAVES_Y 0.3 - - #define MID_WAVES_X 0.3 // strength of middle sized waves - #define MID_WAVES_Y 0.15 - - #define SMALL_WAVES_X 0.15 // strength of small waves - #define SMALL_WAVES_Y 0.1 - - #define WAVE_CHOPPYNESS 0.15 // wave choppyness - #define WAVE_SCALE 75.0 // overall wave scale + #define BIG_WAVES_X 0.1 // strength of big waves + #define BIG_WAVES_Y 0.1 - #define BUMP 1.5 // overall water surface bumpiness - #define REFL_BUMP 0.08 // reflection distortion amount + #define MID_WAVES_X 0.1 // strength of middle sized waves + #define MID_WAVES_Y 0.1 + + #define SMALL_WAVES_X 0.1 // strength of small waves + #define SMALL_WAVES_Y 0.1 + + #define WAVE_CHOPPYNESS 0.05 // wave choppyness + #define WAVE_SCALE 75.0 // overall wave scale + + #define BUMP 0.5 // overall water surface bumpiness + #define REFL_BUMP 0.15 // reflection distortion amount #define REFR_BUMP 0.06 // refraction distortion amount #define SCATTER_AMOUNT 0.3 // amount of sunlight scattering #define SCATTER_COLOUR float3(0.0,1.0,0.95) // colour of sunlight scattering - #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction - - #define SPEC_HARDNESS 256.0 // specular highlights hardness - + #define SUN_EXT float3(0.45, 0.55, 0.68) //sunlight extinction + + #define SPEC_HARDNESS 256.0 // specular highlights hardness // --------------------------------------------------------------- @@ -187,18 +182,12 @@ } SH_BEGIN_PROGRAM - shInput(float2, UV) shInput(float3, screenCoordsPassthrough) shInput(float4, position) shInput(float, depthPassthrough) - #if RIPPLES - shUniform(float3, rippleCenter) @shSharedParameter(rippleCenter, rippleCenter) - shUniform(float, rippleAreaLength) @shSharedParameter(rippleAreaLength, rippleAreaLength) - #endif - shUniform(float, far) @shAutoConstant(far, far_clip_distance) - + shSampler2D(reflectionMap) #if REFRACTION shSampler2D(refractionMap) @@ -206,27 +195,23 @@ shSampler2D(depthMap) shSampler2D(normalMap) - #if RIPPLES - shSampler2D(rippleNormalMap) - shUniform(float4x4, wMat) @shAutoConstant(wMat, world_matrix) - #endif - + shUniform(float4x4, wMat) @shAutoConstant(wMat, world_matrix) shUniform(float3, windDir_windSpeed) @shSharedParameter(windDir_windSpeed) #define WIND_SPEED windDir_windSpeed.z #define WIND_DIR windDir_windSpeed.xy - + shUniform(float, waterTimer) @shSharedParameter(waterTimer) shUniform(float2, waterSunFade_sunHeight) @shSharedParameter(waterSunFade_sunHeight) - + shUniform(float4, sunPosition) @shAutoConstant(sunPosition, light_position, 0) shUniform(float4, sunSpecular) @shAutoConstant(sunSpecular, light_specular_colour, 0) - + shUniform(float, renderTargetFlipping) @shAutoConstant(renderTargetFlipping, render_target_flipping) - - + + shUniform(float3, fogColor) @shAutoConstant(fogColor, fog_colour) shUniform(float4, fogParams) @shAutoConstant(fogParams, fog_params) - + shUniform(float4, cameraPos) @shAutoConstant(cameraPos, camera_position_object_space) @@ -247,10 +232,14 @@ #if SHADOWS || SHADOWS_PSSM shUniform(float4, shadowFar_fadeStart) @shSharedParameter(shadowFar_fadeStart) #endif - + SH_START_PROGRAM { + float3 worldPos = shMatrixMult (wMat, position).xyz; + float2 UV = worldPos.xy / (8192.0*5.0) * 3.0; + UV.y *= -1.0; + #if SHADOWS float shadow = depthShadowPCF (shadowMap0, lightSpacePos0, invShadowmapSize0); #endif @@ -278,50 +267,45 @@ float3 normal0 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.015,-waterTimer*0.005)).rgb - 1.0; nCoord = UV * (WAVE_SCALE * 0.1) + WIND_DIR * waterTimer * (WIND_SPEED*0.08)-(normal0.xy/normal0.zz)*WAVE_CHOPPYNESS; float3 normal1 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.020,+waterTimer*0.015)).rgb - 1.0; - + nCoord = UV * (WAVE_SCALE * 0.25) + WIND_DIR * waterTimer * (WIND_SPEED*0.07)-(normal1.xy/normal1.zz)*WAVE_CHOPPYNESS; float3 normal2 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.04,-waterTimer*0.03)).rgb - 1.0; nCoord = UV * (WAVE_SCALE * 0.5) + WIND_DIR * waterTimer * (WIND_SPEED*0.09)-(normal2.xy/normal2.z)*WAVE_CHOPPYNESS; float3 normal3 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.03,+waterTimer*0.04)).rgb - 1.0; - + nCoord = UV * (WAVE_SCALE* 1.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.4)-(normal3.xy/normal3.zz)*WAVE_CHOPPYNESS; - float3 normal4 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; + float3 normal4 = 2.0 * shSample(normalMap, nCoord + float2(-waterTimer*0.02,+waterTimer*0.1)).rgb - 1.0; nCoord = UV * (WAVE_SCALE * 2.0) + WIND_DIR * waterTimer * (WIND_SPEED*0.7)-(normal4.xy/normal4.zz)*WAVE_CHOPPYNESS; float3 normal5 = 2.0 * shSample(normalMap, nCoord + float2(+waterTimer*0.1,-waterTimer*0.06)).rgb - 1.0; - - + + float3 normal = (normal0 * BIG_WAVES_X + normal1 * BIG_WAVES_Y + normal2 * MID_WAVES_X + normal3 * MID_WAVES_Y + normal4 * SMALL_WAVES_X + normal5 * SMALL_WAVES_Y); - float4 worldPosition = shMatrixMult(wMat, float4(position.xyz, 1)); - float2 relPos = (worldPosition.xy - rippleCenter.xy) / rippleAreaLength + 0.5; - float3 normal_ripple = normalize(shSample(rippleNormalMap, relPos.xy).xyz * 2.0 - 1.0); - - //normal = normalize(normal + normal_ripple); - normal = normalize(float3(normal.x * BUMP + normal_ripple.x, normal.y * BUMP + normal_ripple.y, normal.z)); + normal = normalize(float3(normal.x * BUMP, normal.y * BUMP, normal.z)); normal = float3(normal.x, normal.y, -normal.z); - // normal for sunlight scattering + // normal for sunlight scattering float3 lNormal = (normal0 * BIG_WAVES_X*0.5 + normal1 * BIG_WAVES_Y*0.5 + normal2 * MID_WAVES_X*0.2 + normal3 * MID_WAVES_Y*0.2 + normal4 * SMALL_WAVES_X*0.1 + normal5 * SMALL_WAVES_Y*0.1).xyz; lNormal = normalize(float3(lNormal.x * BUMP, lNormal.y * BUMP, lNormal.z)); lNormal = float3(lNormal.x, lNormal.y, -lNormal.z); - + float3 lVec = normalize(sunPosition.xyz); float3 vVec = normalize(position.xyz - cameraPos.xyz); - - + + float isUnderwater = (cameraPos.z > 0.0) ? 0.0 : 1.0; - + // sunlight scattering float3 pNormal = float3(0,0,1); float3 lR = reflect(lVec, lNormal); float3 llR = reflect(lVec, pNormal); - + float s = shSaturate(dot(lR, vVec)*2.0-1.2); float lightScatter = shadow * shSaturate(dot(-lVec,lNormal)*0.7+0.3) * s * SCATTER_AMOUNT * waterSunFade_sunHeight.x * shSaturate(1.0-exp(-waterSunFade_sunHeight.y)); float3 scatterColour = shLerp(float3(SCATTER_COLOUR)*float3(1.0,0.4,0.0), SCATTER_COLOUR, shSaturate(1.0-exp(-waterSunFade_sunHeight.y*SUN_EXT))); @@ -329,12 +313,12 @@ // fresnel float ior = (cameraPos.z>0.0)?(1.333/1.0):(1.0/1.333); //air to water; water to air float fresnel = fresnel_dielectric(-vVec, normal, ior); - + fresnel = shSaturate(fresnel); - + // reflection float3 reflection = shSample(reflectionMap, screenCoords+(normal.xy*REFL_BUMP)).rgb; - + // refraction float3 R = reflect(vVec, normal); diff --git a/files/materials/watersim.mat b/files/materials/watersim.mat deleted file mode 100644 index b58b1a8512..0000000000 --- a/files/materials/watersim.mat +++ /dev/null @@ -1,59 +0,0 @@ -material HeightmapSimulation -{ - allow_fixed_function false - pass - { - depth_check off - depth_write off - vertex_program transform_vertex - fragment_program watersim_fragment - - texture_unit heightPrevSampler - { - tex_address_mode border - tex_border_colour 0 0 0 - texture_alias Heightmap0 - } - texture_unit heightCurrentSampler - { - tex_address_mode border - tex_border_colour 0 0 0 - texture_alias Heightmap1 - } - } -} - -material HeightToNormalMap -{ - allow_fixed_function false - pass - { - depth_check off - depth_write off - vertex_program transform_vertex - fragment_program height_to_normal_fragment - - texture_unit heightCurrentSampler - { - texture_alias Heightmap2 - } - } -} - -material AddImpulse -{ - allow_fixed_function false - pass - { - depth_check off - depth_write off - scene_blend alpha_blend - vertex_program transform_vertex - fragment_program add_impulse_fragment - - texture_unit alphaMap - { - texture circle.png - } - } -} diff --git a/files/materials/watersim.shaderset b/files/materials/watersim.shaderset deleted file mode 100644 index ea512e25f0..0000000000 --- a/files/materials/watersim.shaderset +++ /dev/null @@ -1,31 +0,0 @@ -shader_set transform_vertex -{ - source quad.shader - type vertex - profiles_cg vs_2_0 vp40 arbvp1 - profiles_hlsl vs_2_0 -} - -shader_set watersim_fragment -{ - source watersim_heightmap.shader - type fragment - profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} - -shader_set height_to_normal_fragment -{ - source watersim_heighttonormal.shader - type fragment - profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} - -shader_set add_impulse_fragment -{ - source watersim_addimpulse.shader - type fragment - profiles_cg ps_3_0 ps_2_x ps_2_0 fp40 arbfp1 - profiles_hlsl ps_3_0 ps_2_0 -} diff --git a/files/materials/watersim_addimpulse.shader b/files/materials/watersim_addimpulse.shader deleted file mode 100644 index 3ca4192cd7..0000000000 --- a/files/materials/watersim_addimpulse.shader +++ /dev/null @@ -1,12 +0,0 @@ -#include "core.h" -#include "watersim_common.h" - - SH_BEGIN_PROGRAM - shInput(float2, UV) - shSampler2D(alphaMap) - - SH_START_PROGRAM - { - shOutputColour(0) = EncodeHeightmap(1.0); - shOutputColour(0).a = shSample (alphaMap, UV.xy).a; - } diff --git a/files/materials/watersim_common.h b/files/materials/watersim_common.h deleted file mode 100644 index aa7a636a06..0000000000 --- a/files/materials/watersim_common.h +++ /dev/null @@ -1,25 +0,0 @@ -float DecodeHeightmap(float4 heightmap) -{ - float4 table = float4(1.0, -1.0, 0.0, 0.0); - return dot(heightmap, table); -} - -float DecodeHeightmap(shTexture2D HeightmapSampler, float2 texcoord) -{ - float4 heightmap = shSample(HeightmapSampler, texcoord); - return DecodeHeightmap(heightmap); -} - -float4 EncodeHeightmap(float fHeight) -{ - float h = fHeight; - float positive = fHeight > 0.0 ? fHeight : 0.0; - float negative = fHeight < 0.0 ? -fHeight : 0.0; - - float4 color = float4(0,0,0,0); - - color.r = positive; - color.g = negative; - - return color; -} diff --git a/files/materials/watersim_heightmap.shader b/files/materials/watersim_heightmap.shader deleted file mode 100644 index ec8ae4174a..0000000000 --- a/files/materials/watersim_heightmap.shader +++ /dev/null @@ -1,51 +0,0 @@ -#include "core.h" - -#define DAMPING 0.95 - -#include "watersim_common.h" - - SH_BEGIN_PROGRAM - shInput(float2, UV) - shSampler2D(heightPrevSampler) - shSampler2D(heightCurrentSampler) - shUniform(float3, previousFrameOffset) @shSharedParameter(previousFrameOffset, previousFrameOffset) - shUniform(float3, currentFrameOffset) @shSharedParameter(currentFrameOffset, currentFrameOffset) - shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize) - - SH_START_PROGRAM - { -#if !SH_HLSL - const float3 offset[4] = float3[4]( - float3(-1.0, 0.0, 0.25), - float3( 1.0, 0.0, 0.25), - float3( 0.0,-1.0, 0.25), - float3( 0.0, 1.0, 0.25) - ); -#else - const float3 offset[4] = { - float3(-1.0, 0.0, 0.25), - float3( 1.0, 0.0, 0.25), - float3( 0.0,-1.0, 0.25), - float3( 0.0, 1.0, 0.25) - }; -#endif - - float fHeightPrev = DecodeHeightmap(heightPrevSampler, UV.xy + previousFrameOffset.xy + currentFrameOffset.xy); - - float fNeighCurrent = 0; - for ( int i=0; i<4; i++ ) - { - float2 vTexcoord = UV + currentFrameOffset.xy + offset[i].xy * rippleTextureSize.xy; - fNeighCurrent += (DecodeHeightmap(heightCurrentSampler, vTexcoord) * offset[i].z); - } - - float fHeight = fNeighCurrent * 2.0 - fHeightPrev; - - fHeight *= DAMPING; - - shOutputColour(0) = EncodeHeightmap(fHeight); - } - - - - diff --git a/files/materials/watersim_heighttonormal.shader b/files/materials/watersim_heighttonormal.shader deleted file mode 100644 index eaa4af4983..0000000000 --- a/files/materials/watersim_heighttonormal.shader +++ /dev/null @@ -1,36 +0,0 @@ -#include "core.h" -#include "watersim_common.h" - - SH_BEGIN_PROGRAM - shInput(float2, UV) - shSampler2D(heightCurrentSampler) - shUniform(float4, rippleTextureSize) @shSharedParameter(rippleTextureSize, rippleTextureSize) - - SH_START_PROGRAM - { -#if !SH_HLSL - float2 offset[4] = float2[4] ( - float2(-1.0, 0.0), - float2( 1.0, 0.0), - float2( 0.0,-1.0), - float2( 0.0, 1.0) - ); -#else - float2 offset[4] = { - float2(-1.0, 0.0), - float2( 1.0, 0.0), - float2( 0.0,-1.0), - float2( 0.0, 1.0) - }; -#endif - - float fHeightL = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[0]*rippleTextureSize.xy); - float fHeightR = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[1]*rippleTextureSize.xy); - float fHeightT = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[2]*rippleTextureSize.xy); - float fHeightB = DecodeHeightmap(heightCurrentSampler, UV.xy + offset[3]*rippleTextureSize.xy); - - float3 n = float3(fHeightB - fHeightT, fHeightR - fHeightL, 1.0); - float3 normal = (n + 1.0) * 0.5; - - shOutputColour(0) = float4(normal.rgb, 1.0); - } diff --git a/files/mygui/CMakeLists.txt b/files/mygui/CMakeLists.txt index 638266b0ce..13d1a9e1a4 100644 --- a/files/mygui/CMakeLists.txt +++ b/files/mygui/CMakeLists.txt @@ -86,6 +86,7 @@ set(MYGUI_FILES openmw_edit_note.layout openmw_debug_window.layout openmw_debug_window.skin.xml + openmw_jail_screen.layout DejaVuLGCSansMono.ttf ../launcher/images/openmw.png OpenMWResourcePlugin.xml diff --git a/files/mygui/openmw_dialogue_window.layout b/files/mygui/openmw_dialogue_window.layout index 5a7cd772da..a0a4bf4416 100644 --- a/files/mygui/openmw_dialogue_window.layout +++ b/files/mygui/openmw_dialogue_window.layout @@ -6,8 +6,8 @@ - - + + diff --git a/files/mygui/openmw_edit_effect.layout b/files/mygui/openmw_edit_effect.layout index 5dc53e5057..6123f90a73 100644 --- a/files/mygui/openmw_edit_effect.layout +++ b/files/mygui/openmw_edit_effect.layout @@ -12,9 +12,12 @@ - + + + + @@ -22,9 +25,12 @@ - + + + + @@ -49,9 +55,12 @@ - + + + + @@ -66,9 +75,12 @@ - + + + + diff --git a/files/mygui/openmw_enchanting_dialog.layout b/files/mygui/openmw_enchanting_dialog.layout index 811a214e3e..b90fc7a775 100644 --- a/files/mygui/openmw_enchanting_dialog.layout +++ b/files/mygui/openmw_enchanting_dialog.layout @@ -117,7 +117,7 @@ - + @@ -133,15 +133,13 @@ - - - - - + + + diff --git a/files/mygui/openmw_hud.layout b/files/mygui/openmw_hud.layout index 640e5867f4..84fd9d247b 100644 --- a/files/mygui/openmw_hud.layout +++ b/files/mygui/openmw_hud.layout @@ -108,8 +108,6 @@ - - diff --git a/files/mygui/openmw_hud_box.skin.xml b/files/mygui/openmw_hud_box.skin.xml index e7457e7d29..4e63497680 100644 --- a/files/mygui/openmw_hud_box.skin.xml +++ b/files/mygui/openmw_hud_box.skin.xml @@ -6,14 +6,14 @@ + + + + - - - - @@ -22,14 +22,14 @@ + + + + - - - - diff --git a/files/mygui/openmw_interactive_messagebox.layout b/files/mygui/openmw_interactive_messagebox.layout index a1dbc5aa83..a72bebf3a9 100644 --- a/files/mygui/openmw_interactive_messagebox.layout +++ b/files/mygui/openmw_interactive_messagebox.layout @@ -1,7 +1,7 @@ - + diff --git a/files/mygui/openmw_jail_screen.layout b/files/mygui/openmw_jail_screen.layout new file mode 100644 index 0000000000..4385494e1c --- /dev/null +++ b/files/mygui/openmw_jail_screen.layout @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_layers.xml b/files/mygui/openmw_layers.xml index 38a98d133f..50f83aaa2c 100644 --- a/files/mygui/openmw_layers.xml +++ b/files/mygui/openmw_layers.xml @@ -11,6 +11,7 @@ + diff --git a/files/mygui/openmw_levelup_dialog.layout b/files/mygui/openmw_levelup_dialog.layout index 190f16e5d9..ead181d26a 100644 --- a/files/mygui/openmw_levelup_dialog.layout +++ b/files/mygui/openmw_levelup_dialog.layout @@ -50,7 +50,7 @@ - + @@ -64,7 +64,7 @@ - + @@ -78,7 +78,7 @@ - + @@ -92,7 +92,7 @@ - + @@ -107,7 +107,7 @@ - + @@ -121,7 +121,7 @@ - + @@ -135,7 +135,7 @@ - + @@ -149,7 +149,7 @@ - + diff --git a/files/mygui/openmw_list.skin.xml b/files/mygui/openmw_list.skin.xml index 21b17c8f07..ce8209e3d5 100644 --- a/files/mygui/openmw_list.skin.xml +++ b/files/mygui/openmw_list.skin.xml @@ -157,6 +157,15 @@ + + + + + + + + + diff --git a/files/mygui/openmw_loading_screen.layout b/files/mygui/openmw_loading_screen.layout index 49ee0a1de5..8a1514f844 100644 --- a/files/mygui/openmw_loading_screen.layout +++ b/files/mygui/openmw_loading_screen.layout @@ -4,9 +4,9 @@ - + - + diff --git a/files/mygui/openmw_magicselection_dialog.layout b/files/mygui/openmw_magicselection_dialog.layout index a89795473f..bf4cb71d48 100644 --- a/files/mygui/openmw_magicselection_dialog.layout +++ b/files/mygui/openmw_magicselection_dialog.layout @@ -3,12 +3,10 @@ - + - - - + diff --git a/files/mygui/openmw_map_window.layout b/files/mygui/openmw_map_window.layout index 605c3d6fff..b38097dce5 100644 --- a/files/mygui/openmw_map_window.layout +++ b/files/mygui/openmw_map_window.layout @@ -6,8 +6,6 @@ - - diff --git a/files/mygui/openmw_merchantrepair.layout b/files/mygui/openmw_merchantrepair.layout index 360f5f4f09..23ab6008ff 100644 --- a/files/mygui/openmw_merchantrepair.layout +++ b/files/mygui/openmw_merchantrepair.layout @@ -4,7 +4,6 @@ - @@ -20,10 +19,10 @@ - + - + diff --git a/files/mygui/openmw_persuasion_dialog.layout b/files/mygui/openmw_persuasion_dialog.layout index 87851b479b..6fb2989141 100644 --- a/files/mygui/openmw_persuasion_dialog.layout +++ b/files/mygui/openmw_persuasion_dialog.layout @@ -1,17 +1,13 @@ - + - + - - - - @@ -39,7 +35,11 @@ - + + + + + diff --git a/files/mygui/openmw_resources.xml b/files/mygui/openmw_resources.xml index 7d7ba07b6e..423281a621 100644 --- a/files/mygui/openmw_resources.xml +++ b/files/mygui/openmw_resources.xml @@ -137,4 +137,19 @@ + + + + + + + + + + + + + + diff --git a/files/mygui/openmw_settings_window.layout b/files/mygui/openmw_settings_window.layout index fd84be3e9e..4a993e1406 100644 --- a/files/mygui/openmw_settings_window.layout +++ b/files/mygui/openmw_settings_window.layout @@ -1,8 +1,8 @@  - + - + @@ -214,7 +214,7 @@ - + @@ -240,12 +240,22 @@ + + + + + + + + + + - + @@ -255,7 +265,7 @@ - + @@ -282,6 +292,27 @@ + + + + + + + + + + + + + + + + + + + + + @@ -460,7 +491,7 @@ - + diff --git a/files/mygui/openmw_spell_buying_window.layout b/files/mygui/openmw_spell_buying_window.layout index b24a476c4c..0350f1e278 100644 --- a/files/mygui/openmw_spell_buying_window.layout +++ b/files/mygui/openmw_spell_buying_window.layout @@ -1,10 +1,9 @@ - + - @@ -14,14 +13,12 @@ - - diff --git a/files/mygui/openmw_spell_window.layout b/files/mygui/openmw_spell_window.layout index ec655ace81..21bf74267a 100644 --- a/files/mygui/openmw_spell_window.layout +++ b/files/mygui/openmw_spell_window.layout @@ -10,10 +10,7 @@ - - - - + diff --git a/files/mygui/openmw_spellcreation_dialog.layout b/files/mygui/openmw_spellcreation_dialog.layout index 78d3f3de73..11197bf2a9 100644 --- a/files/mygui/openmw_spellcreation_dialog.layout +++ b/files/mygui/openmw_spellcreation_dialog.layout @@ -1,42 +1,53 @@ - + - + + + - - - + + + + + + - + + + + + - + + + @@ -44,6 +55,9 @@ + + + @@ -51,7 +65,7 @@ - + @@ -60,6 +74,9 @@ + + + diff --git a/files/mygui/openmw_travel_window.layout b/files/mygui/openmw_travel_window.layout index 30b470e08d..3603f7dcbf 100644 --- a/files/mygui/openmw_travel_window.layout +++ b/files/mygui/openmw_travel_window.layout @@ -1,10 +1,9 @@ - + - @@ -21,11 +20,10 @@ - - + diff --git a/files/mygui/openmw_windows.skin.xml b/files/mygui/openmw_windows.skin.xml index d01bd8ef20..2854401c89 100644 --- a/files/mygui/openmw_windows.skin.xml +++ b/files/mygui/openmw_windows.skin.xml @@ -395,17 +395,9 @@ - - - - - - - - - - - + + + @@ -538,12 +531,7 @@ - - - - - - + @@ -695,7 +684,8 @@ - + + @@ -814,12 +804,7 @@ - - - - - - +