diff --git a/README.md b/README.md index 0aaeb866d6..01a261237c 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ 1. ![Logo](https://raw.githubusercontent.com/mehah/otclient/main/src/otcicon.ico) [What is otclient?](#whatisotclient) 2. 🚀 [Features](#features) -6. Android [The Mobile Project](#themobileproject) +6. [The Mobile Project](#themobileproject) 3. 🔨 [Compiling](#compiling) 4. 🐳 [Docker](#docker) 5. 🩺 [Need help?](#need-help?) @@ -151,7 +151,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu -| Haskanoid Video | Peoplemon by Alex Stuart | Space Invaders | +| | | | |-------------------------------------------|---------------|-------------------------| | ThingCategory Attached Effect | Texture(Png) Attached Effect |
Particule
| @@ -215,7 +215,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu - Tile Widget [Wiki](https://github.com/mehah/otclient/wiki/Tutorial-Attached-Effects) -| Haskanoid Video | Peoplemon by Alex Stuart | Space Invaders | +| | | | |-------------------------------------------|---------------|-------------------------| |
Title Attached Effect
|
Title Widget
|
Title Particule
| @@ -248,12 +248,17 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu In short, if the ping gets high, the camera moves slower, trying to keep up with the server's response time. If the ping goes down soon after, the camera will move faster. Of course, this all depends on your character's speed. +-
+ Texture Atlas system +
+ +
##### 🙋 Community (Features) - Mobile Support [@tuliomagalhaes](https://github.com/tuliomagalhaes) & [@BenDol](https://github.com/BenDol) & [@SkullzOTS](https://github.com/SkullzOTS) -| Haskanoid Video | Peoplemon by Alex Stuart | Space Invaders | +| | | | |-------------------------------------------|---------------|-------------------------| | Interface | Density Pixel | Joystick (patrykq) | @@ -271,7 +276,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu -| Haskanoid Video | Peoplemon by Alex Stuart | Space Invaders | +| | | | |-------------------------------------------|---------------|-------------------------| | Example interface | Example in game | future discord-game-sdk | @@ -280,7 +285,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu - Access to widget children via widget.childId by [@Hugo0x1337](https://github.com/Hugo0x1337) - Shader System Fix (CTRL + Y) by [@FreshyPeshy](https://github.com/FreshyPeshy) -| Haskanoid Video | Peoplemon by Alex Stuart | Space Invaders | +| | | | |-------------------------------------------|---------------|-------------------------| | Creature | Map | Mount | @@ -339,7 +344,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu

- Haskanoid Video +

@@ -352,7 +357,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu

- Haskanoid Video +

@@ -408,7 +413,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu -| Haskanoid Video | Peoplemon by Alex Stuart | +| | | |-------------------------------------------|---------------| |
Interface
|
In-game
| - Imbuement tracker by [@Reyaleman](https://github.com/reyaleman) @@ -437,7 +442,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu - Shader with Framebuffer ([@SkullzOTS](https://github.com/SkullzOTS), [@Mryukiimaru](https://github.com/Mryukiimaru), [@JeanTheOne](https://github.com/JeanTheOne), [@KizaruHere](https://github.com/KizaruHere)) -| Haskanoid Video | Peoplemon by Alex Stuart | Space Invaders | +| | | | |-------------------------------------------|---------------|-------------------------| |
Creature.
|
Items
|
UICreature
| @@ -454,7 +459,7 @@ Beyond of it's flexibility with scripts, otclient comes with tons of other featu - keybinds - Cam system -## Android The Mobile Project +## The Mobile Project The Mobile Project This is a fork of edubart's otclient. The objective of this fork it's to develop a runnable otclient on mobiles devices. @@ -534,9 +539,9 @@ Have found a bug? Please create an issue in our [bug tracker](https://github.com | TFS 1.5
(8.0 / 8.60) | Downgrade nekiro /
MillhioreBT | [force-new-walking-formula: true](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L21)
[item-ticks-per-frame: 500](https://github.com/mehah/otclient/blob/cf7badda978de88cb3724615688e3d9da2ff4207/data/setup.otml#L32) | ✅ | | TFS 1.4.2
(10.98) | Release Otland | | ✅ | | TFS 1.6
(13.10) | Main repo
otland (2024) | [See wiki](https://github.com/mehah/otclient/wiki/Tutorial-to-Use-OTC-in-TFS-main) | ✅ | -| Canary
(13.21 / 13.32 / 13.40) | OpenTibiaBr | [Assets , Enable HTTP login and port 80](https://docs.opentibiabr.com/opentibiabr/projects/otclient-redemption#how-to-connect-on-canary-with-otclient-redemption) | ✅ | -| Canary
(14.05) | OpenTibiaBr | [Assets , Enable HTTP login and port 80](https://docs.opentibiabr.com/opentibiabr/projects/otclient-redemption#how-to-connect-on-canary-with-otclient-redemption) | ❌ | - +| Canary
(13.21 / 13.32 / 13.40) | OpenTibiaBr | [See Wiki](https://docs.opentibiabr.com/opentibiabr/projects/otclient-redemption/about#how-to-connect-on-canary-with-otclient-redemption) | ✅ | +| Canary
(14.00 ~ 14.12) | OpenTibiaBr | [See Wiki](https://docs.opentibiabr.com/opentibiabr/projects/otclient-redemption/about#how-to-connect-on-canary-with-otclient-redemption) | ✅ | +| Canary
(15.00 ~ 15.10) | OpenTibiaBr | [See Wiki](https://docs.opentibiabr.com/opentibiabr/projects/otclient-redemption/about#how-to-connect-on-canary-with-otclient-redemption) | ❌ | diff --git a/data/images/background.png b/data/images/background.png index f64220061e..ae52adc868 100644 Binary files a/data/images/background.png and b/data/images/background.png differ diff --git a/data/images/game/cyclopedia/magical_off.png b/data/images/game/cyclopedia/magical_off.png new file mode 100644 index 0000000000..7afd7fb218 Binary files /dev/null and b/data/images/game/cyclopedia/magical_off.png differ diff --git a/data/images/game/cyclopedia/magical_on.png b/data/images/game/cyclopedia/magical_on.png new file mode 100644 index 0000000000..d44b05b482 Binary files /dev/null and b/data/images/game/cyclopedia/magical_on.png differ diff --git a/data/images/ui/item.png b/data/images/ui/item.png index 6793eb1904..6b605a3ba6 100644 Binary files a/data/images/ui/item.png and b/data/images/ui/item.png differ diff --git a/data/setup.otml b/data/setup.otml index e64bb76f16..896d40eecb 100644 --- a/data/setup.otml +++ b/data/setup.otml @@ -1,6 +1,6 @@ game sprite-size: 32 - last-supported-version: 1340 + last-supported-version: 1412 draw-typing: false typing-icon: /images/game/console/typing diff --git a/modules/game_blessing/blessing.lua b/modules/game_blessing/blessing.lua index caedf9d2d2..2cf8f3036f 100644 --- a/modules/game_blessing/blessing.lua +++ b/modules/game_blessing/blessing.lua @@ -92,6 +92,8 @@ function onUpdateBlessDialog(data) label.text:setText(entry.playerBlessCount .. " (" .. entry.store .. ")") if totalCount >= 1 then label.enabled:setImageSource("images/" .. i .. "_on") + else + label.enabled:setImageSource("images/" .. i) end end diff --git a/modules/game_cyclopedia/cyclopedia_widgets.otui b/modules/game_cyclopedia/cyclopedia_widgets.otui index eb43f81459..7d6a6a66f1 100644 --- a/modules/game_cyclopedia/cyclopedia_widgets.otui +++ b/modules/game_cyclopedia/cyclopedia_widgets.otui @@ -710,6 +710,10 @@ CharmItem < UICheckBox image-source: /images/ui/item image-border: 10 phantom: true + @onSetup: | + if g_game.getClientVersion() >= 1410 then + self:setVisible(false) + end Label id: Value anchors.centerIn: parent @@ -736,8 +740,14 @@ CharmItem < UICheckBox margin-bottom: 7 image-source: /images/ui/panel_flat image-border: 10 - size: 37 37 phantom: true + @onSetup: | + if g_game.getClientVersion() >= 1410 then + self:setSize("46 46") + self:setMarginBottom(-5) + else + self:setSize("37 37") + end UIWidget size: 32 32 id: image @@ -749,6 +759,22 @@ CharmItem < UICheckBox anchors.centerIn: parent image-source: /images/ui/ditherpattern phantom: true + @onSetup: | + if g_game.getClientVersion() >= 1410 then + self:setSize("43 43") + else + self:setSize("35 35") + end + UIWidget + id: border + anchors.centerIn: parent + phantom: true + @onSetup: | + if g_game.getClientVersion() >= 1410 then + self:setVisible(true) + else + self:setVisible(false) + end MarkListItem < ButtonBox icon-size: 11 11 icon-offset: 3 3 @@ -1422,4 +1448,4 @@ BlessCreate < UIWidget margin-left: 8 anchors.left: prev.right anchors.verticalCenter: parent.verticalCenter - focusable: false \ No newline at end of file + focusable: false diff --git a/modules/game_cyclopedia/game_cyclopedia.lua b/modules/game_cyclopedia/game_cyclopedia.lua index 06644be4bb..309165f861 100644 --- a/modules/game_cyclopedia/game_cyclopedia.lua +++ b/modules/game_cyclopedia/game_cyclopedia.lua @@ -21,7 +21,7 @@ local ButtonBestiary = nil local tabStack = {} local previousType = nil local windowTypes = {} - +local magicalArchives = nil function toggle(defaultWindow) if not controllerCyclopedia.ui then return @@ -58,6 +58,7 @@ function controllerCyclopedia:onGameStart() character = buttonSelection:recursiveGetChildById('character') bosstiary = buttonSelection:recursiveGetChildById('bosstiary') bossSlot = buttonSelection:recursiveGetChildById('bossSlot') + magicalArchives = buttonSelection:recursiveGetChildById('magicalArchives') windowTypes = { items = { obj = items, func = showItems }, @@ -67,7 +68,8 @@ function controllerCyclopedia:onGameStart() houses = { obj = houses, func = showHouse }, character = { obj = character, func = showCharacter }, bosstiary = { obj = bosstiary, func = showBosstiary }, - bossSlot = { obj = bossSlot, func = showBossSlot } + bossSlot = { obj = bossSlot, func = showBossSlot }, + magicalArchives = { obj = magicalArchives, func = showMagicalArchives }, } g_ui.importStyle("cyclopedia_widgets") @@ -93,6 +95,12 @@ function controllerCyclopedia:onGameStart() onUpdateCyclopediaCharacterItemSummary = Cyclopedia.loadCharacterItems, onParseCyclopediaCharacterAppearances = Cyclopedia.loadCharacterAppearances, onParseCyclopediaStoreSummary = Cyclopedia.onParseCyclopediaStoreSummary, +-- character 14.10 + onCyclopediaCharacterOffenceStats = Cyclopedia.onCyclopediaCharacterOffenceStats, + onCyclopediaCharacterDefenceStats = Cyclopedia.onCyclopediaCharacterDefenceStats, + onCyclopediaCharacterMiscStats = Cyclopedia.onCyclopediaCharacterMiscStats, + + -- charms onUpdateBestiaryCharmsData = Cyclopedia.loadCharms, -- items @@ -193,7 +201,9 @@ function controllerCyclopedia:onGameStart() }}) end - + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase.Icon:setImageSource("/game_cyclopedia/images/monster-icon-bonuspoints") + end end @@ -227,6 +237,20 @@ function controllerCyclopedia:onTerminate() trackerMiniWindowBosstiary:destroy() trackerMiniWindowBosstiary = nil end + + if CyclopediaButton then + CyclopediaButton:destroy() + CyclopediaButton = nil + end + if ButtonBossSlot then + ButtonBossSlot:destroy() + ButtonBossSlot = nil + end + if ButtonBestiary then + ButtonBestiary:destroy() + ButtonBestiary = nil + end + onTerminateCharm() end function hide() @@ -257,7 +281,7 @@ function show(defaultWindow) controllerCyclopedia.ui:raise() controllerCyclopedia.ui:focus() SelectWindow(defaultWindow, false) - controllerCyclopedia.ui.GoldBase.Value:setText(Cyclopedia.formatGold(g_game.getLocalPlayer():getResourceBalance(1))) + controllerCyclopedia.ui.GoldBase.Value:setText(Cyclopedia.formatGold(g_game.getLocalPlayer():getResourceBalance())) end function toggleBack() diff --git a/modules/game_cyclopedia/game_cyclopedia.otui b/modules/game_cyclopedia/game_cyclopedia.otui index f9e1ba13d0..825baf9cad 100644 --- a/modules/game_cyclopedia/game_cyclopedia.otui +++ b/modules/game_cyclopedia/game_cyclopedia.otui @@ -197,6 +197,28 @@ MainWindow image-clip: 0 34 34 34 image-source: /images/game/cyclopedia/bossSlot_off + UIButton + id: magicalArchives + anchors.top: prev.top + anchors.left: prev.right + margin-left: 1 + @onClick: SelectWindow("magicalArchives") + + $on: + size: 150 34 + image-clip: 0 0 150 34 + image-source: /images/game/cyclopedia/magical_on + + $!on: + size: 34 34 + image-clip: 0 0 34 34 + image-source: /images/game/cyclopedia/magical_off + + $pressed: + size: 34 34 + image-clip: 0 34 34 34 + image-source: /images/game/cyclopedia/magical_off + UIWidget id: contentContainer anchors.top: prev.bottom @@ -277,11 +299,36 @@ MainWindow !text: tr('0') text-auto-resize: true color: #BDBDBD + + UIWidget + id: CharmsBase1410 + visible: false + anchors.bottom: parent.bottom + anchors.left: prev.right + margin-left: 5 + image-source: /images/ui/item + image-border: 10 + size: 100 20 + !tooltip: tr("Minor Charm echoes\nEarned by unlocking or upgrading major charm and promotion your character") + UIWidget + id: Icon + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + margin-right: 4 + image-source: /game_cyclopedia/images/minor-charm-echoes + Label + id: Value + anchors.right: Icon.left + anchors.verticalCenter: parent.verticalCenter + margin-right: 4 + !text: tr('0 / 0') + text-auto-resize: true + color: #BDBDBD Button id: BestiaryTrackerButton size: 90 20 anchors.bottom: parent.bottom - anchors.left: CharmsBase.right + anchors.left: prev.right margin-left: 5 !text: tr('Bestiary Tracker') font: small-9px diff --git a/modules/game_cyclopedia/images/character_icons/icon-character-generalstats-defence.png b/modules/game_cyclopedia/images/character_icons/icon-character-generalstats-defence.png new file mode 100644 index 0000000000..2630e47952 Binary files /dev/null and b/modules/game_cyclopedia/images/character_icons/icon-character-generalstats-defence.png differ diff --git a/modules/game_cyclopedia/images/character_icons/icon-character-generalstats-misc.png b/modules/game_cyclopedia/images/character_icons/icon-character-generalstats-misc.png new file mode 100644 index 0000000000..04a0415604 Binary files /dev/null and b/modules/game_cyclopedia/images/character_icons/icon-character-generalstats-misc.png differ diff --git a/modules/game_cyclopedia/images/charms/0.png b/modules/game_cyclopedia/images/charms/0.png deleted file mode 100644 index e24c2c2b53..0000000000 Binary files a/modules/game_cyclopedia/images/charms/0.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/1.png b/modules/game_cyclopedia/images/charms/1.png deleted file mode 100644 index 942be1e978..0000000000 Binary files a/modules/game_cyclopedia/images/charms/1.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/10.png b/modules/game_cyclopedia/images/charms/10.png deleted file mode 100644 index 7f0e3f4896..0000000000 Binary files a/modules/game_cyclopedia/images/charms/10.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/11.png b/modules/game_cyclopedia/images/charms/11.png deleted file mode 100644 index 1355863cc9..0000000000 Binary files a/modules/game_cyclopedia/images/charms/11.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/12.png b/modules/game_cyclopedia/images/charms/12.png deleted file mode 100644 index 66b2dc50df..0000000000 Binary files a/modules/game_cyclopedia/images/charms/12.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/13.png b/modules/game_cyclopedia/images/charms/13.png deleted file mode 100644 index aecba95ad1..0000000000 Binary files a/modules/game_cyclopedia/images/charms/13.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/14.png b/modules/game_cyclopedia/images/charms/14.png deleted file mode 100644 index 2ab52539e0..0000000000 Binary files a/modules/game_cyclopedia/images/charms/14.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/15.png b/modules/game_cyclopedia/images/charms/15.png deleted file mode 100644 index b08a03dd08..0000000000 Binary files a/modules/game_cyclopedia/images/charms/15.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/16.png b/modules/game_cyclopedia/images/charms/16.png deleted file mode 100644 index a6ccdd6741..0000000000 Binary files a/modules/game_cyclopedia/images/charms/16.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/17.png b/modules/game_cyclopedia/images/charms/17.png deleted file mode 100644 index f1dc200331..0000000000 Binary files a/modules/game_cyclopedia/images/charms/17.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/18.png b/modules/game_cyclopedia/images/charms/18.png deleted file mode 100644 index 6f00013b2e..0000000000 Binary files a/modules/game_cyclopedia/images/charms/18.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/19.png b/modules/game_cyclopedia/images/charms/19.png deleted file mode 100644 index 6f00013b2e..0000000000 Binary files a/modules/game_cyclopedia/images/charms/19.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/2.png b/modules/game_cyclopedia/images/charms/2.png deleted file mode 100644 index 511c801f70..0000000000 Binary files a/modules/game_cyclopedia/images/charms/2.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/3.png b/modules/game_cyclopedia/images/charms/3.png deleted file mode 100644 index 2d22ff3ee2..0000000000 Binary files a/modules/game_cyclopedia/images/charms/3.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/4.png b/modules/game_cyclopedia/images/charms/4.png deleted file mode 100644 index e188f84266..0000000000 Binary files a/modules/game_cyclopedia/images/charms/4.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/5.png b/modules/game_cyclopedia/images/charms/5.png deleted file mode 100644 index f054aac439..0000000000 Binary files a/modules/game_cyclopedia/images/charms/5.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/6.png b/modules/game_cyclopedia/images/charms/6.png deleted file mode 100644 index 2b5d649be2..0000000000 Binary files a/modules/game_cyclopedia/images/charms/6.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/7.png b/modules/game_cyclopedia/images/charms/7.png deleted file mode 100644 index 3be5f38fde..0000000000 Binary files a/modules/game_cyclopedia/images/charms/7.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/8.png b/modules/game_cyclopedia/images/charms/8.png deleted file mode 100644 index d5f32bcece..0000000000 Binary files a/modules/game_cyclopedia/images/charms/8.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/9.png b/modules/game_cyclopedia/images/charms/9.png deleted file mode 100644 index 9d57bcb226..0000000000 Binary files a/modules/game_cyclopedia/images/charms/9.png and /dev/null differ diff --git a/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade1.png b/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade1.png new file mode 100644 index 0000000000..0febc84a02 Binary files /dev/null and b/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade1.png differ diff --git a/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade2.png b/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade2.png new file mode 100644 index 0000000000..07347c3b24 Binary files /dev/null and b/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade2.png differ diff --git a/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade3.png b/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade3.png new file mode 100644 index 0000000000..5ede595022 Binary files /dev/null and b/modules/game_cyclopedia/images/charms/border/backdrop_charmgrade3.png differ diff --git a/modules/game_cyclopedia/images/charms/border/border_charmgrades.png b/modules/game_cyclopedia/images/charms/border/border_charmgrades.png new file mode 100644 index 0000000000..a7f48cd07f Binary files /dev/null and b/modules/game_cyclopedia/images/charms/border/border_charmgrades.png differ diff --git a/modules/game_cyclopedia/images/charms/icon-charms-major.png b/modules/game_cyclopedia/images/charms/icon-charms-major.png new file mode 100644 index 0000000000..21570bd714 Binary files /dev/null and b/modules/game_cyclopedia/images/charms/icon-charms-major.png differ diff --git a/modules/game_cyclopedia/images/charms/icon-charms-minor.png b/modules/game_cyclopedia/images/charms/icon-charms-minor.png new file mode 100644 index 0000000000..28af33a13d Binary files /dev/null and b/modules/game_cyclopedia/images/charms/icon-charms-minor.png differ diff --git a/modules/game_cyclopedia/images/charms/monster-bonus-effects.png b/modules/game_cyclopedia/images/charms/monster-bonus-effects.png new file mode 100644 index 0000000000..6fd8b49dd6 Binary files /dev/null and b/modules/game_cyclopedia/images/charms/monster-bonus-effects.png differ diff --git a/modules/game_cyclopedia/images/minor-charm-echoes.png b/modules/game_cyclopedia/images/minor-charm-echoes.png new file mode 100644 index 0000000000..a3c9e1918e Binary files /dev/null and b/modules/game_cyclopedia/images/minor-charm-echoes.png differ diff --git a/modules/game_cyclopedia/images/monster-icon-bonuspoints.png b/modules/game_cyclopedia/images/monster-icon-bonuspoints.png new file mode 100644 index 0000000000..1b1997f372 Binary files /dev/null and b/modules/game_cyclopedia/images/monster-icon-bonuspoints.png differ diff --git a/modules/game_cyclopedia/tab/bestiary/bestiary.lua b/modules/game_cyclopedia/tab/bestiary/bestiary.lua index 971181ec26..812a90fbbc 100644 --- a/modules/game_cyclopedia/tab/bestiary/bestiary.lua +++ b/modules/game_cyclopedia/tab/bestiary/bestiary.lua @@ -34,6 +34,9 @@ function showBestiary() controllerCyclopedia.ui.CharmsBase:setVisible(true) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(true) + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:hide() + end g_game.requestBestiary() end diff --git a/modules/game_cyclopedia/tab/boss_slots/boss_slots.lua b/modules/game_cyclopedia/tab/boss_slots/boss_slots.lua index 11976dcb2b..459ddb0056 100644 --- a/modules/game_cyclopedia/tab/boss_slots/boss_slots.lua +++ b/modules/game_cyclopedia/tab/boss_slots/boss_slots.lua @@ -8,6 +8,9 @@ function showBossSlot() controllerCyclopedia.ui.CharmsBase:setVisible(false) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:hide() + end Cyclopedia.BossSlots.UnlockBosses = {} end diff --git a/modules/game_cyclopedia/tab/bosstiary/bosstiary.lua b/modules/game_cyclopedia/tab/bosstiary/bosstiary.lua index 0de6da0826..7700f5cc6b 100644 --- a/modules/game_cyclopedia/tab/bosstiary/bosstiary.lua +++ b/modules/game_cyclopedia/tab/bosstiary/bosstiary.lua @@ -23,6 +23,9 @@ function showBosstiary() controllerCyclopedia.ui.CharmsBase:setVisible(false) controllerCyclopedia.ui.GoldBase:setVisible(false) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:setVisible(false) + end end Cyclopedia.Bosstiary = {} diff --git a/modules/game_cyclopedia/tab/character/character.lua b/modules/game_cyclopedia/tab/character/character.lua index 797f50d781..d0d3effcd6 100644 --- a/modules/game_cyclopedia/tab/character/character.lua +++ b/modules/game_cyclopedia/tab/character/character.lua @@ -100,6 +100,9 @@ function showCharacter() controllerCyclopedia.ui.CharmsBase:setVisible(true) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:setVisible(true) + end end Cyclopedia.Character = {} @@ -966,18 +969,41 @@ function Cyclopedia.configureCharacterCategories() { text = "General Stats", icon = "/game_cyclopedia/images/character_icons/icon_generalstats", - subCategories = { - { - text = "Character Stats", - icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-overview", - open = "CharacterStats" - }, - { - text = "Combat Stats", - icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-combatstats", - open = "CombatStats" + subCategories = function() + local categories = { + { + text = "Character Stats", + icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-overview", + open = "CharacterStats" + } } - } + + if g_game.getClientVersion() < 1410 then + table.insert(categories, { + text = "Combat Stats", + icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-combatstats", + open = "CombatStats" + }) + else + table.insert(categories, { + text = "Offence Stats", + icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-combatstats", + open = "OffenceStats" + }) + table.insert(categories, { + text = "Deffence Stats", + icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-defence", + open = "DeffenceStats" + }) + table.insert(categories, { + text = "Misc. Stats", + icon = "/game_cyclopedia/images/character_icons/icon-character-generalstats-misc", + open = "MiscStats" + }) + end + + return categories + end }, { text = "Battle Results", @@ -1033,11 +1059,16 @@ function Cyclopedia.configureCharacterCategories() end if button.subCategories ~= nil then - widget.subCategories = button.subCategories - widget.subCategoriesSize = #button.subCategories + local subCats = button.subCategories + if type(subCats) == "function" then + subCats = subCats() + end + + widget.subCategories = subCats + widget.subCategoriesSize = #subCats widget.Button.Arrow:setVisible(true) - for subId, subButton in ipairs(button.subCategories) do + for subId, subButton in ipairs(subCats) do local subWidget = g_ui.createWidget("CharacterCategoryItem", widget) subWidget:setId(subId) subWidget.Button.Icon:setIcon(subButton.icon) @@ -1060,6 +1091,12 @@ function Cyclopedia.configureCharacterCategories() g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Badges) elseif subWidget.open == "CombatStats" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.CombatStats) + elseif subWidget.open == "OffenceStats" then + g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Offencestats) + elseif subWidget.open == "DeffenceStats" then + g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Defencestats) + elseif subWidget.open == "MiscStats" then + g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.Miscstats) elseif subWidget.open == "RecentDeaths" then g_game.requestCharacterInfo(0, CyclopediaCharacterInfoTypes.RecentDeaths, 23, 1) elseif subWidget.open == "RecentKills" then @@ -1084,11 +1121,11 @@ function Cyclopedia.configureCharacterCategories() if id == 1 then widget:addAnchor(AnchorTop, "parent", AnchorTop) widget:addAnchor(AnchorHorizontalCenter, "parent", AnchorHorizontalCenter) - widget:setMarginTop(10) + widget:setMarginTop(5) else widget:addAnchor(AnchorTop, "prev", AnchorBottom) widget:addAnchor(AnchorHorizontalCenter, "parent", AnchorHorizontalCenter) - widget:setMarginTop(10) + widget:setMarginTop(5) end function widget.Button.onClick(this) @@ -1262,3 +1299,346 @@ function Cyclopedia.onParseCyclopediaStoreSummary(xpBoostTime, dailyRewardXpBoos itemWidget:fill('parent') end end + +local function getWeaponSkillName(skillType) + local skillNames = { + [0] = "Fist Fighting", + [1] = "Club Fighting", + [2] = "Sword Fighting", + [3] = "Axe Fighting", + [4] = "Distance Fighting", + [5] = "Shielding", + [6] = "Fishing", + [7] = "Magic Level", + [8] = "Critical Hits", + [9] = "Life Leech", + [10] = "Mana Leech" + } + + return skillNames[skillType] or "Fighting Skill" + end + function Cyclopedia.onCyclopediaCharacterOffenceStats(data) + UI.OffenceStats.rightPanel:destroyChildren() + UI.OffenceStats.leftPanel:destroyChildren() + + local attackValue = data.weaponAttack + data.weaponFlatModifier + data.weaponDamage + data.weaponSkillLevel + local stats = { + {name = "Flat Damage and healing", value = data.flatDamage or 0, icon = false, percent = false}, + {name = "Attack Value", value = attackValue, icon = true, weaponElement = data.weaponElement}, + {name = "From Base Attack", value = data.weaponAttack or 0, align = "center", icon = false}, + {name = "From Equipment", value = data.weaponFlatModifier or 0, align = "center", icon = false}, + + {name = getWeaponSkillName(data.weaponSkillType), value = data.weaponSkillLevel or 0, align = "center", icon = false}, + {name = "From Combat Tactics", value = data.weaponDamage or 0, align = "center", icon = false}, + + {name = "Life Leech", value = data.lifeLeech or 0, icon = false, percent = true}, + {name = "From Base", value = data.lifeLeechBase or 0, align = "center", percent = true, icon = false}, + {name = "From Equipment", value = data.lifeLeechImbuement or 0, align = "center", percent = true, icon = false}, + {name = "From Wheel", value = data.lifeLeechWheel or 0, align = "center", percent = true, icon = false}, + + {name = "Mana Leech", value = data.manaLeech or 0, icon = false, percent = true}, + {name = "From Base", value = data.manaLeechBase or 0, align = "center", percent = true, icon = false}, + {name = "From Equipment", value = data.manaLeechImbuement or 0, align = "center", percent = true, icon = false}, + {name = "From Wheel", value = data.manaLeechWheel or 0, align = "center", percent = true, icon = false}, + + {name = "Onslaught", value = data.onslaught or 0, icon = false, percent = true}, + {name = "From Base", value = data.onslaughtBase or 0, align = "center", percent = true, icon = false}, + {name = "From Amplification", value = data.onslaughtBonus or 0, align = "center", percent = true, icon = false}, + + {name = "Critical Hit", parent = "right", value = "", icon = false}, + {name = " Chance", parent = "right", value = data.critChance or 0, percent = true, icon = false}, + {name = " Extra Damage", parent = "right", value = data.critDamage or 0, percent = true, icon = false}, + {name = "From Base", parent = "right", value = data.critDamageBase or 0, align = "center", percent = true, icon = false}, + {name = "From Equipment", parent = "right", value = data.critDamageImbuement or 0, align = "center", percent = true, icon = false}, + {name = "From Wheel", parent = "right", value = data.critDamageWheel or 0, align = "center", percent = true, icon = false} + } + + if data.perfectShotDamage then + for i = 1, 5 do + if data.perfectShotDamage[i] and data.perfectShotDamage[i] > 0 then + table.insert(stats, { + name = "Perfect Shot Damage Bonus", + parent = "right", + value = "", + icon = false + }) + table.insert(stats, { + name = " +" .. data.perfectShotDamage[i] .. " from range " .. i, + parent = "right", + value = "", + align = "center", + icon = false + }) + break + end + end + end + + local function renderStat(stat) + local parent = stat.parent == "right" and UI.OffenceStats.rightPanel or UI.OffenceStats.leftPanel + + if stat.align == "center" then + local widget = g_ui.createWidget("Label", parent) + local valueText = stat.value + if stat.percent then + local percentValue = math.floor(stat.value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + valueText = sign .. percentValue .. "%" + end + widget:setText(" " .. valueText .. " " .. stat.name) + widget:setMarginLeft(80) + return widget + else + local widget = g_ui.createWidget("CharacterSkillBase", parent) + local nameLabel = g_ui.createWidget("SkillNameLabel", widget) + nameLabel:setText(stat.name .. ":") + local valueLabel = g_ui.createWidget("SkillValueLabel", widget) + if stat.percent then + local percentValue = math.floor(stat.value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + valueLabel:setText(sign .. percentValue .. "%") + else + valueLabel:setText(tostring(stat.value)) + end + if stat.icon then + valueLabel:setMarginRight(12) + local icon = g_ui.createWidget("SkillCharacterIcon", widget) + icon:setMarginTop(2) + icon:addAnchor(AnchorRight, "parent", AnchorRight) + local element = clientCombat[stat.weaponElement] + if element then + icon:setImageSource(element.path) + icon:setImageSize({ + width = 9, + height = 9 + }) + end + end + + return widget + end + end + + for _, stat in ipairs(stats) do + if stat.align ~= "center" and stat.value == 0 and stat.value ~= "" then + -- Skip + else + renderStat(stat) + end + end + end + function Cyclopedia.onCyclopediaCharacterDefenceStats(data) + UI.DeffenceStats.rightPanel:destroyChildren() + UI.DeffenceStats.leftPanel:destroyChildren() + + local stats = { + {name = "Defence Value", value = data.defense or 0, icon = false, percent = false}, + {name = "From Equipment", value = data.defenseEquipment or 0, align = "center", icon = false}, + {name = "From Wheel", value = data.defenseWheel or 0, align = "center", icon = false}, + {name = getWeaponSkillName(data.defenseSkillType), value = data.shieldingSkill or 0, align = "center", icon = false}, + + {name = "Armor Value", value = data.armor or 0, icon = false, percent = false}, + + {name = "Mitigation", value = data.mitigation or 0, icon = false, percent = true}, + {name = "From Shielding", value = data.mitigationShield or 0, align = "center", percent = true, icon = false}, + {name = "From Combat Tactics", value = data.mitigationCombatTactics or 0, align = "center", percent = true, icon = false}, + {name = "From Base", value = data.mitigationBase or 0, align = "center", percent = true, icon = false}, + {name = "From Equipment", value = data.mitigationEquipment or 0, align = "center", percent = true, icon = false}, + {name = "From Wheel", value = data.mitigationWheel or 0, align = "center", percent = true, icon = false}, + + {name = "Dodge", value = data.dodgeTotal or 0, icon = false, percent = true}, + {name = "From Base", value = data.dodgeBase or 0, align = "center", percent = true, icon = false}, + {name = "From Amplification", value = data.dodgeBonus or 0, align = "center", percent = true, icon = false}, + {name = "From Wheel", value = data.dodgeWheel or 0, align = "center", percent = true, icon = false}, + + {name = "Magic Shield Capacity", value = data.magicShieldCapacity or 0, icon = false, percent = false}, + {name = "Flat", value = data.magicShieldCapacityFlat or 0, align = "center", icon = false}, + {name = "Percent", value = data.magicShieldCapacityPercent or 0, align = "center", percent = true, icon = false}, + + {name = "Reflect Physical", value = data.reflectPhysical or 0, icon = false, percent = false}, + + {name = "Resistances", parent = "right", value = "", icon = false} + } + + local resistanceMap = {} + if data.resistances then + for _, resistance in ipairs(data.resistances) do + resistanceMap[resistance.element] = resistance.value + end + end + + for elementId, elementInfo in pairs(Cyclopedia.clientCombat) do + local value = resistanceMap[elementId] or 0 + local percentValue = value *100 + local color = "#FFFFFF" + + if percentValue > 0 then + color = "#44AD25" + elseif percentValue < 0 then + color = "#FF9900" + end + + local sign = percentValue >= 0 and "+" or "" + table.insert(stats, { + name = " " .. elementInfo.id, + parent = "right", + value = sign .. string.format("%.2f", tonumber(percentValue)) .. "%", + percent = false, + element = elementId, + icon = true, + color = color + }) + end + + local function renderStat(stat) + local parent = stat.parent == "right" and UI.DeffenceStats.rightPanel or UI.DeffenceStats.leftPanel + + if stat.align == "center" then + local widget = g_ui.createWidget("Label", parent) + local valueText = stat.value + if stat.percent then + local percentValue = math.floor(stat.value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + valueText = sign .. percentValue .. "%" + end + widget:setText(" " .. valueText .. " " .. stat.name) + widget:setMarginLeft(80) + return widget + else + local widget = g_ui.createWidget("CharacterSkillBase", parent) + local nameLabel = g_ui.createWidget("SkillNameLabel", widget) + nameLabel:setText(stat.name .. ":") + local valueLabel = g_ui.createWidget("SkillValueLabel", widget) + if stat.percent then + local percentValue = math.floor(stat.value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + valueLabel:setText(sign .. percentValue .. "%") + else + valueLabel:setText(tostring(stat.value)) + end + + if stat.color then + valueLabel:setColor(stat.color) + end + + if stat.icon then + valueLabel:setMarginRight(12) + local icon = g_ui.createWidget("SkillCharacterIcon", widget) + icon:setMarginTop(2) + icon:addAnchor(AnchorRight, "parent", AnchorRight) + local element = Cyclopedia.clientCombat[stat.element] + if element then + icon:setImageSource(element.path) + icon:setImageSize({ + width = 9, + height = 9 + }) + end + end + + return widget + end + end + + for _, stat in ipairs(stats) do + if stat.align ~= "center" and stat.value == 0 and stat.value ~= "" then + -- Skip + else + renderStat(stat) + end + end + end + + function Cyclopedia.onCyclopediaCharacterMiscStats(data) + UI.MiscStats.leftPanel:destroyChildren() + UI.MiscStats.rightPanel:destroyChildren() + + local stats = { + {name = "Momentum", value = data.momentumTotal or 0, icon = false, percent = true}, + {name = "From Equipment", value = data.momentumBase or 0, align = "center", percent = true, icon = false}, + {name = "From Amplification", value = data.momentumBonus or 0, align = "center", percent = true, icon = false}, + {name = "From Wheel", value = data.momentumWheel or 0, align = "center", percent = true, icon = false}, + + {name = "Transcendence", value = data.dodgeTotal or 0, icon = false, percent = true}, + {name = "From Base", value = data.dodgeBase or 0, align = "center", percent = true, icon = false}, + {name = "From Amplification", value = data.dodgeBonus or 0, align = "center", percent = true, icon = false}, + {name = "From Event Bonus", value = data.dodgeWheel or 0, align = "center", percent = true, icon = false}, + + {name = "Damage Reflection", value = data.damageReflectionTotal or 0, icon = false, percent = true}, + {name = "From Base", value = data.damageReflectionBase or 0, align = "center", percent = true, icon = false}, + {name = "From Bonus", value = data.damageReflectionBonus or 0, align = "center", percent = true, icon = false}, + + {name = "Blessings", value = (data.haveBlesses or 0) .. "/" .. (data.totalBlesses or 0), icon = false, percent = false}, + + } + + if data.concoctions and #data.concoctions > 0 then + for _, concoction in ipairs(data.concoctions) do + table.insert(stats, { + name = " " .. concoction.name, + parent = "right", + value = concoction.value, + percent = true, + icon = false + }) + end + end + + local function renderStat(stat) + local parent = stat.parent == "right" and UI.MiscStats.rightPanel or UI.MiscStats.leftPanel + + if stat.align == "center" then + local widget = g_ui.createWidget("Label", parent) + local valueText = stat.value + if stat.percent then + local percentValue = math.floor(stat.value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + valueText = sign .. percentValue .. "%" + end + widget:setText(" " .. valueText .. " " .. stat.name) + widget:setMarginLeft(60) + return widget + else + local widget = g_ui.createWidget("CharacterSkillBase", parent) + local nameLabel = g_ui.createWidget("SkillNameLabel", widget) + nameLabel:setText(stat.name .. ":") + local valueLabel = g_ui.createWidget("SkillValueLabel", widget) + if stat.percent then + local percentValue = math.floor(stat.value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + + + valueLabel:setText(sign .. percentValue .. "%") + else + valueLabel:setText(tostring(stat.value)) + end + + if stat.icon then + valueLabel:setMarginRight(12) + local icon = g_ui.createWidget("SkillCharacterIcon", widget) + icon:setMarginTop(2) + icon:addAnchor(AnchorRight, "parent", AnchorRight) + if stat.element then + local element = Cyclopedia.clientCombat[stat.element] + if element then + icon:setImageSource(element.path) + icon:setImageSize({ + width = 9, + height = 9 + }) + end + end + end + + return widget + end + end + + for _, stat in ipairs(stats) do + if stat.align ~= "center" and stat.value == 0 and stat.value ~= "" then + -- Skip + else + renderStat(stat) + end + end + end \ No newline at end of file diff --git a/modules/game_cyclopedia/tab/character/character.otui b/modules/game_cyclopedia/tab/character/character.otui index 549bdad63e..ae20c409dd 100644 --- a/modules/game_cyclopedia/tab/character/character.otui +++ b/modules/game_cyclopedia/tab/character/character.otui @@ -1791,4 +1791,126 @@ UIWidget margin: 1 step: 80 - pixel-scroll: true \ No newline at end of file + pixel-scroll: true + + BorderBox + id: OffenceStats + anchors.top: CharacterBase.top + anchors.left: CharacterBase.right + anchors.bottom: OptionsBase.bottom + anchors.right: parent.right + margin-left: 10 + visible: false + + VerticalSeparator + id: separator + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-top: 5 + margin-bottom: 5 + + + Panel + id: leftPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: separator.right + margin-top: 5 + margin-left: 5 + height: 350 + layout: + type: verticalBox + spacing: 2 + + Panel + id: rightPanel + anchors.top: prev.top + anchors.left: separator.left + anchors.right: parent.right + margin-top: 1 + height: 350 + layout: + type: verticalBox + spacing: 2 + + + BorderBox + id: DeffenceStats + anchors.top: CharacterBase.top + anchors.left: CharacterBase.right + anchors.bottom: OptionsBase.bottom + anchors.right: parent.right + margin-left: 10 + visible: false + VerticalSeparator + id: separator + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-top: 5 + margin-bottom: 5 + + + Panel + id: leftPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: separator.right + margin-top: 5 + margin-left: 5 + height: 350 + layout: + type: verticalBox + spacing: 2 + + Panel + id: rightPanel + anchors.top: prev.top + anchors.left: separator.left + anchors.right: parent.right + margin-top: 1 + height: 350 + layout: + type: verticalBox + spacing: 2 + + BorderBox + id: MiscStats + anchors.top: CharacterBase.top + anchors.left: CharacterBase.right + anchors.bottom: OptionsBase.bottom + anchors.right: parent.right + margin-left: 10 + visible: false + VerticalSeparator + id: separator + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: parent.top + anchors.bottom: parent.bottom + margin-top: 5 + margin-bottom: 5 + + + Panel + id: leftPanel + anchors.top: parent.top + anchors.left: parent.left + anchors.right: separator.right + margin-top: 5 + margin-left: 5 + height: 350 + layout: + type: verticalBox + spacing: 2 + + Panel + id: rightPanel + anchors.top: prev.top + anchors.left: separator.left + anchors.right: parent.right + margin-top: 1 + height: 350 + layout: + type: verticalBox + spacing: 2 diff --git a/modules/game_cyclopedia/tab/charms/charms.lua b/modules/game_cyclopedia/tab/charms/charms.lua index 9e34e1c671..681fef0b43 100644 --- a/modules/game_cyclopedia/tab/charms/charms.lua +++ b/modules/game_cyclopedia/tab/charms/charms.lua @@ -1,203 +1,614 @@ local UI = nil +local TypeCharmRadioGroup = nil +Cyclopedia.Charms = {} +local charmCategory_t = { + CHARM_ALL = 0, + CHARM_MAJOR = 1, + CHARM_MINOR = 2 +}; + +local charm_t = { + CHARM_UNDEFINED = 0, + CHARM_OFFENSIVE = 1, + CHARM_DEFENSIVE = 2, + CHARM_PASSIVE = 3 +}; +local charmRune_t = { + CHARM_WOUND = 0, + CHARM_ENFLAME = 1, + CHARM_POISON = 2, + CHARM_FREEZE = 3, + CHARM_ZAP = 4, + CHARM_CURSE = 5, + CHARM_CRIPPLE = 6, + CHARM_PARRY = 7, + CHARM_DODGE = 8, + CHARM_ADRENALINE = 9, + CHARM_NUMB = 10, + CHARM_CLEANSE = 11, + CHARM_BLESS = 12, + CHARM_SCAVENGE = 13, + CHARM_GUT = 14, + CHARM_LOW = 15, + CHARM_DIVINE = 16, + CHARM_VAMP = 17, + CHARM_VOID = 18, + CHARM_SAVAGE = 19, + CHARM_FATAL = 20, + CHARM_VOIDINVERSION = 21, + CHARM_CARNAGE = 22, + CHARM_OVERPOWER = 23, + CHARM_OVERFLUX = 24 +} + +local charms = { + [charmRune_t.CHARM_WOUND] = { + name = "Wound", + description = "Triggers on a creature with a chance to deal 5% of its initial HP as physical damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {240, 360, 1200} + }, + [charmRune_t.CHARM_ENFLAME] = { + name = "Enflame", + description = "Triggers on a creature with a chance to deal 5% of its initial HP as fire damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {400, 600, 2000} + }, + [charmRune_t.CHARM_POISON] = { + name = "Poison", + description = "Triggers on a creature with a chance to deal 5% of its initial HP as earth damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {240, 360, 1200} + }, + [charmRune_t.CHARM_FREEZE] = { + name = "Freeze", + description = "Triggers on a creature with a chance to deal 5% of its initial HP as ice damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {320, 480, 1600} + }, + [charmRune_t.CHARM_ZAP] = { + name = "Zap", + description = "Triggers on a creature with a chance to deal 5% of its initial HP as energy damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {320, 480, 1600} + }, + [charmRune_t.CHARM_CURSE] = { + name = "Curse", + description = "Triggers on a creature with a chance to deal 5% of its initial HP as death damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {360, 540, 1800} + }, + [charmRune_t.CHARM_CRIPPLE] = { + name = "Cripple", + description = "Cripples the creature and paralyzes it for 10 seconds.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_OFFENSIVE, + chance = {6, 9, 12}, + messageCancel = "You crippled a monster. (cripple charm)", + points = {100, 150, 225} + }, + [charmRune_t.CHARM_PARRY] = { + name = "Parry", + description = "Reflects incoming damage back to the aggressor.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_DEFENSIVE, + chance = {5, 10, 11}, + messageCancel = "You parried an attack. (parry charm)", + points = {400, 600, 2000} + }, + [charmRune_t.CHARM_DODGE] = { + name = "Dodge", + description = "Dodges an attack with a chance, avoiding all damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_DEFENSIVE, + chance = {5, 10, 11}, + messageCancel = "You dodged an attack. (dodge charm)", + points = {240, 360, 1200} + }, + [charmRune_t.CHARM_ADRENALINE] = { + name = "Adrenaline Burst", + description = "Boosts movement speed for 10 seconds after being hit.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_DEFENSIVE, + chance = {6, 9, 12}, + messageCancel = "Your movements where bursted. (adrenaline burst charm)", + points = {100, 150, 225} + }, + [charmRune_t.CHARM_NUMB] = { + name = "Numb", + description = "Numbs the creature and paralyzes it for 10 seconds.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_DEFENSIVE, + chance = {6, 9, 12}, + messageCancel = "You numbed a monster. (numb charm)", + points = {100, 150, 225} + }, + [charmRune_t.CHARM_CLEANSE] = { + name = "Cleanse", + description = "Removes a negative status effect and grants temporary immunity.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_DEFENSIVE, + chance = {6, 9, 12}, + messageCancel = "You purified an attack. (cleanse charm)", + points = {100, 150, 225} + }, + [charmRune_t.CHARM_BLESS] = { + name = "Bless", + description = "Reduces skill and XP loss by 10% when killed by the chosen creature.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + percent = 10, + chance = {6, 9, 12}, + points = {100, 150, 225} + }, + [charmRune_t.CHARM_SCAVENGE] = { + name = "Scavenge", + description = "Enhances chances to successfully skin or dust a creature.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + chance = {60, 90, 120}, + points = {100, 150, 225} + }, + [charmRune_t.CHARM_GUT] = { + name = "Gut", + description = "Increases creature product yields by 20%.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + chance = {6, 9, 12}, + points = {100, 150, 225} + }, + [charmRune_t.CHARM_LOW] = { + name = "Low Blow", + description = "Adds 8% critical hit chance to attacks with critical hit weapons.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_PASSIVE, + chance = {4, 8, 9}, + points = {800, 1200, 4000} + }, + [charmRune_t.CHARM_DIVINE] = { + name = "Divine Wrath", + description = "Triggers on a creature and deals 5% of its initial HP as holy damage.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {600, 900, 3000} + }, + [charmRune_t.CHARM_VAMP] = { + name = "Vampiric Embrace", + description = "Adds 4% life leech to attacks if using life-leeching equipment.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + chance = {1.6, 2.4, 3.2}, + points = {100, 150, 225} + }, + [charmRune_t.CHARM_VOID] = { + name = "Void's Call", + description = "Adds 2% mana leech to attacks if using mana-leeching equipment.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + chance = {0.8, 1.2, 1.6}, + points = {100, 150, 225} + }, + [charmRune_t.CHARM_SAVAGE] = { + name = "Savage Blow", + description = "Adds extra critical damage to attacks with critical hit weapons.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_PASSIVE, + chance = {20, 40, 44}, + points = {800, 1200, 4000} + }, + [charmRune_t.CHARM_FATAL] = { + name = "Fatal Hold", + description = "Prevents creatures from fleeing due to low health for 30 seconds.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + chance = {30, 45, 60}, + messageCancel = "Your enemy is not able to flee now for 30 seconds. (fatal hold charm)", + points = {100, 150, 225} + }, + [charmRune_t.CHARM_VOIDINVERSION] = { + name = "Void Inversion", + description = "Chance to gain mana instead of losing it when taking Mana Drain damage.", + category = charmCategory_t.CHARM_MINOR, + type = charm_t.CHARM_PASSIVE, + chance = {20, 30, 40}, + points = {100, 150, 225} + }, + [charmRune_t.CHARM_CARNAGE] = { + name = "Carnage", + description = "Killing a monster deals physical damage to others nearby.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 15, + chance = {10, 20, 22}, + points = {600, 900, 3000} + }, + [charmRune_t.CHARM_OVERPOWER] = { + name = "Overpower", + description = "Deals physical damage based on your maximum health.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 5, + chance = {5, 10, 11}, + points = {600, 900, 3000} + }, + [charmRune_t.CHARM_OVERFLUX] = { + name = "Overflux", + description = "Deals physical damage based on your maximum mana.", + category = charmCategory_t.CHARM_MAJOR, + type = charm_t.CHARM_OFFENSIVE, + percent = 2.5, + chance = {5, 10, 11}, + points = {600, 900, 3000} + } +} +local isModernUI = false function showCharms() - UI = g_ui.loadUI("charms", contentContainer) + isModernUI = g_game.getClientVersion() >= 1410 + local UIUX = isModernUI and "charms1410" or "charms" + UI = g_ui.loadUI(UIUX, contentContainer) UI:show() g_game.requestBestiary() controllerCyclopedia.ui.CharmsBase:setVisible(true) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) + if isModernUI then + controllerCyclopedia.ui.CharmsBase1410:setVisible(true) + TypeCharmRadioGroup = UIRadioGroup.create() + TypeCharmRadioGroup:addWidget(UI.mainPanelCharmsType.typeCharmPanel.MajorCharms) + TypeCharmRadioGroup:addWidget(UI.mainPanelCharmsType.typeCharmPanel.MinorCharms) + TypeCharmRadioGroup:selectWidget(TypeCharmRadioGroup:getFirstWidget()) + connect(TypeCharmRadioGroup, { + onSelectionChange = onTypeCharmRadioGroup + }) + end end -Cyclopedia.Charms = {} - -local CHARMS = { - { ID = 9, IMAGE = "/game_cyclopedia/images/charms/9", TYPE = 1 }, - { ID = 11, IMAGE = "/game_cyclopedia/images/charms/11", TYPE = 2 }, - { ID = 10, IMAGE = "/game_cyclopedia/images/charms/10", TYPE = 3 }, - { ID = 6, IMAGE = "/game_cyclopedia/images/charms/6", TYPE = 2 }, - { ID = 8, IMAGE = "/game_cyclopedia/images/charms/8", TYPE = 2 }, - { ID = 7, IMAGE = "/game_cyclopedia/images/charms/7", TYPE = 3 }, - { ID = 5, IMAGE = "/game_cyclopedia/images/charms/5", TYPE = 4 }, - { ID = 1, IMAGE = "/game_cyclopedia/images/charms/1", TYPE = 4 }, - { ID = 3, IMAGE = "/game_cyclopedia/images/charms/3", TYPE = 4 }, - { ID = 2, IMAGE = "/game_cyclopedia/images/charms/2", TYPE = 4 }, - { ID = 0, IMAGE = "/game_cyclopedia/images/charms/0", TYPE = 4 }, - { ID = 4, IMAGE = "/game_cyclopedia/images/charms/4", TYPE = 4 }, - { ID = 16, IMAGE = "/game_cyclopedia/images/charms/16", TYPE = 5 }, - { ID = 15, IMAGE = "/game_cyclopedia/images/charms/15", TYPE = 6 }, - { ID = 17, IMAGE = "/game_cyclopedia/images/charms/17", TYPE = 6 }, - { ID = 13, IMAGE = "/game_cyclopedia/images/charms/13", TYPE = 6 }, - { ID = 12, IMAGE = "/game_cyclopedia/images/charms/12", TYPE = 6 }, - { ID = 14, IMAGE = "/game_cyclopedia/images/charms/14", TYPE = 6 }, - { ID = 18, IMAGE = "/game_cyclopedia/images/charms/18", TYPE = 6 }, -} - -function Cyclopedia.UpdateCharmsBalance(Value) - for i, child in pairs(UI.Bottombase:getChildren()) do - if child.CharmsBase then - child.CharmsBase.Value:setText(Value) - end +function onTerminateCharm() + if TypeCharmRadioGroup then + disconnect(TypeCharmRadioGroup, { + onSelectionChange = onTypeCharmRadioGroup + }) + TypeCharmRadioGroup:destroy() + TypeCharmRadioGroup = nil end end function Cyclopedia.CreateCharmItem(data) - local widget = g_ui.createWidget("CharmItem", UI.CharmList) + local CharmList = isModernUI and UI.mainPanelCharmsType.panelCharmList.CharmList or UI.CharmList + local widget = g_ui.createWidget("CharmItem", CharmList) local value = widget.PriceBase.Value widget:setId(data.id) + widget.charmBase.image:setImageSource("/game_cyclopedia/images/charms/monster-bonus-effects") if data.id ~= nil then - widget.charmBase.image:setImageSource("/game_cyclopedia/images/charms/" .. data.id) + widget.charmBase.image:setImageClip((data.id * 32) .. ' 0 32 32') else g_logger.error(string.format("Cyclopedia.CreateCharmItem - charm %s is nil", data.id)) return end - widget:setText(data.name) + local charmData = charms[data.id] + widget:setText(isModernUI and charmData.name or data.name) widget.data = data if data.asignedStatus then if data.raceId then - widget.InfoBase.Sprite:setOutfit(g_things.getRaceData(data.raceId).outfit) + local raceData = g_things.getRaceData(data.raceId) + widget.InfoBase.Sprite:setOutfit(raceData.outfit) widget.InfoBase.Sprite:getCreature():setStaticWalking(1000) else g_logger.error("Cyclopedia.CreateCharmItem - no race id provided") end end - if data.unlocked then - widget.PriceBase.Charm:setVisible(false) - widget.PriceBase.Gold:setVisible(true) - widget.charmBase.lockedMask:setVisible(false) - widget.icon = 1 - if data.asignedStatus then - widget.PriceBase.Value:setText(comma_value(data.removeRuneCost)) - else - widget.PriceBase.Value:setText(0) - end + local isUnlocked = data.tier > 0 or data.unlocked + if not isModernUI then + widget.PriceBase.Charm:setVisible(not isUnlocked) + widget.PriceBase.Gold:setVisible(isUnlocked) + end + widget.charmBase.lockedMask:setVisible(not isUnlocked) + widget.icon = isUnlocked and 1 or 0 + + if isUnlocked then + widget.PriceBase.Value:setText(data.asignedStatus and comma_value(data.removeRuneCost) or 0) else - widget.PriceBase.Charm:setVisible(true) - widget.PriceBase.Gold:setVisible(false) - widget.charmBase.lockedMask:setVisible(true) widget.PriceBase.Value:setText(comma_value(data.unlockPrice)) - widget.icon = 0 end - if widget.icon == 1 and g_game.getLocalPlayer():getResourceBalance(1) then - if data.removeRuneCost > g_game.getLocalPlayer():getResourceBalance(1) then - value:setColor("#D33C3C") - else - value:setColor("#C0C0C0") - end + local player = g_game.getLocalPlayer() + if widget.icon == 1 and player:getResourceBalance(ResourceTypes.GOLD_EQUIPPED) then + local canAfford = data.removeRuneCost <= player:getResourceBalance(ResourceTypes.GOLD_EQUIPPED) + value:setColor(canAfford and "#C0C0C0" or "#D33C3C") + elseif widget.icon == 0 then + local canAfford = data.unlockPrice <= UI.CharmsPoints + value:setColor(canAfford and "#C0C0C0" or "#D33C3C") end - if widget.icon == 0 then - if data.unlockPrice > UI.CharmsPoints then - value:setColor("#D33C3C") - else - value:setColor("#C0C0C0") - end + widget.category = charmData.category + + if isModernUI and data.tier > 0 then + widget.charmBase.border:setImageSource("/game_cyclopedia/images/charms/border/backdrop_charmgrade" .. data.tier) end end function Cyclopedia.loadCharms(charmsData) - controllerCyclopedia.ui.CharmsBase.Value:setText(Cyclopedia.formatGold(charmsData.points)) - - if UI == nil or UI.CharmList == nil then -- I know, don't change it + if not UI then + return + end + if isModernUI and not UI.mainPanelCharmsType then return end + local CharmList = isModernUI and UI.mainPanelCharmsType.panelCharmList.CharmList or UI.CharmList + local player = g_game.getLocalPlayer() + + if isModernUI then + local formatResourceBalance = function(resourceType, maxResourceType) + return string.format("%d/%d", player:getResourceBalance(resourceType), + player:getResourceBalance(maxResourceType)) + end + + controllerCyclopedia.ui.CharmsBase.Value:setText(formatResourceBalance(ResourceTypes.CHARM, + ResourceTypes.MAX_CHARM)) + controllerCyclopedia.ui.CharmsBase1410.Value:setText( + formatResourceBalance(ResourceTypes.MINOR_CHARM, ResourceTypes.MAX_MINOR_CHARM)) + else + controllerCyclopedia.ui.CharmsBase.Value:setText(Cyclopedia.formatGold(charmsData.points)) + end UI.CharmsPoints = charmsData.points + Cyclopedia.Charms.Monsters = {} local raceIdNamePairs = {} for _, raceId in ipairs(charmsData.finishedMonsters) do - local raceName = g_things.getRaceData(raceId).name - if #raceName == 0 then - raceName = string.format("unnamed_%d", raceId) - end - + local raceData = g_things.getRaceData(raceId) + local raceName = raceData.name ~= "" and raceData.name or string.format("unnamed_%d", raceId) table.insert(raceIdNamePairs, { raceId = raceId, name = raceName }) end - local function compareByName(a, b) + table.sort(raceIdNamePairs, function(a, b) return a.name:lower() < b.name:lower() - end - - table.sort(raceIdNamePairs, compareByName) - - Cyclopedia.Charms.Monsters = {} + end) for _, pair in ipairs(raceIdNamePairs) do table.insert(Cyclopedia.Charms.Monsters, pair.raceId) end - UI.CharmList:destroyChildren() - - local formatedData = {} + CharmList:destroyChildren() + local formattedData = {} for _, charmData in pairs(charmsData.charms) do - local internalId = (charmData.id) - if internalId then + local internalId = charmData.id + if internalId and charms[internalId] then + local charm = charms[internalId] + charmData.name = charmData.name ~= "" and charmData.name or charm.name + charmData.description = charmData.description ~= "" and charmData.description or charm.description charmData.internalId = internalId - charmData.typePriority = CHARMS[internalId + 1].TYPE - table.insert(formatedData, charmData) + charmData.typePriority = charm.type + charmData.category = charm.category + table.insert(formattedData, charmData) end end - table.sort(formatedData, function(a, b) - if a.unlocked == b.unlocked then - if a.typePriority == b.typePriority then - return a.name < b.name - else - return a.typePriority < b.typePriority + if isModernUI then + table.sort(formattedData, function(a, b) + local tierA, tierB = a.tier or 0, b.tier or 0 + if tierA ~= tierB then + return tierA > tierB + end + return a.name:lower() < b.name:lower() + end) + else + table.sort(formattedData, function(a, b) + if a.unlocked ~= b.unlocked then + return a.unlocked and not b.unlocked + end + return a.name:lower() < b.name:lower() + end) + end + + for _, value in ipairs(formattedData) do + if value and value.name and value.description and value.internalId and value.typePriority then + local success, error = pcall(Cyclopedia.CreateCharmItem, value) + if not success then + g_logger.error(string.format("Error creating charm item: %s for charm ID: %s (%s)", error, + tostring(value.internalId), tostring(value.name))) end else - return a.unlocked and not b.unlocked + g_logger.error(string.format("Incomplete charm data: ID: %s", + value and tostring(value.internalId or "unknown") or "nil")) end - end) + end + + if isModernUI then + local selectedWidget = TypeCharmRadioGroup:getSelectedWidget() + if selectedWidget then + local charmCategory = selectedWidget:getId() == "MajorCharms" and charmCategory_t.CHARM_MAJOR or + charmCategory_t.CHARM_MINOR + + for _, widget in ipairs(CharmList:getChildren()) do + widget:setVisible(widget.category == charmCategory) + end - for _, value in ipairs(formatedData) do - Cyclopedia.CreateCharmItem(value) + CharmList:getLayout():update() + end end - if Cyclopedia.Charms.redirect then - Cyclopedia.selectCharm(UI.CharmList:getChildById(Cyclopedia.Charms.redirect), - UI.CharmList:getChildById(Cyclopedia.Charms.redirect):isChecked()) + local firstCharm = Cyclopedia.Charms.redirect and CharmList:getChildById(Cyclopedia.Charms.redirect) or + CharmList:getChildByIndex(1) + + if firstCharm then + Cyclopedia.selectCharm(firstCharm, firstCharm:isChecked()) Cyclopedia.Charms.redirect = nil + end +end + +local function getUIBase() + if isModernUI then + return { + CreatureList = UI.InformationBase.PanelCreatureList.CreaturesBase.CreatureList, + InfoBase = UI.InformationBase.panelSelectCreature.InfoBase, + TextBase = UI.InformationBase.TextBase, + ItemBase = UI.InformationBase.ItemBase, + PriceBase = UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseGold, + UnlockButton = UI.InformationBase.verticalPanelUnLockClearChram.UnlockButton, + SearchEdit = UI.InformationBase.PanelCreatureList.SearchEdit.SearchEdit, + SearchLabel = UI.InformationBase.SearchLabel, + CreaturesBase = UI.InformationBase.PanelCreatureList.CreaturesBase, + CreaturesLabel = UI.InformationBase.panelSelectCreature.CreaturesLabel + } else - Cyclopedia.selectCharm(UI.CharmList:getChildByIndex(1), UI.CharmList:getChildByIndex(1):isChecked()) + return { + CreatureList = UI.InformationBase.CreaturesBase.CreatureList, + InfoBase = UI.InformationBase.InfoBase, + TextBase = UI.InformationBase.TextBase, + ItemBase = UI.InformationBase.ItemBase, + PriceBase = UI.InformationBase.PriceBase, + UnlockButton = UI.InformationBase.UnlockButton, + SearchEdit = UI.InformationBase.SearchEdit, + SearchLabel = UI.InformationBase.SearchLabel, + CreaturesBase = UI.InformationBase.CreaturesBase, + CreaturesLabel = UI.InformationBase.CreaturesLabel + } end end -function Cyclopedia.selectCharm(widget, isChecked) - UI.InformationBase.CreaturesBase.CreatureList:destroyChildren() +local function formatCreatureName(text) + local capitalizedText = text:gsub("(%l)(%w*)", function(first, rest) + return first:upper() .. rest + end) + return #capitalizedText > 19 and capitalizedText:sub(1, 16) .. "..." or capitalizedText +end - local parent = widget:getParent() - local button = UI.InformationBase.UnlockButton - local value = UI.InformationBase.PriceBase.Value +local function updateUIColors(widget, UI_BASE) + local player = g_game.getLocalPlayer() + local priceValue = UI_BASE.PriceBase.Value + if isModernUI then + local charmEntry = charms[widget.data.id] + if charmEntry and charmEntry.points and charmEntry.points[widget.data.tier + 1] then + local selectedWidget = TypeCharmRadioGroup:getSelectedWidget() + if selectedWidget then + local charmCategory = selectedWidget:getId() == "MajorCharms" and ResourceTypes.CHARM or + ResourceTypes.MINOR_CHARM + local pointsValue = charmEntry.points[widget.data.tier + 1] + local canAfford = pointsValue <= player:getResourceBalance(charmCategory) + UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseCharm.Value:setColor( + canAfford and "#C0C0C0" or "#D33C3C") + UI.InformationBase.verticalPanelUnLockClearChram.UnlockButton:setEnabled(canAfford) + end + else + UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseCharm.Value:setColor("#C0C0C0") + UI.InformationBase.verticalPanelUnLockClearChram.UnlockButton:setEnabled(false) + end + if not widget.data.asignedStatus then + priceValue:setText(0) + end + else + if widget.icon == 1 and player:getResourceBalance(ResourceTypes.GOLD_EQUIPPED) then + local canAfford = widget.data.removeRuneCost <= player:getResourceBalance(ResourceTypes.GOLD_EQUIPPED) + priceValue:setColor(canAfford and "#C0C0C0" or "#D33C3C") + UI_BASE.UnlockButton:setEnabled(canAfford) + + local priceText = (widget.data.unlocked and not widget.data.asignedStatus) and 0 or + comma_value(widget.data.removeRuneCost) + priceValue:setText(priceText) + elseif widget.icon == 0 then + local canAfford = widget.data.unlockPrice <= UI.CharmsPoints + priceValue:setColor(canAfford and "#C0C0C0" or "#D33C3C") + UI_BASE.UnlockButton:setEnabled(canAfford) + priceValue:setText(widget.data.unlockPrice) + end + end +end - UI.InformationBase.data = widget.data +local function setupCreatureList(widget, UI_BASE) + if (widget.data.unlocked and not widget.data.asignedStatus) or isModernUI then + UI_BASE.UnlockButton:setText("Select") - local function format(text) - local capitalizedText = text:gsub("(%l)(%w*)", function(first, rest) - return first:upper() .. rest - end) + local color = "#484848" + for index, raceId in ipairs(Cyclopedia.Charms.Monsters) do + local creatureWidget = g_ui.createWidget("CharmCreatureName", UI_BASE.CreatureList) + creatureWidget:setId(index) + creatureWidget:setText(formatCreatureName(g_things.getRaceData(raceId).name)) + creatureWidget.raceId = raceId + creatureWidget:setBackgroundColor(color) + creatureWidget.color = color + color = color == "#484848" and "#414141" or "#484848" + end - if #capitalizedText > 19 then - return capitalizedText:sub(1, 16) .. "..." - else - return capitalizedText + UI_BASE.UnlockButton:setEnabled(false) + UI_BASE.SearchEdit:setEnabled(true) + if UI_BASE.SearchLabel then + UI_BASE.SearchLabel:setEnabled(true) end + UI_BASE.CreaturesLabel:setEnabled(true) end +end + +local function setupModernVersionUpgrade(widget, UI_BASE) + if not isModernUI then + return + end + + local charmId = widget.data.id + local tier = widget.data.tier or 0 + local charmEntry = charms[charmId] + + if charmEntry and charmEntry.points and charmEntry.points[tier + 1] then + local pointsValue = charmEntry.points[tier + 1] + local chanceValue = charmEntry.chance and charmEntry.chance[tier + 1] or 0 + + UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseCharm.Value:setText(comma_value(pointsValue)) + local tierButtons = { + [3] = "Fully Unlocked", + [2] = string.format("Upgrade to %d%%", charmEntry.chance[3] or 0), + [1] = string.format("Upgrade to %d%%", charmEntry.chance[2] or 0), + [0] = "Unlock" + } + UI_BASE.UnlockButton:setText(tierButtons[tier]) + if tier >= 0 and tier < 3 then + UI_BASE.UnlockButton:setEnabled(true) + end + UI_BASE.UnlockButton:getParent().data = widget.data + else + UI_BASE.UnlockButton:setText("Fully Unlocked") + UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseCharm.Value:setText(comma_value(0)) + end +end + +function Cyclopedia.selectCharm(widget, isChecked) + local UI_BASE = getUIBase() + UI_BASE.CreatureList:destroyChildren() + local parent = widget:getParent() + UI.InformationBase.data = widget.data for i = 1, parent:getChildCount() do local internalWidget = parent:getChildByIndex(i) - if internalWidget:isChecked() and widget:getId() ~= internalWidget:getId() then internalWidget:setChecked(false) end @@ -207,96 +618,79 @@ function Cyclopedia.selectCharm(widget, isChecked) widget:setChecked(true) end - UI.InformationBase.TextBase:setText(widget.data.description) - UI.InformationBase.ItemBase.image:setImageSource(widget.charmBase.image:getImageSource()) - - if widget.data.asignedStatus then - UI.InformationBase.InfoBase.sprite:setVisible(true) - UI.InformationBase.InfoBase.sprite:setOutfit(g_things.getRaceData(widget.data.raceId).outfit) - UI.InformationBase.InfoBase.sprite:getCreature():setStaticWalking(1000) - UI.InformationBase.InfoBase.sprite:setOpacity(1) - else - UI.InformationBase.InfoBase.sprite:setVisible(false) - end - - if widget.icon == 1 then - UI.InformationBase.PriceBase.Gold:setVisible(true) - UI.InformationBase.PriceBase.Charm:setVisible(false) - else - UI.InformationBase.PriceBase.Gold:setVisible(false) - UI.InformationBase.PriceBase.Charm:setVisible(true) - end - - if widget.icon == 1 and g_game.getLocalPlayer():getResourceBalance(1) then - if widget.data.removeRuneCost > g_game.getLocalPlayer():getResourceBalance(1) then - value:setColor("#D33C3C") - button:setEnabled(false) - else - value:setColor("#C0C0C0") - button:setEnabled(true) - end + UI_BASE.TextBase:setText(widget.data.description) + UI_BASE.ItemBase.image:setImageSource(widget.charmBase.image:getImageSource()) + UI_BASE.ItemBase.image:setImageClip(widget.charmBase.image:getImageClip()) - if widget.data.unlocked and not widget.data.asignedStatus then - value:setText(0) + if isModernUI then + UI.InformationBase:setText(widget:getText()) + if widget.data.tier > 0 then + UI_BASE.ItemBase.border:setImageSource("/game_cyclopedia/images/charms/border/backdrop_charmgrade" .. + widget.data.tier) + UI_BASE.ItemBase.lockedMask:setVisible(false) else - value:setText(comma_value(widget.data.removeRuneCost)) + UI_BASE.ItemBase.lockedMask:setVisible(true) + UI_BASE.ItemBase.border:setImageSource("") end end - if widget.icon == 0 then - if widget.data.unlockPrice > UI.CharmsPoints then - value:setColor("#D33C3C") - button:setEnabled(false) - else - value:setColor("#C0C0C0") - button:setEnabled(true) - end - - value:setText(widget.data.unlockPrice) + if widget.data.asignedStatus then + local sprite = UI_BASE.InfoBase.sprite + sprite:setVisible(true) + sprite:setOutfit(g_things.getRaceData(widget.data.raceId).outfit) + sprite:getCreature():setStaticWalking(1000) + sprite:setOpacity(1) + else + UI_BASE.InfoBase.sprite:setVisible(false) end - if widget.data.unlocked and not widget.data.asignedStatus then - button:setText("Select") - - local color = "#484848" + if not isModernUI then + UI_BASE.PriceBase.Gold:setVisible(widget.icon == 1) + UI_BASE.PriceBase.Charm:setVisible(widget.icon == 0) + end - for index, raceId in ipairs(Cyclopedia.Charms.Monsters) do - local internalWidget = g_ui.createWidget("CharmCreatureName", UI.InformationBase.CreaturesBase.CreatureList) - internalWidget:setId(index) - internalWidget:setText(format(g_things.getRaceData(raceId).name)) - internalWidget.raceId = raceId - internalWidget:setBackgroundColor(color) - internalWidget.color = color - color = color == "#484848" and "#414141" or "#484848" - end + updateUIColors(widget, UI_BASE) - button:setEnabled(false) - UI.InformationBase.SearchEdit:setEnabled(true) - UI.InformationBase.SearchLabel:setEnabled(true) - UI.InformationBase.CreaturesLabel:setEnabled(true) - end + setupCreatureList(widget, UI_BASE) if widget.data.asignedStatus then - button:setText("Remove") - - local internalWidget = g_ui.createWidget("CharmCreatureName", UI.InformationBase.CreaturesBase.CreatureList) - internalWidget:setText(format(g_things.getRaceData(widget.data.raceId).name)) - internalWidget:setEnabled(false) - internalWidget:setColor("#707070") - UI.InformationBase.SearchEdit:setEnabled(false) - UI.InformationBase.SearchLabel:setEnabled(false) - UI.InformationBase.CreaturesLabel:setEnabled(false) + UI_BASE.UnlockButton:setText("Remove") + local creatureWidget = g_ui.createWidget("CharmCreatureName", UI_BASE.CreatureList) + creatureWidget:setText(formatCreatureName(g_things.getRaceData(widget.data.raceId).name)) + creatureWidget:setEnabled(false) + creatureWidget:setColor("#707070") + + UI_BASE.SearchEdit:setEnabled(false) + if UI_BASE.SearchLabel then + UI_BASE.SearchLabel:setEnabled(false) + end + UI_BASE.CreaturesLabel:setEnabled(false) end if not widget.data.unlocked then - button:setText("Unlock") - UI.InformationBase.SearchEdit:setEnabled(false) - UI.InformationBase.SearchLabel:setEnabled(false) - UI.InformationBase.CreaturesLabel:setEnabled(false) + UI_BASE.UnlockButton:setText("Unlock") + UI_BASE.SearchEdit:setEnabled(false) + if UI_BASE.SearchLabel then + UI_BASE.SearchLabel:setEnabled(false) + end + if not isModernUI then + UI_BASE.CreaturesLabel:setEnabled(false) + end end + + setupModernVersionUpgrade(widget, UI_BASE) end function Cyclopedia.selectCreatureCharm(widget, isChecked) + local UI_BASE = {} + + if isModernUI then + UI_BASE.InfoBase = UI.InformationBase.panelSelectCreature.InfoBase + UI_BASE.UnlockButton = UI.InformationBase.verticalPanelUnLockClearChram.UnlockButton + else + UI_BASE.InfoBase = UI.InformationBase.InfoBase + UI_BASE.UnlockButton = UI.InformationBase.UnlockButton + end local parent = widget:getParent() for i = 1, parent:getChildCount() do @@ -312,17 +706,25 @@ function Cyclopedia.selectCreatureCharm(widget, isChecked) widget:setChecked(true) end - UI.InformationBase.InfoBase.sprite:setVisible(true) - UI.InformationBase.InfoBase.sprite:setOutfit(g_things.getRaceData(widget.raceId).outfit) - UI.InformationBase.InfoBase.sprite:getCreature():setStaticWalking(1000) - UI.InformationBase.InfoBase.sprite:setOpacity(0.5) - UI.InformationBase.UnlockButton:setEnabled(true) + UI_BASE.InfoBase.sprite:setVisible(true) + UI_BASE.InfoBase.sprite:setOutfit(g_things.getRaceData(widget.raceId).outfit) + UI_BASE.InfoBase.sprite:getCreature():setStaticWalking(1000) + UI_BASE.InfoBase.sprite:setOpacity(0.5) + UI_BASE.UnlockButton:setEnabled(true) Cyclopedia.Charms.SelectedCreature = widget.raceId end function Cyclopedia.searchCharmMonster(text) - UI.InformationBase.CreaturesBase.CreatureList:destroyChildren() + local UI_BASE = {} + + if isModernUI then + UI_BASE.CreaturesBase = UI.InformationBase.PanelCreatureList.CreaturesBase + else + UI_BASE.CreaturesBase = UI.InformationBase.CreaturesBase + end + + UI_BASE.CreaturesBase.CreatureList:destroyChildren() local function format(string) local capitalizedText = string:gsub("(%l)(%w*)", function(first, rest) @@ -356,7 +758,7 @@ function Cyclopedia.searchCharmMonster(text) local color = "#484848" for _, raceId in ipairs(searchedMonsters) do - local internalWidget = g_ui.createWidget("CharmCreatureName", UI.InformationBase.CreaturesBase.CreatureList) + local internalWidget = g_ui.createWidget("CharmCreatureName", UI_BASE.CreaturesBase.CreatureList) internalWidget:setId(raceId) internalWidget:setText(format(g_things.getRaceData(raceId).name)) internalWidget.raceId = raceId @@ -373,7 +775,11 @@ function Cyclopedia.actionCharmButton(widget) if type == "Unlock" then local function yesCallback() - g_game.BuyCharmRune(data.id) + if isModernUI then + g_game.BuyCharmRune(0, data.id, 0) + else + g_game.BuyCharmRune(data.id) + end if confirmWindow then confirmWindow:destroy() confirmWindow = nil @@ -403,14 +809,16 @@ function Cyclopedia.actionCharmButton(widget) callback = noCallback }, anchor = AnchorHorizontalCenter - }, yesCallback, noCallback - ) + }, yesCallback, noCallback) end end - - if type == "Select" then + if type == "Select" or type == "Select Creature" then local function yesCallback() - g_game.BuyCharmRune(data.id, 1, Cyclopedia.Charms.SelectedCreature) + if isModernUI then + g_game.BuyCharmRune(1, data.id, Cyclopedia.Charms.SelectedCreature) + else + g_game.BuyCharmRune(data.id, 1, Cyclopedia.Charms.SelectedCreature) + end if confirmWindow then confirmWindow:destroy() confirmWindow = nil @@ -437,8 +845,7 @@ function Cyclopedia.actionCharmButton(widget) callback = noCallback }, anchor = AnchorHorizontalCenter - }, yesCallback, noCallback - ) + }, yesCallback, noCallback) end end @@ -473,8 +880,104 @@ function Cyclopedia.actionCharmButton(widget) callback = noCallback }, anchor = AnchorHorizontalCenter - }, yesCallback, noCallback - ) + }, yesCallback, noCallback) + end + end + if isModernUI and type:match("^Upgrade") then + local function yesCallback() + g_game.BuyCharmRune(0, data.id, 0) + if confirmWindow then + confirmWindow:destroy() + confirmWindow = nil + end + Cyclopedia.Charms.redirect = data.id + end + + local function noCallback() + if confirmWindow then + confirmWindow:destroy() + confirmWindow = nil + end + end + + if not confirmWindow then + confirmWindow = displayGeneralBox(tr("Confirm Unlocking of Charm"), tr( + "Do you want to upgrade the Charm %s? This will cost you %d Charm Points?", data.name, data.unlockPrice), + { + { + text = tr("Yes"), + callback = yesCallback + }, + { + text = tr("No"), + callback = noCallback + }, + anchor = AnchorHorizontalCenter + }, yesCallback, noCallback) + end + end +end + +function onTypeCharmRadioGroup(radioGroup, selectedWidget) + local charmCategory = selectedWidget:getId() == "MajorCharms" and charmCategory_t.CHARM_MAJOR or + charmCategory_t.CHARM_MINOR + local CharmList = UI.mainPanelCharmsType.panelCharmList.CharmList + if charmCategory == charmCategory_t.CHARM_MAJOR then + UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseCharm.Charm:setImageSource( + "/game_cyclopedia/images/monster-icon-bonuspoints") + else + UI.InformationBase.verticalPanelUnLockClearChram.PriceBaseCharm.Charm:setImageSource( + "/game_cyclopedia/images/minor-charm-echoes") + end + for _, widget in ipairs(CharmList:getChildren()) do + if widget.category == charmCategory then + widget:setVisible(true) + else + widget:setVisible(false) + end + end + + CharmList:getLayout():update() +end + +function Cyclopedia.actionSelectCharmButton(widget) + local confirmWindow + local type = widget:getText() + local data = UI.InformationBase.data + if type == "Select" or type == "Select Creature" then + local function yesCallback() + if isModernUI then + g_game.BuyCharmRune(1, data.id, Cyclopedia.Charms.SelectedCreature) + else + g_game.BuyCharmRune(data.id, 1, Cyclopedia.Charms.SelectedCreature) + end + if confirmWindow then + confirmWindow:destroy() + confirmWindow = nil + end + Cyclopedia.Charms.redirect = data.id + end + + local function noCallback() + if confirmWindow then + confirmWindow:destroy() + confirmWindow = nil + end + end + + if not confirmWindow then + confirmWindow = displayGeneralBox(tr("Confirm Selected Charm"), + tr("Do you want to use the Charm %s for this creature?", data.name), { + { + text = tr("Yes"), + callback = yesCallback + }, + { + text = tr("No"), + callback = noCallback + }, + anchor = AnchorHorizontalCenter + }, yesCallback, noCallback) end end end diff --git a/modules/game_cyclopedia/tab/charms/charms1410.otui b/modules/game_cyclopedia/tab/charms/charms1410.otui new file mode 100644 index 0000000000..4ea788962c --- /dev/null +++ b/modules/game_cyclopedia/tab/charms/charms1410.otui @@ -0,0 +1,327 @@ +UIWidget + id: Cat3 + anchors.fill: parent + visible: false + MiniPanel + id: InformationBase + text: carnage + anchors.top: parent.top + anchors.left: parent.left + size: 551 122 + layout: + type: horizontalBox + spacing: -1 + + Label + id: TextBase + text: "killin a monster has 10% chance to deal physical damage equal to 15& of its maximum health to all monsters in a small rabius" + width: 165 + text-wrap: true + margin-top: 25 + margin-right: 5 + BorderBox + id: ItemBase + image-source: /images/ui/panel_flat + image-border: 10 + size: 46 46 + margin-top: 23 + margin-bottom: 22 + phantom: true + border-width-top: 1 + border-color-top: black + border-width-left: 1 + border-color-left: black + border-width-bottom: 1 + border-color-bottom: gray + border-width-right: 1 + border-color-right: gray + UIWidget + size: 32 32 + id: image + anchors.centerIn: parent + phantom: true + UIWidget + id: lockedMask + size: 42 42 + anchors.centerIn: parent + image-source: /images/ui/ditherpattern + phantom: true + UIWidget + id: border + anchors.centerIn: parent + phantom: true + + Panel + id: verticalPanelUnLockClearChram + width: 92 + margin-left: 5 + QtButton + id: UnlockButton + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + text: UnLock + @onClick: modules.game_cyclopedia.Cyclopedia.actionCharmButton(self) + UIWidget + id: PriceBaseCharm + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + height: 20 + image-source: /images/ui/item + image-border: 10 + phantom: true + Label + id: Value + anchors.centerIn: parent + !text: tr('0') + text-auto-resize: true + UIWidget + id: Charm + anchors.left: Value.right + anchors.verticalCenter: parent.verticalCenter + margin-left: 2 + image-source: /game_cyclopedia/images/monster-icon-bonuspoints + + QtButton + id: check + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + text: Addon 1 + margin-top:3 + text: Clear Charm + + + UIWidget + id: PriceBaseGold + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 3 + height: 20 + image-source: /images/ui/item + image-border: 10 + phantom: true + Label + id: Value + anchors.centerIn: parent + !text: tr('0') + text-auto-resize: true + UIWidget + id: Gold + anchors.left: Value.right + anchors.verticalCenter: parent.verticalCenter + margin-left: 2 + image-source: /game_cyclopedia/images/icon_gold + visible: true + Panel + id: PanelCreatureList + width: 140 + margin-left: 5 + TextEditQuestLog + id:SearchEdit + height: 20 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + BorderBox + id: CreaturesBase + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 68 + margin-left: 5 + margin-top: 5 + image-source: /images/ui/panel_flat + image-border: 10 + phantom: true + UIScrollArea + id: CreatureList + vertical-scrollbar: CreatureListScrollbar + anchors.fill: parent + margin-right: 13 + padding-left: 3 + padding-top: 3 + padding-bottom: 2 + layout: + type: grid + cell-size: 125 15 + cell-spacing: 2 + flow: true + CharmCreatureName + background-color: #484848 + CharmCreatureName + background-color: #414141 + VerticalQtScrollBar + id: CreatureListScrollbar + anchors.top: parent.top + anchors.right: parent.right + anchors.bottom: parent.bottom + step: 80 + pixel-scroll: true + + Panel + id: panelSelectCreature + width: 80 + margin-left: 5 + QtButton + id: CreaturesLabel + height: 20 + text: Select Creature + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + @onClick: modules.game_cyclopedia.Cyclopedia.actionSelectCharmButton(self) + BorderBox + id: InfoBase + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 68 + margin-top: 5 + image-source: /images/ui/panel_flat + image-border: 10 + phantom: true + UICreature + id: sprite + anchors.centerIn: parent + size: 64 64 + + QtPanel + id: anotherPanel + size: 112 122 + anchors.top: parent.top + anchors.left: prev.right + margin-left: 5 + + QtButton + text: Reset all charms + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + margin-top: -15 + + UIWidget + id: CharmsBase1410 + anchors.left: parent.left + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + image-source: /images/ui/item + image-border: 10 + margin-top: 15 + size: 100 20 + !tooltip: tr("Minor Charm echoes\nEarned by unlocking or upgrading major charm and promotion your character") + UIWidget + id: Icon + anchors.right: parent.right + anchors.verticalCenter: parent.verticalCenter + margin-right: 4 + image-source: /game_cyclopedia/images/icon_gold + Label + id: Value + anchors.right: Icon.left + anchors.verticalCenter: parent.verticalCenter + margin-right: 4 + !text: tr('0') + text-auto-resize: true + color: #BDBDBD + + QtPanel + id: mainPanelCharmsType + height: 299 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: 5 + + Panel + id: typeCharmPanel + height: 38 + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + QtButton + id: MajorCharms + !text: tr('Major Charms') + font: verdana-11px-antialised + anchors.left: parent.left + anchors.top: parent.top + anchors.bottom: parent.bottom + width: 322 + margin: 1 + $on: + margin-top: -1 + $checked: + image-clip: 0 46 22 23 + text-offset: 1 1 + + UIWidget + image-source: /game_cyclopedia/images/charms/icon-charms-major + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + phantom: true + QtButton + id: MinorCharms + !text: tr('Minor Charms') + font: verdana-11px-antialised + width: 322 + anchors.top: parent.top + anchors.left: prev.right + anchors.bottom: parent.bottom + margin: 1 + $on: + margin-top: -1 + $checked: + image-clip: 0 46 22 23 + text-offset: 1 1 + + + UIWidget + image-source: /game_cyclopedia/images/charms/icon-charms-minor + anchors.verticalCenter: parent.verticalCenter + anchors.left: parent.left + phantom: true + + Panel + id: panelCharmList + font: verdana-11px-antialised + text-offset: 24 5 + text-align: topLeft + border-width: 1 + border-color: #00000077 + anchors.top: prev.bottom + anchors.left: parent.left + anchors.right: parent.right + height: 232 + margin-top: 10 + image-border: 4 + image-border-top: 23 + image-border-bottom: 4 + focusable: true + UIScrollArea + id: CharmList + vertical-scrollbar: spellsScrollBar + anchors.fill: parent + padding: 1 + width: 10 + focusable: true + background-color: #404040 + border-width: 1 + border-color: #272727 + padding: 1 + auto-focus: first + layout: + type: grid + cell-size: 158 100 + cell-spacing: 2 + flow: true + VerticalQtScrollBar + id: spellsScrollBar + anchors.top: prev.top + anchors.bottom: prev.bottom + anchors.right: prev.right + step: 50 + pixels-scroll: true + margin-right: -2 diff --git a/modules/game_cyclopedia/tab/house/house.lua b/modules/game_cyclopedia/tab/house/house.lua index 480e101c2a..022fd773ea 100644 --- a/modules/game_cyclopedia/tab/house/house.lua +++ b/modules/game_cyclopedia/tab/house/house.lua @@ -25,7 +25,9 @@ function showHouse() controllerCyclopedia.ui.CharmsBase:setVisible(false) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) - + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:setVisible(false) + end -- Cyclopedia.House.Data = json_data if not Cyclopedia.House.Loaded then diff --git a/modules/game_cyclopedia/tab/items/items.lua b/modules/game_cyclopedia/tab/items/items.lua index 198736a6fd..466fefb348 100644 --- a/modules/game_cyclopedia/tab/items/items.lua +++ b/modules/game_cyclopedia/tab/items/items.lua @@ -60,7 +60,9 @@ function showItems() controllerCyclopedia.ui.CharmsBase:setVisible(false) controllerCyclopedia.ui.GoldBase:setVisible(false) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) - + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:setVisible(false) + end local CategoryColor = "#484848" for _, data in ipairs(Cyclopedia.CategoryItems) do diff --git a/modules/game_cyclopedia/tab/magicalArchives/magicalArchives.lua b/modules/game_cyclopedia/tab/magicalArchives/magicalArchives.lua new file mode 100644 index 0000000000..dc876aee4d --- /dev/null +++ b/modules/game_cyclopedia/tab/magicalArchives/magicalArchives.lua @@ -0,0 +1,13 @@ +local UI = nil + +function showMagicalArchives() + UI = g_ui.loadUI("magicalArchives", contentContainer) + UI:show() + controllerCyclopedia.ui.CharmsBase:setVisible(false) + controllerCyclopedia.ui.GoldBase:setVisible(false) + controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:setVisible(false) + end +end + diff --git a/modules/game_cyclopedia/tab/magicalArchives/magicalArchives.otui b/modules/game_cyclopedia/tab/magicalArchives/magicalArchives.otui new file mode 100644 index 0000000000..037b1c8445 --- /dev/null +++ b/modules/game_cyclopedia/tab/magicalArchives/magicalArchives.otui @@ -0,0 +1,51 @@ +UIWidget + id: Cat5 + anchors.fill: parent + visible: false + UIWidget + id: InformationBase + anchors.top: parent.top + anchors.left: parent.left + anchors.bottom: parent.bottom + margin-bottom: 8 + margin-top: 10 + width: 180 + MiniPanel + id: selectSpell + anchors.fill: parent + image-border: 10 + padding-right: 16 + margin-bottom: 25 + !text: tr('Select Spell') + layout: + type: verticalBox + spacing: 6 + + ComboBox + TextListQuestLog + height: 330 + TextEditQuestLog + + MiniPanel + id: spellAndRune + text: Spell / Rune Information + anchors.top: InformationBase.top + anchors.left: InformationBase.right + anchors.right: parent.right + anchors.bottom: InformationBase.bottom + margin-left: 10 + margin-bottom: 25 + + Label + anchors.centerIn: parent + text: select a spell to view detailed information. + Label + id: deleteme + anchors.centerIn: parent + !text: tr("\nis this in the protobuf ?\n in http post ? \nor modify gamelib/spells.lua?") + font-scale: 2 + margin-top: 32 + text-auto-resize: true + + + diff --git a/modules/game_cyclopedia/tab/map/map.lua b/modules/game_cyclopedia/tab/map/map.lua index 46814ac14d..3e74e9141b 100644 --- a/modules/game_cyclopedia/tab/map/map.lua +++ b/modules/game_cyclopedia/tab/map/map.lua @@ -14,6 +14,9 @@ function showMap() controllerCyclopedia.ui.CharmsBase:setVisible(false) controllerCyclopedia.ui.GoldBase:setVisible(true) controllerCyclopedia.ui.BestiaryTrackerButton:setVisible(false) + if g_game.getClientVersion() >= 1410 then + controllerCyclopedia.ui.CharmsBase1410:setVisible(false) + end end function Cyclopedia.loadMap() diff --git a/modules/game_cyclopedia/utils.lua b/modules/game_cyclopedia/utils.lua index 57c02de244..644097026b 100644 --- a/modules/game_cyclopedia/utils.lua +++ b/modules/game_cyclopedia/utils.lua @@ -709,6 +709,6 @@ Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_ICE] = { path = '/game_cyclop Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_HOLY] = {path = '/game_cyclopedia/images/bestiary/icons/monster-icon-holy-resist', id = 'Holy' } Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_DEATH] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-death-resist', id = 'Death' } Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_HEALING] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-healing-resist', id = 'Healing' } -Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_DROWN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-drown-resist', id = 'Drown' } +Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_DROWN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-drowning-resist', id = 'Drown' } Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_LIFEDRAIN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-lifedrain-resist', id = 'Lifedrain ' } Cyclopedia.clientCombat[combatStates.CLIENT_COMBAT_MANADRAIN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-manadrain-resist', id = 'Manadrain' } diff --git a/modules/game_features/features.lua b/modules/game_features/features.lua index 121f3ec59d..05a79d49c0 100644 --- a/modules/game_features/features.lua +++ b/modules/game_features/features.lua @@ -210,6 +210,7 @@ controller:registerEvents(g_game, { end if version >= 1281 then + g_game.enableFeature(GameForgeSkillStats) g_game.enableFeature(GamePlayerFamiliars) g_game.disableFeature(GameEnvironmentEffect) g_game.disableFeature(GameItemAnimationPhase) @@ -252,7 +253,13 @@ controller:registerEvents(g_game, { end if version >= 1332 then - g_game.enableFeature(GameForgeConvergence); + g_game.enableFeature(GameForgeConvergence) + end + + if version >= 1410 then + g_game.disableFeature(GameAdditionalSkills) + g_game.disableFeature(GameForgeSkillStats) + g_game.enableFeature(GameCharacterSkillStats) end end }) diff --git a/modules/game_mainpanel/mainoptionspanel.otui b/modules/game_mainpanel/mainoptionspanel.otui index 53772bbdca..851bd0a0c6 100644 --- a/modules/game_mainpanel/mainoptionspanel.otui +++ b/modules/game_mainpanel/mainoptionspanel.otui @@ -75,14 +75,12 @@ PhantomMiniWindow cell-spacing: 2 flow: true - UIWidget - image-source: /images/ui/vertical_line_bright + VerticalSeparator anchors.top: options.top anchors.bottom: options.bottom anchors.left: store.right margin-left: 5 phantom: true - width: 1 UIWidget id: specials diff --git a/modules/game_questlog/game_questlog.lua b/modules/game_questlog/game_questlog.lua index 1223959058..d2147eb7ef 100644 --- a/modules/game_questlog/game_questlog.lua +++ b/modules/game_questlog/game_questlog.lua @@ -476,16 +476,16 @@ local function onQuestTracker(remainingQuests, missions) end trackerMiniWindow.contentsPanel.list:destroyChildren() for index, mission in ipairs(missions) do - local missionId, questName, questIsCompleted, missionName, missionDesc = unpack(mission) + local questId, missionId, questName, missionName, missionDesc = unpack(mission) local trackerLabel = g_ui.createWidget('QuestTrackerLabel', trackerMiniWindow.contentsPanel.list) trackerLabel:setId(missionId) trackerLabel.description:setText(missionDesc) end end -local function onUpdateQuestTracker(missionId, missionName, questIsCompleted, missionDesc) +local function onUpdateQuestTracker(questId, missionId, questName, missionName, missionDesc) -- untest - -- print(missionId, missionName, questIsCompleted, missionDesc) + -- print(questId, missionId, questName, missionName, missionDesc) local trackerLabel = trackerMiniWindow.contentsPanel.list:getChildById(missionId) if trackerLabel then trackerLabel.description:setText(missionDesc) diff --git a/modules/game_skills/skills.lua b/modules/game_skills/skills.lua index 5921190b12..eaf7cf672f 100644 --- a/modules/game_skills/skills.lua +++ b/modules/game_skills/skills.lua @@ -1,6 +1,7 @@ skillsWindow = nil skillsButton = nil skillsSettings = nil +local ExpRating = {} function init() connect(LocalPlayer, { @@ -19,7 +20,15 @@ function init() onMagicLevelChange = onMagicLevelChange, onBaseMagicLevelChange = onBaseMagicLevelChange, onSkillChange = onSkillChange, - onBaseSkillChange = onBaseSkillChange + onBaseSkillChange = onBaseSkillChange, + onFlatDamageHealingChange = onFlatDamageHealingChange, + onAttackInfoChange = onAttackInfoChange, + onConvertedDamageChange = onConvertedDamageChange, + onImbuementsChange = onImbuementsChange, + onDefenseInfoChange = onDefenseInfoChange, + onCombatAbsorbValuesChange = onCombatAbsorbValuesChange, + onForgeBonusesChange = onForgeBonusesChange, + onExperienceRateChange = onExperienceRateChange }) connect(g_game, { onGameStart = online, @@ -68,7 +77,15 @@ function terminate() onMagicLevelChange = onMagicLevelChange, onBaseMagicLevelChange = onBaseMagicLevelChange, onSkillChange = onSkillChange, - onBaseSkillChange = onBaseSkillChange + onBaseSkillChange = onBaseSkillChange, + onFlatDamageHealingChange = onFlatDamageHealingChange, + onAttackInfoChange = onAttackInfoChange, + onConvertedDamageChange = onConvertedDamageChange, + onImbuementsChange = onImbuementsChange, + onDefenseInfoChange = onDefenseInfoChange, + onCombatAbsorbValuesChange = onCombatAbsorbValuesChange, + onForgeBonusesChange = onForgeBonusesChange, + onExperienceRateChange = onExperienceRateChange }) disconnect(g_game, { onGameStart = online, @@ -230,6 +247,15 @@ function update() else regenerationTime:show() end + local xpBoostButton = skillsWindow:recursiveGetChildById('xpBoostButton') + local xpGainRate = skillsWindow:recursiveGetChildById('xpGainRate') + if g_game.getFeature(GameExperienceBonus) then + xpBoostButton:show() + xpGainRate:show() + else + xpBoostButton:hide() + xpGainRate:hide() + end end function online() @@ -271,22 +297,21 @@ function refresh() local ativedAdditionalSkills = hasAdditionalSkills if ativedAdditionalSkills then if g_game.getClientVersion() >= 1281 then - if i == Skill.LifeLeechAmount or i == Skill.ManaLeechAmount then + if i == Skill.LifeLeechAmount or i == Skill.ManaLeechAmount then ativedAdditionalSkills = false elseif g_game.getClientVersion() < 1332 and Skill.Transcendence then ativedAdditionalSkills = false elseif i >= Skill.Fatal and player:getSkillLevel(i) <= 0 then ativedAdditionalSkills = false end - elseif g_game.getClientVersion() < 1281 and i >= Skill.Fatal then + elseif g_game.getClientVersion() < 1281 and i >= Skill.Fatal then ativedAdditionalSkills = false - end + end end - toggleSkill('skillId' .. i, ativedAdditionalSkills) end end - +-- todo reload skills 14.12 update() updateHeight() end @@ -435,12 +460,12 @@ function onLevelChange(localPlayer, value, percent) end function onHealthChange(localPlayer, health, maxHealth) - setSkillValue('health', health) + setSkillValue('health', comma_value(health)) checkAlert('health', health, maxHealth, 30) end function onManaChange(localPlayer, mana, maxMana) - setSkillValue('mana', mana) + setSkillValue('mana', comma_value(mana)) checkAlert('mana', mana, maxMana, 30) end @@ -449,7 +474,7 @@ function onSoulChange(localPlayer, soul) end function onFreeCapacityChange(localPlayer, freeCapacity) - setSkillValue('capacity', freeCapacity) + setSkillValue('capacity', comma_value(freeCapacity)) checkAlert('capacity', freeCapacity, localPlayer:getTotalCapacity(), 20) end @@ -540,7 +565,7 @@ function onRegenerationChange(localPlayer, regenerationTime) end function onSpeedChange(localPlayer, speed) - setSkillValue('speed', speed) + setSkillValue('speed', comma_value(speed)) onBaseSpeedChange(localPlayer, localPlayer:getBaseSpeed()) end @@ -574,3 +599,199 @@ end function onBaseSkillChange(localPlayer, id, baseLevel) setSkillBase('skillId' .. id, localPlayer:getSkillLevel(id), baseLevel) end + +-- 14.12 +local function updateExperienceRate(localPlayer) + local baseRate = ExpRating[ExperienceRate.BASE] or 100 + local expRateTotal = baseRate + + for type, value in pairs(ExpRating) do + if type ~= ExperienceRate.BASE and type ~= ExperienceRate.STAMINA_MULTIPLIER then + expRateTotal = expRateTotal + (value or 0) + end + end + + local staminaMultiplier = ExpRating[ExperienceRate.STAMINA_MULTIPLIER] or 100 + expRateTotal = expRateTotal * staminaMultiplier / 100 + + local xpgainrate = skillsWindow:recursiveGetChildById("xpGainRate") + if not xpgainrate then + return + end + + local widget = xpgainrate:getChildById("value") + if not widget then + return + end + + widget:setText(math.floor(expRateTotal) .. "%") + + local tooltip = string.format("Your current XP gain rate amounts to %d%%.", math.floor(expRateTotal)) + tooltip = tooltip .. + string.format("\nYour XP gain rate is calculated as follows:\n- Base XP gain rate %d%%", baseRate) + + if (ExpRating[ExperienceRate.VOUCHER] or 0) > 0 then + tooltip = tooltip .. string.format("\n- Voucher: %d%%", ExpRating[ExperienceRate.VOUCHER]) + end + + if (ExpRating[ExperienceRate.XP_BOOST] or 0) > 0 then + tooltip = tooltip .. string.format("\n- XP Boost: %d%% (%s h remaining)", ExpRating[ExperienceRate.XP_BOOST], + formatTimeBySeconds(localPlayer:getStoreExpBoostTime())) + end + tooltip = tooltip .. string.format("\n- Stamina multiplier: x%.1f (%s h remaining)", staminaMultiplier / 100, + formatTimeByMinutes(localPlayer:getStamina() - 2340)) + + xpgainrate:setTooltip(tooltip) + + if expRateTotal == 0 then + widget:setColor("#ff4a4a") + elseif expRateTotal > 100 then + widget:setColor("#00cc00") + elseif expRateTotal < 100 then + widget:setColor("#ff9429") + else + widget:setColor("#ffffff") + end +end + +function onExperienceRateChange(localPlayer, type, value) + ExpRating[type] = value + updateExperienceRate(localPlayer) +end + +local function setSkillValueWithTooltips(id, value, tooltip, showPercentage, color) + local skill = skillsWindow:recursiveGetChildById(id) + if not skill then + return + end + if value and value ~= 0 then + skill:show() + local widget = skill:getChildById('value') + if not widget then + return + end + if color then + widget:setColor(color) + end + if showPercentage then + local percentValue = math.floor(value * 10000) / 100 + local sign = percentValue > 0 and "+ " or "" + widget:setText(sign .. percentValue .. "%") + if percentValue < 0 then + widget:setColor("#FF9854") + end + else + widget:setText(tostring(value)) + end + if tooltip then + skill:setTooltip(tooltip) + end + else + skill:hide() + end +end + +function onFlatDamageHealingChange(localPlayer, flatBonus) + local tooltips = + "This flat bonus is the main source of your character's power, added \nto most of the damage and healing values you cause." + setSkillValueWithTooltips('damageHealing', flatBonus, tooltips, false) +end + +function onAttackInfoChange(localPlayer, attackValue, attackElement) + local tooltips = + "This is your character's basic attack power whenever you enter a \nfight with a weapon or your fists. It does not apply to any spells \nyou cast. The attack value is calculated from the weapon's attack\n value, the corresponding weapon skill, combat tactics, the bonus \nreceived from the Revelation Perks and the player's level. The \nvalue represents the average damage you would inflict on a\ncreature which had no kind of defence or protection." + setSkillValueWithTooltips('attackValue', attackValue, tooltips, false) + local skill = skillsWindow:recursiveGetChildById("attackValue") + if skill then + local element = clientCombat[attackElement] + if element then + skill:getChildById('icon'):setImageSource(element.path) + skill:getChildById('icon'):setImageSize({ + width = 9, + height = 9 + }) + end + end +end + +function onConvertedDamageChange(localPlayer, convertedDamage, convertedElement) + setSkillValueWithTooltips('convertedDamage', convertedDamage, false, true) + setSkillValueWithTooltips('convertedElement', convertedElement, false, true) +end + +function onImbuementsChange(localPlayer, lifeLeech, manaLeech, critChance, critDamage, onslaught) + local lifeLeechTooltips = + "You have a +11.4% chance to trigger Onslaught, granting you 60% increased damage for all attacks." + local manaLeechTooltips = "You have a +1% chance to cause +1% extra damage." + local critChanceTooltips = + "Critical Hits deal more damage than normal attacks. They have a chance to be \ntriggered during combat, inflicting additional damage beyond the standard amount." + local critDamageTooltips = "You get +1% of the damage dealt as mana" + local onslaughtTooltips = "You get +1% of the damage dealt as hit points" + skillsWindow:recursiveGetChildById("criticalHit"):setVisible(true) + setSkillValueWithTooltips('lifeLeech', lifeLeech, lifeLeechTooltips, true) + setSkillValueWithTooltips('manaLeech', manaLeech, manaLeechTooltips, true) + setSkillValueWithTooltips('criticalChance', critChance, critChanceTooltips, true) + setSkillValueWithTooltips('criticalExtraDamage', critDamage, critDamageTooltips, true) + setSkillValueWithTooltips('onslaught', onslaught, onslaughtTooltips, true) +end + +local combatIdToWidgetId = { + [0] = "physicalResist", + [1] = "fireResist", + [2] = "earthResist", + [3] = "energyResist", + [4] = "IceResist", + [5] = "HolyResist", + [6] = "deathResist", + [7] = "HealingResist", + [8] = "drowResist", + [9] = "lifedrainResist", + [10] = "manadRainResist" +} + +function onCombatAbsorbValuesChange(localPlayer, absorbValues) + for id, widgetId in pairs(combatIdToWidgetId) do + local skill = skillsWindow:recursiveGetChildById(widgetId) + if skill then + local value = absorbValues[id] + if value then + setSkillValueWithTooltips(widgetId, value, false, true, "#44AD25") + else + skill:hide() + end + end + end +end +function onDefenseInfoChange(localPlayer, defense, armor, mitigation, dodge, damageReflection) + skillsWindow:recursiveGetChildById("separadorOnDefenseInfoChange"):setVisible(true) + local defenseToolstip = + "When attacked, you have a +9.6% chance to trigger Dodge, which \nwill fully mitigate the damage." + local armorToolstip = + "Mitigation reduces most of the damage you take and varies based\non your shielding skill, equipped weapon, chosen combat tactics \nand any mitigation multipliers acquired in your Wheel of Destiny." + local mitigationToolstip = "This shows how well your armor protects you from all physical\nattacks." + local dodgetToolstip = + "This is your protection against all physical attacks in close combat \nas well as all distance physical attacks. The higher the defence value, the less damage you will take from melee physical hits. The defence\n value is calculated from your shield and/or weapon\n defence and the corresponding skill. Careful! \nYour defence value protects you only from hits of two creatures in a single round." + setSkillValueWithTooltips('defenceValue', defense, defenseToolstip, false) + setSkillValueWithTooltips('armorValue', armor, armorToolstip, false) + setSkillValueWithTooltips('mitigation', mitigation, mitigationToolstip, true) + setSkillValueWithTooltips('dodge', dodge, dodgetToolstip, true) + setSkillValueWithTooltips('damageReflection', damageReflection, false, true) + +end + +function onForgeBonusesChange(localPlayer, momentum, transcendence, amplification) + skillsWindow:recursiveGetChildById("separadorOnForgeBonusesChange"):setVisible(true) + local momentumTooltip = "During combat, you have a +" .. math.floor(momentum * 10000) / 100 .. + "% chance to trigger Momentum\n, which reduces all spell cooldowns by 2 seconds." + + local transcendenceTooltip = "During combat, you have a +" .. math.floor(transcendence * 10000) / 100 .. + "% chance to trigger\nTranscendence, which transforms your character into a vocation-\nspecific avatar for 7 seconds. " .. + "While in this form, you will benefit\nfrom a 15% damage reduction and guaranteed critical hits that \ndeal an additional 15% damage." + + local amplificationTooltip = + "Effects of tiered items are amplified by +" .. math.floor(amplification * 10000) / 100 .. "%." + + setSkillValueWithTooltips('momentum', momentum, momentumTooltip, true) + setSkillValueWithTooltips('transcendence', transcendence, transcendenceTooltip, true) + setSkillValueWithTooltips('amplification', amplification, amplificationTooltip, true) +end diff --git a/modules/game_skills/skills.otui b/modules/game_skills/skills.otui index eb66b77821..3d9b162e02 100644 --- a/modules/game_skills/skills.otui +++ b/modules/game_skills/skills.otui @@ -13,6 +13,7 @@ SkillNameLabel < GameLabel anchors.left: parent.left anchors.top: parent.top anchors.bottom: parent.bottom + color: #C0C0C0 SkillValueLabel < GameLabel id: value @@ -22,6 +23,7 @@ SkillValueLabel < GameLabel anchors.top: parent.top anchors.bottom: parent.bottom anchors.left: prev.left + color: #C0C0C0 ImageSkill < UIWidget id: icon @@ -32,6 +34,15 @@ ImageSkill < UIWidget anchors.top: parent.top phantom: false +ImageAbsorb < UIWidget + id: icon + size: 9 9 + width: 9 + margin-top: 2 + anchors.right: parent.right + anchors.top: parent.top + phantom: false + RedPercentPanel < ProgressBar id: percent background-color: red @@ -53,6 +64,27 @@ SkillPercentPanel < ProgressBar anchors.top: parent.top phantom: false +XpBoostButton < UIWidget + height: 30 + HorizontalSeparator + id: leftSeparator + anchors.top: parent.bottom + anchors.left: parent.left + anchors.right: parent.right + margin-top: -23 + margin-left: 5 + UIWidget + id: xpBoostButton + image-source: /images/ui/button-storexp + anchors.horizontalCenter: parent.horizontalCenter + anchors.top: prev.bottom + margin-top : -7 + @onClick: | + modules.game_store.toggle() + g_game.sendRequestStorePremiumBoost() + $pressed: + image-source: /images/ui/button-storexp-pressed + MiniWindow id: skillWindow !text: tr('Skills') @@ -69,20 +101,31 @@ MiniWindow SkillButton margin-top: 5 + id: level + SkillNameLabel + !text: tr('Level') + SkillValueLabel + RedPercentPanel + + SkillButton id: experience height: 15 SkillNameLabel - !text: tr('Experience') + !text: tr('XP') SkillValueLabel SkillButton - id: level + id: xpGainRate + height: 15 SkillNameLabel - !text: tr('Level') + !text: tr('XP Gain Rate') SkillValueLabel - RedPercentPanel + + XpBoostButton + id: xpBoos SkillButton + margin-top: -12 id: health height: 15 SkillNameLabel @@ -119,6 +162,7 @@ MiniWindow SkillButton id: regenerationTime + height: 15 SkillNameLabel !text: tr('Regeneration Time') SkillValueLabel @@ -137,6 +181,10 @@ MiniWindow SkillValueLabel RedPercentPanel + HorizontalSeparator + margin-top: 5 + margin-bottom: 5 + SkillButton id: magiclevel SkillNameLabel @@ -281,3 +329,251 @@ MiniWindow SkillNameLabel !text: tr('Transcendence') SkillValueLabel + +// 14.12 + SmallSkillButton + id: damageHealing + visible: false + SkillNameLabel + !text: tr('Damage/Healing') + SkillValueLabel + + SmallSkillButton + id: attackValue + visible: false + SkillNameLabel + !text: tr('Attack Value') + SkillValueLabel + margin-right: 12 + ImageAbsorb + + SmallSkillButton + visible: false + id: convertedDamage + SkillNameLabel + !text: tr('converted Damage') + SkillValueLabel + SmallSkillButton + visible: false + id: convertedElement + SkillNameLabel + !text: tr('converted Element') + SkillValueLabel + + SmallSkillButton + id: lifeLeech + visible: false + SkillNameLabel + !text: tr('Life Leech') + SkillValueLabel + SmallSkillButton + id: manaLeech + visible: false + SkillNameLabel + !text: tr('Mana Leech') + SkillValueLabel + SmallSkillButton + visible: false + id: criticalHit + SkillNameLabel + !text: tr('Critical Hit:') + SmallSkillButton + id: criticalChance + visible: false + SkillNameLabel + margin-left: 15 + !text: tr('Chance') + SkillValueLabel + SmallSkillButton + id: criticalExtraDamage + visible: false + SkillNameLabel + margin-left: 15 + !text: tr('Extra Damage') + SkillValueLabel + + SmallSkillButton + id: onslaught + visible: false + SkillNameLabel + !text: tr('Onslaught') + SkillValueLabel + HorizontalSeparator + id: separadorOnDefenseInfoChange + margin-top: 5 + visible: false + margin-bottom: 5 + + SmallSkillButton + id: physicalResist + visible: false + SkillNameLabel + !text: tr('Physical') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-physical-resist + SmallSkillButton + id: fireResist + visible: false + SkillNameLabel + !text: tr('Fire') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-fire-resist + + SmallSkillButton + id: earthResist + visible: false + SkillNameLabel + !text: tr('Earth') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-earth-resist + SmallSkillButton + id: energyResist + visible: false + SkillNameLabel + !text: tr('Energy') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-energy-resist + SmallSkillButton + id: IceResist + visible: false + SkillNameLabel + !text: tr('Ice') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-ice-resist + SmallSkillButton + id: HolyResist + visible: false + SkillNameLabel + !text: tr('Holy') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-holy-resist + + SmallSkillButton + id: deathResist + visible: false + SkillNameLabel + !text: tr('Death') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-death-resist + SmallSkillButton + id: HealingResist + visible: false + SkillNameLabel + !text: tr('Healing') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-healing-resist + + SmallSkillButton + id: drowResist + visible: false + SkillNameLabel + !text: tr('Drown') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-drowning-resist + SmallSkillButton + id: lifedrainResist + visible: false + SkillNameLabel + !text: tr('Lifedrain') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-lifedrain-resist + SmallSkillButton + id: manadRainResist + visible: false + SkillNameLabel + !text: tr('manadrain') + SkillValueLabel + margin-right: 12 + ImageAbsorb + image-source: /game_cyclopedia/images/bestiary/icons/monster-icon-manadrain-resist + + + + + + SmallSkillButton + id: defenceValue + visible: false + SkillNameLabel + !text: tr('Defence Value') + SkillValueLabel + + SmallSkillButton + id: armorValue + visible: false + SkillNameLabel + !text: tr('Armor Value') + SkillValueLabel + + SmallSkillButton + id: mitigation + visible: false + SkillNameLabel + !text: tr('Mitigation') + SkillValueLabel + SmallSkillButton + id: dodge + visible: false + SkillNameLabel + !text: tr('Dodge') + SkillValueLabel + SmallSkillButton + id: damageReflection + visible: false + SkillNameLabel + !text: tr('Damage Reflection') + SkillValueLabel + + HorizontalSeparator + id: separadorOnForgeBonusesChange + visible: false + margin-top: 5 + margin-bottom: 5 + + SmallSkillButton + id: momentum + visible: false + SkillNameLabel + !text: tr('Momentum') + SkillValueLabel + SmallSkillButton + id: transcendence + visible: false + SkillNameLabel + !text: tr('Transcendence') + SkillValueLabel + SmallSkillButton + id: amplification + visible: false + SkillNameLabel + !text: tr('Amplification') + SkillValueLabel + + UIWidget + id: miniborder + anchors.bottom: parent.bottom + anchors.left: parent.left + image-source: /images/ui/miniborder + size: 14 14 + margin-left: 2 + margin-bottom: 2 \ No newline at end of file diff --git a/modules/gamelib/const.lua b/modules/gamelib/const.lua index dd167145f9..b21ef4619b 100644 --- a/modules/gamelib/const.lua +++ b/modules/gamelib/const.lua @@ -218,6 +218,8 @@ GameAllowPreWalk = 122 GamePlayerFamiliars = 123 GameLatencyAdaptiveCamera = 124 GameMapCache = 125 +GameForgeSkillStats = 126 +GameCharacterSkillStats = 127 TextColors = { red = '#f55e5e', -- '#c83200' @@ -417,10 +419,22 @@ ChannelEvent = { ResourceTypes = { BANK_BALANCE = 0, GOLD_EQUIPPED = 1, + CURRENCY_CUSTOM_EQUIPPED = 2, PREY_WILDCARDS = 10, DAILYREWARD_STREAK = 20, DAILYREWARD_JOKERS = 21, + CHARM = 30, + MINOR_CHARM = 31, + MAX_CHARM = 32, + MAX_MINOR_CHARM = 33, TASK_HUNTING = 50, + FORGE_DUST = 70, + FORGE_SLIVER = 71, + FORGE_CORES = 72, + LESSER_GEMS = 81, + REGULAR_GEMS = 82, + GREATER_GEMS = 83, + WHEEL_OF_DESTINY = 86, COIN_NORMAL = 90, COIN_TRANSFERRABLE = 91, COIN_AUCTION = 92, @@ -439,10 +453,22 @@ CyclopediaCharacterInfoTypes = { StoreSummary = 8, Ispection = 9, Badges = 10, - Titles = 11 + Titles = 11, + Wheel = 12, + Offencestats = 13, + Defencestats = 14, + Miscstats = 15 } StoreConst = { InstantRewardAccess = 233, } + +ExperienceRate = { + BASE = 0, + VOUCHER = 1, + LOW_LEVEL = 2, + XP_BOOST = 3, + STAMINA_MULTIPLIER = 4 +} -- @} diff --git a/modules/gamelib/game.lua b/modules/gamelib/game.lua index db48a69931..dff27eca02 100644 --- a/modules/gamelib/game.lua +++ b/modules/gamelib/game.lua @@ -58,7 +58,7 @@ function g_game.getSupportedClients() 1012, 1013, 1020, 1021, 1022, 1030, 1031, 1032, 1033, 1034, 1035, 1036, 1037, 1038, 1039, 1040, 1041, 1050, 1051, 1052, 1053, 1054, 1055, 1056, 1057, 1058, 1059, 1060, 1061, 1062, 1063, 1064, 1070, 1071, 1072, 1073, 1074, 1075, 1076, 1080, 1081, 1082, 1090, 1091, 1092, 1093, 1094, 1095, 1096, 1097, 1098, 1099, 1100, 1281, 1285, - 1286, 1287, 1291, 1300, 1310, 1311, 1314, 1316, 1320, 1321, 1322, 1332, 1334, 1336, 1337, 1340} + 1286, 1287, 1291, 1300, 1310, 1311, 1314, 1316, 1320, 1321, 1322, 1332, 1334, 1336, 1337, 1340, 1400, 1405, 1410, 1412} end -- The client version and protocol version where diff --git a/modules/gamelib/player.lua b/modules/gamelib/player.lua index 66938f5f45..aff91e1f0b 100644 --- a/modules/gamelib/player.lua +++ b/modules/gamelib/player.lua @@ -69,6 +69,32 @@ Icons[PlayerStates.Agony] = { clip = 28, tooltip = tr('You are Agony'), id = 'c Icons[PlayerStates.Rewards] = { clip = 30, tooltip = tr('Rewards'), id = 'condition_Rewards' } Icons[PlayerStates.Hungry] = { clip = 32, tooltip = tr('You are hungry'), id = 'condition_hungry' } +combatStates= { + CLIENT_COMBAT_PHYSICAL = 0, + CLIENT_COMBAT_FIRE = 1, + CLIENT_COMBAT_EARTH = 2, + CLIENT_COMBAT_ENERGY = 3, + CLIENT_COMBAT_ICE = 4, + CLIENT_COMBAT_HOLY = 5, + CLIENT_COMBAT_DEATH = 6, + CLIENT_COMBAT_HEALING = 7, + CLIENT_COMBAT_DROWN = 8, + CLIENT_COMBAT_LIFEDRAIN = 9, + CLIENT_COMBAT_MANADRAIN = 10, +} +clientCombat ={} +clientCombat[combatStates.CLIENT_COMBAT_PHYSICAL] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-physical-resist', id = 'Physical' } +clientCombat[combatStates.CLIENT_COMBAT_FIRE] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-fire-resist', id = 'Fire' } +clientCombat[combatStates.CLIENT_COMBAT_EARTH] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-earth-resist', id = 'Earth' } +clientCombat[combatStates.CLIENT_COMBAT_ENERGY] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-energy-resist', id = 'Energy' } +clientCombat[combatStates.CLIENT_COMBAT_ICE] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-ice-resist', id = 'Ice' } +clientCombat[combatStates.CLIENT_COMBAT_HOLY] = {path = '/game_cyclopedia/images/bestiary/icons/monster-icon-holy-resist', id = 'Holy' } +clientCombat[combatStates.CLIENT_COMBAT_DEATH] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-death-resist', id = 'Death' } +clientCombat[combatStates.CLIENT_COMBAT_HEALING] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-healing-resist', id = 'Healing' } +clientCombat[combatStates.CLIENT_COMBAT_DROWN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-drowning-resist', id = 'Drown' } +clientCombat[combatStates.CLIENT_COMBAT_LIFEDRAIN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-lifedrain-resist', id = 'Lifedrain ' } +clientCombat[combatStates.CLIENT_COMBAT_MANADRAIN] = { path = '/game_cyclopedia/images/bestiary/icons/monster-icon-manadrain-resist', id = 'Manadrain' } + InventorySlotOther = 0 InventorySlotHead = 1 InventorySlotNeck = 2 diff --git a/modules/gamelib/util.lua b/modules/gamelib/util.lua index 1bfb2bbba8..ec778ba2c3 100644 --- a/modules/gamelib/util.lua +++ b/modules/gamelib/util.lua @@ -14,3 +14,18 @@ function comma_value(n) local left, num, right = string.match(n, '^([^%d]*%d)(%d*)(.-)$') return left .. (num:reverse():gsub('(%d%d%d)', '%1,'):reverse()) .. right end + +function formatTimeBySeconds(totalSeconds) + local hours = math.floor(totalSeconds / 3600) + local remainingSeconds = totalSeconds % 3600 + local minutes = math.floor(remainingSeconds / 60) + return string.format("%02d:%02d", hours, minutes) +end + +function formatTimeByMinutes(totalMinutes) + local totalSeconds = totalMinutes * 60 + local hours = math.floor(totalSeconds / 3600) + local remainingSeconds = totalSeconds % 3600 + local minutes = math.floor(remainingSeconds / 60) + return string.format("%02d:%02d", hours, minutes) +end diff --git a/src/client/const.h b/src/client/const.h index d44fdf978f..87bdfcdfa3 100644 --- a/src/client/const.h +++ b/src/client/const.h @@ -560,6 +560,8 @@ namespace Otc GamePlayerFamiliars = 123, GameLatencyAdaptiveCamera = 124, GameMapCache = 125, + GameForgeSkillStats = 126, + GameCharacterSkillStats = 127, LastGameFeature }; @@ -673,6 +675,10 @@ namespace Otc RESOURCE_PREY_WILDCARDS = 10, RESOURCE_DAILYREWARD_STREAK = 20, RESOURCE_DAILYREWARD_JOKERS = 21, + RESOURCE_CHARM = 30, + RESOURCE_MINOR_CHARM = 31, + RESOURCE_MAX_CHARM = 32, + RESOURCE_MAX_MINOR_CHARM = 33, RESOURCE_TASK_HUNTING = 50, RESOURCE_FORGE_DUST = 70, RESOURCE_FORGE_SLIVER = 71, @@ -687,6 +693,15 @@ namespace Otc RESOURE_COIN_TOURNAMENT = 93, }; + enum ExperienceRate_t : uint8_t + { + EXP_BASE = 0, + EXP_VOUCHER = 1, + EXP_LOWLEVEL = 2, + EXP_XPBOOST = 3, + EXP_STANINAMULTIPLIER = 4 + }; + enum MarketItemDescription : uint8_t { ITEM_DESC_ARMOR = 1, @@ -783,7 +798,11 @@ namespace Otc CYCLOPEDIA_CHARACTERINFO_STORESUMMARY = 8, CYCLOPEDIA_CHARACTERINFO_INSPECTION = 9, CYCLOPEDIA_CHARACTERINFO_BADGES = 10, - CYCLOPEDIA_CHARACTERINFO_TITLES = 11 + CYCLOPEDIA_CHARACTERINFO_TITLES = 11, + CYCLOPEDIA_CHARACTERINFO_WHEEL = 12, + CYCLOPEDIA_CHARACTERINFO_OFFENCESTATS = 13, + CYCLOPEDIA_CHARACTERINFO_DEFENCESTATS = 14, + CYCLOPEDIA_CHARACTERINFO_MISCSTATS = 15 }; enum InspectObjectTypes : uint8_t diff --git a/src/client/game.cpp b/src/client/game.cpp index d6af7392b4..52974b26ec 100644 --- a/src/client/game.cpp +++ b/src/client/game.cpp @@ -507,11 +507,6 @@ void Game::processItemDetail(const uint32_t itemId, const std::vector& bestiaryRaces) -{ - g_lua.callGlobalField("g_game", "onParseBestiaryRaces", bestiaryRaces); -} - void Game::processCyclopediaCharacterGeneralStats(const CyclopediaCharacterGeneralStats& stats, const std::vector>& skills, const std::vector>& combats) { @@ -1999,3 +1994,19 @@ void Game::sendRequestTrackerQuestLog(const std::map& que m_protocolGame->sendRequestTrackerQuestLog(quests); } + +void Game::processCyclopediaCharacterOffenceStats(const CyclopediaCharacterOffenceStats& data) +{ + g_lua.callGlobalField("g_game", "onCyclopediaCharacterOffenceStats", data); +} + +void Game::processCyclopediaCharacterDefenceStats(const CyclopediaCharacterDefenceStats& data) +{ + g_lua.callGlobalField("g_game", "onCyclopediaCharacterDefenceStats", data); +} + +void Game::processCyclopediaCharacterMiscStats(const CyclopediaCharacterMiscStats& data) +{ + g_lua.callGlobalField("g_game", "onCyclopediaCharacterMiscStats", data); +} + diff --git a/src/client/game.h b/src/client/game.h index a43501c104..c5ebd1de3c 100644 --- a/src/client/game.h +++ b/src/client/game.h @@ -241,10 +241,15 @@ struct CharmData bool asignedStatus; uint16_t raceId; uint32_t removeRuneCost; + uint8_t availableCharmSlots; + uint8_t tier; }; struct BestiaryCharmsData { + uint64_t resetAllCharmsCost; + uint8_t availableCharmSlots; + uint32_t points; std::vector charms; std::vector finishedMonsters; @@ -450,6 +455,111 @@ struct DailyRewardData uint8_t maxUnlockableDragons; }; +struct CyclopediaCharacterOffenceStats +{ + double critChance; + double critDamage; + double critDamageBase; + double critDamageImbuement; + double critDamageWheel; + + double lifeLeech; + double lifeLeechBase; + double lifeLeechImbuement; + double lifeLeechWheel; + + double manaLeech; + double manaLeechBase; + double manaLeechImbuement; + double manaLeechWheel; + + double onslaught; + double onslaughtBase; + double onslaughtBonus; + + double cleavePercent; + + std::vector perfectShotDamage; + + uint16_t flatDamage; + uint16_t flatDamageBase; + + uint16_t weaponAttack; + uint16_t weaponFlatModifier; + uint16_t weaponDamage; + uint8_t weaponSkillType; + uint16_t weaponSkillLevel; + uint16_t weaponSkillModifier; + uint8_t weaponElement; + double weaponElementDamage; + uint8_t weaponElementType; + std::vector weaponAccuracy; +}; + +struct CyclopediaCharacterDefenceStats +{ + double dodgeTotal; + double dodgeBase; + double dodgeBonus; + double dodgeWheel; + + uint32_t magicShieldCapacity; + uint16_t magicShieldCapacityFlat; + double magicShieldCapacityPercent; + + uint16_t reflectPhysical; + uint16_t armor; + + uint16_t defense; + uint16_t defenseEquipment; + uint8_t defenseSkillType; + uint16_t shieldingSkill; + uint16_t defenseWheel; + + double mitigation; + double mitigationBase; + double mitigationEquipment; + double mitigationShield; + double mitigationWheel; + double mitigationCombatTactics; + + struct ElementalResistance + { + uint8_t element; + double value; + }; + + std::vector resistances; +}; + +struct CyclopediaCharacterMiscStats +{ + double momentumTotal; + double momentumBase; + double momentumBonus; + double momentumWheel; + + double dodgeTotal; + double dodgeBase; + double dodgeBonus; + double dodgeWheel; + + double damageReflectionTotal; + double damageReflectionBase; + double damageReflectionBonus; + + uint8_t haveBlesses; + uint8_t totalBlesses; + + struct Concoction + { + uint16_t id; + uint32_t duration; + }; + + std::vector concoctions; +}; + //@bindsingleton g_game class Game { @@ -555,7 +665,6 @@ class Game // cyclopedia static void processItemDetail(uint32_t itemId, const std::vector>& descriptions); - static void processBestiaryRaces(const std::vector& bestiaryRaces); static void processCyclopediaCharacterGeneralStats(const CyclopediaCharacterGeneralStats& stats, const std::vector>& skills, const std::vector>& combats); static void processCyclopediaCharacterCombatStats(const CyclopediaCharacterCombatStats& data, double mitigation, @@ -841,6 +950,9 @@ class Game void requestOpenRewardHistory(); void requestGetRewardDaily(const uint8_t bonusShrine, const std::map& items); void sendRequestTrackerQuestLog(const std::map& quests); + void processCyclopediaCharacterOffenceStats(const CyclopediaCharacterOffenceStats& data); + void processCyclopediaCharacterDefenceStats(const CyclopediaCharacterDefenceStats& data); + void processCyclopediaCharacterMiscStats(const CyclopediaCharacterMiscStats& data); void updateMapLatency() { if (!m_mapUpdateTimer.first) { diff --git a/src/client/localplayer.cpp b/src/client/localplayer.cpp index 7c49f4c10c..046ac3926b 100644 --- a/src/client/localplayer.cpp +++ b/src/client/localplayer.cpp @@ -514,4 +514,129 @@ void LocalPlayer::setResourceBalance(const Otc::ResourceTypes_t type, const uint bool LocalPlayer::hasSight(const Position& pos) { return m_position.isInRange(pos, g_map.getAwareRange().left - 1, g_map.getAwareRange().top - 1); -} \ No newline at end of file +} + +void LocalPlayer::setFlatDamageHealing(uint16_t flatBonus) +{ + if (m_flatDamageHealing == flatBonus) + return; + + const uint16_t oldFlatBonus = m_flatDamageHealing; + m_flatDamageHealing = flatBonus; + + callLuaField("onFlatDamageHealingChange", flatBonus); +} + +void LocalPlayer::setAttackInfo(uint16_t attackValue, uint8_t attackElement) +{ + if (m_attackValue == attackValue && m_attackElement == attackElement) + return; + + const uint16_t oldAttackValue = m_attackValue; + const uint8_t oldAttackElement = m_attackElement; + m_attackValue = attackValue; + m_attackElement = attackElement; + + callLuaField("onAttackInfoChange", attackValue, attackElement); +} + +void LocalPlayer::setConvertedDamage(double convertedDamage, uint8_t convertedElement) +{ + if (m_convertedDamage == convertedDamage && m_convertedElement == convertedElement) + return; + + const double oldConvertedDamage = m_convertedDamage; + const uint8_t oldConvertedElement = m_convertedElement; + m_convertedDamage = convertedDamage; + m_convertedElement = convertedElement; + + callLuaField("onConvertedDamageChange", convertedDamage, convertedElement); +} + +void LocalPlayer::setImbuements(double lifeLeech, double manaLeech, double critChance, double critDamage, double onslaught) +{ + if (m_lifeLeech == lifeLeech && m_manaLeech == manaLeech && m_critChance == critChance && + m_critDamage == critDamage && m_onslaught == onslaught) + return; + + const double oldLifeLeech = m_lifeLeech; + const double oldManaLeech = m_manaLeech; + const double oldCritChance = m_critChance; + const double oldCritDamage = m_critDamage; + const double oldOnslaught = m_onslaught; + + m_lifeLeech = lifeLeech; + m_manaLeech = manaLeech; + m_critChance = critChance; + m_critDamage = critDamage; + m_onslaught = onslaught; + + callLuaField("onImbuementsChange", lifeLeech, manaLeech, critChance, critDamage, onslaught); +} + +void LocalPlayer::setDefenseInfo(uint16_t defense, uint16_t armor, double mitigation, double dodge, uint16_t damageReflection) +{ + if (m_defense == defense && m_armor == armor && m_mitigation == mitigation && + m_dodge == dodge && m_damageReflection == damageReflection) + return; + + const uint16_t oldDefense = m_defense; + const uint16_t oldArmor = m_armor; + const double oldMitigation = m_mitigation; + const double oldDodge = m_dodge; + const uint16_t oldDamageReflection = m_damageReflection; + + m_defense = defense; + m_armor = armor; + m_mitigation = mitigation; + m_dodge = dodge; + m_damageReflection = damageReflection; + + callLuaField("onDefenseInfoChange", defense, armor, mitigation, dodge, damageReflection); +} + +void LocalPlayer::setCombatAbsorbValues(const std::map& absorbValues) +{ + if (m_combatAbsorbValues == absorbValues) + return; + + const auto oldAbsorbValues = m_combatAbsorbValues; + m_combatAbsorbValues = absorbValues; + + callLuaField("onCombatAbsorbValuesChange", absorbValues); +} + +void LocalPlayer::setForgeBonuses(double momentum, double transcendence, double amplification) +{ + if (m_momentum == momentum && m_transcendence == transcendence && m_amplification == amplification) + return; + + const double oldMomentum = m_momentum; + const double oldTranscendence = m_transcendence; + const double oldAmplification = m_amplification; + + m_momentum = momentum; + m_transcendence = transcendence; + m_amplification = amplification; + + callLuaField("onForgeBonusesChange", momentum, transcendence, amplification); +} + +void LocalPlayer::setExperienceRate(Otc::ExperienceRate_t type, uint16_t value) +{ + if (m_experienceRates[type] == value) + return; + + const uint16_t oldValue = m_experienceRates[type]; + m_experienceRates[type] = value; + + callLuaField("onExperienceRateChange", type, value); +} + +void LocalPlayer::setStoreExpBoostTime(uint16_t value) +{ + if (m_storeExpBoostTime == value) + return; + + m_storeExpBoostTime = value; +} diff --git a/src/client/localplayer.h b/src/client/localplayer.h index ef6a30eaa3..f364cbdb5f 100644 --- a/src/client/localplayer.h +++ b/src/client/localplayer.h @@ -60,6 +60,15 @@ class LocalPlayer final : public Player void setBlessings(uint16_t blessings); void setResourceBalance(Otc::ResourceTypes_t type, uint64_t value); void takeScreenshot(uint8_t type); + void setFlatDamageHealing(uint16_t flatBonus); + void setAttackInfo(uint16_t attackValue, uint8_t attackElement); + void setConvertedDamage(double convertedDamage, uint8_t convertedElement); + void setImbuements(double lifeLeech, double manaLeech, double critChance, double critDamage, double onslaught); + void setDefenseInfo(uint16_t defense, uint16_t armor, double mitigation, double dodge, uint16_t damageReflection); + void setCombatAbsorbValues(const std::map& absorbValues); + void setForgeBonuses(double momentum, double transcendence, double amplification); + void setExperienceRate(Otc::ExperienceRate_t type, uint16_t value); + void setStoreExpBoostTime(uint16_t value); uint32_t getFreeCapacity() { return m_freeCapacity; } uint32_t getTotalCapacity() { return m_totalCapacity; } @@ -79,6 +88,7 @@ class LocalPlayer final : public Player uint16_t getBlessings() { return m_blessings; } uint16_t getRegenerationTime() { return m_regenerationTime; } uint16_t getOfflineTrainingTime() { return m_offlineTrainingTime; } + uint16_t getStoreExpBoostTime() { return m_offlineTrainingTime; } uint32_t getStates() { return m_states; } uint32_t getMana() { return m_mana; } @@ -161,6 +171,8 @@ class LocalPlayer final : public Player std::vector m_spells; stdext::map m_resourcesBalance; + std::map m_combatAbsorbValues; + std::map m_experienceRates; uint8_t m_autoWalkRetries{ 0 }; @@ -185,6 +197,28 @@ class LocalPlayer final : public Player uint16_t m_stamina{ 0 }; uint16_t m_regenerationTime{ 0 }; uint16_t m_offlineTrainingTime{ 0 }; + uint16_t m_storeExpBoostTime{ 0 }; + + uint8_t m_attackElement{ 0 }; + uint8_t m_convertedElement{ 0 }; + + uint16_t m_flatDamageHealing{ 0 }; + uint16_t m_attackValue{ 0 }; + uint16_t m_defense{ 0 }; + uint16_t m_armor{ 0 }; + uint16_t m_damageReflection{ 0 }; + + double m_convertedDamage{ 0 }; + double m_lifeLeech{ 0 }; + double m_manaLeech{ 0 }; + double m_critChance{ 0 }; + double m_critDamage{ 0 }; + double m_onslaught{ 0 }; + double m_mitigation{ 0 }; + double m_dodge{ 0 }; + double m_momentum{ 0 }; + double m_transcendence{ 0 }; + double m_amplification{ 0 }; friend class Game; }; diff --git a/src/client/luafunctions.cpp b/src/client/luafunctions.cpp index f55b51200f..4ba1c823a3 100644 --- a/src/client/luafunctions.cpp +++ b/src/client/luafunctions.cpp @@ -863,6 +863,7 @@ void Client::registerLuaFunctions() g_lua.bindClassMemberFunction("getSoul", &LocalPlayer::getSoul); g_lua.bindClassMemberFunction("getStamina", &LocalPlayer::getStamina); g_lua.bindClassMemberFunction("getOfflineTrainingTime", &LocalPlayer::getOfflineTrainingTime); + g_lua.bindClassMemberFunction("getStoreExpBoostTime", &LocalPlayer::getStoreExpBoostTime); g_lua.bindClassMemberFunction("getRegenerationTime", &LocalPlayer::getRegenerationTime); g_lua.bindClassMemberFunction("getBaseMagicLevel", &LocalPlayer::getBaseMagicLevel); g_lua.bindClassMemberFunction("getTotalCapacity", &LocalPlayer::getTotalCapacity); diff --git a/src/client/luavaluecasts_client.cpp b/src/client/luavaluecasts_client.cpp index e6d9c667bc..58bf5a8754 100644 --- a/src/client/luavaluecasts_client.cpp +++ b/src/client/luavaluecasts_client.cpp @@ -813,7 +813,7 @@ int push_luavalue(const BestiaryMonsterData& data) { } int push_luavalue(const CharmData& charm) { - g_lua.createTable(0, 7); + g_lua.createTable(0, 10); g_lua.pushInteger(charm.id); g_lua.setField("id"); g_lua.pushString(charm.name); @@ -830,6 +830,13 @@ int push_luavalue(const CharmData& charm) { g_lua.setField("raceId"); g_lua.pushInteger(charm.removeRuneCost); g_lua.setField("removeRuneCost"); + //if (g_game.getClientVersion() >= 1410) { + g_lua.pushInteger(charm.availableCharmSlots); + g_lua.setField("availableCharmSlots"); + g_lua.pushInteger(charm.tier); + g_lua.setField("tier"); + // } + return 1; } @@ -1283,3 +1290,243 @@ int push_luavalue(const DailyRewardData& data) { return 1; } + +int push_luavalue(const CyclopediaCharacterOffenceStats& data) +{ + g_lua.createTable(0, 30); + + g_lua.pushNumber(data.critChance); + g_lua.setField("critChance"); + + g_lua.pushNumber(data.critDamage); + g_lua.setField("critDamage"); + + g_lua.pushNumber(data.critDamageBase); + g_lua.setField("critDamageBase"); + + g_lua.pushNumber(data.critDamageImbuement); + g_lua.setField("critDamageImbuement"); + + g_lua.pushNumber(data.critDamageWheel); + g_lua.setField("critDamageWheel"); + + g_lua.pushNumber(data.lifeLeech); + g_lua.setField("lifeLeech"); + + g_lua.pushNumber(data.lifeLeechBase); + g_lua.setField("lifeLeechBase"); + + g_lua.pushNumber(data.lifeLeechImbuement); + g_lua.setField("lifeLeechImbuement"); + + g_lua.pushNumber(data.lifeLeechWheel); + g_lua.setField("lifeLeechWheel"); + + g_lua.pushNumber(data.manaLeech); + g_lua.setField("manaLeech"); + + g_lua.pushNumber(data.manaLeechBase); + g_lua.setField("manaLeechBase"); + + g_lua.pushNumber(data.manaLeechImbuement); + g_lua.setField("manaLeechImbuement"); + + g_lua.pushNumber(data.manaLeechWheel); + g_lua.setField("manaLeechWheel"); + + g_lua.pushNumber(data.onslaught); + g_lua.setField("onslaught"); + + g_lua.pushNumber(data.onslaughtBase); + g_lua.setField("onslaughtBase"); + + g_lua.pushNumber(data.onslaughtBonus); + g_lua.setField("onslaughtBonus"); + + g_lua.pushNumber(data.cleavePercent); + g_lua.setField("cleavePercent"); + + g_lua.createTable(data.perfectShotDamage.size(), 0); + for (size_t i = 0; i < data.perfectShotDamage.size(); ++i) { + g_lua.pushInteger(data.perfectShotDamage[i]); + g_lua.rawSeti(i + 1); + } + g_lua.setField("perfectShotDamage"); + + g_lua.pushInteger(data.flatDamage); + g_lua.setField("flatDamage"); + + g_lua.pushInteger(data.flatDamageBase); + g_lua.setField("flatDamageBase"); + + g_lua.pushInteger(data.weaponAttack); + g_lua.setField("weaponAttack"); + + g_lua.pushInteger(data.weaponFlatModifier); + g_lua.setField("weaponFlatModifier"); + + g_lua.pushInteger(data.weaponDamage); + g_lua.setField("weaponDamage"); + + g_lua.pushInteger(data.weaponSkillType); + g_lua.setField("weaponSkillType"); + + g_lua.pushInteger(data.weaponSkillLevel); + g_lua.setField("weaponSkillLevel"); + + g_lua.pushInteger(data.weaponSkillModifier); + g_lua.setField("weaponSkillModifier"); + + g_lua.pushInteger(data.weaponElement); + g_lua.setField("weaponElement"); + + g_lua.pushNumber(data.weaponElementDamage); + g_lua.setField("weaponElementDamage"); + + g_lua.pushInteger(data.weaponElementType); + g_lua.setField("weaponElementType"); + + g_lua.createTable(data.weaponAccuracy.size(), 0); + for (size_t i = 0; i < data.weaponAccuracy.size(); ++i) { + g_lua.pushNumber(data.weaponAccuracy[i]); + g_lua.rawSeti(i + 1); + } + g_lua.setField("weaponAccuracy"); + + return 1; +} + +int push_luavalue(const CyclopediaCharacterDefenceStats& data) +{ + g_lua.createTable(0, 20); + + g_lua.pushNumber(data.dodgeTotal); + g_lua.setField("dodgeTotal"); + + g_lua.pushNumber(data.dodgeBase); + g_lua.setField("dodgeBase"); + + g_lua.pushNumber(data.dodgeBonus); + g_lua.setField("dodgeBonus"); + + g_lua.pushNumber(data.dodgeWheel); + g_lua.setField("dodgeWheel"); + + g_lua.pushInteger(data.magicShieldCapacity); + g_lua.setField("magicShieldCapacity"); + + g_lua.pushInteger(data.magicShieldCapacityFlat); + g_lua.setField("magicShieldCapacityFlat"); + + g_lua.pushNumber(data.magicShieldCapacityPercent); + g_lua.setField("magicShieldCapacityPercent"); + + g_lua.pushInteger(data.reflectPhysical); + g_lua.setField("reflectPhysical"); + + g_lua.pushInteger(data.armor); + g_lua.setField("armor"); + + g_lua.pushInteger(data.defense); + g_lua.setField("defense"); + + g_lua.pushInteger(data.defenseEquipment); + g_lua.setField("defenseEquipment"); + + g_lua.pushInteger(data.defenseSkillType); + g_lua.setField("defenseSkillType"); + + g_lua.pushInteger(data.shieldingSkill); + g_lua.setField("shieldingSkill"); + + g_lua.pushInteger(data.defenseWheel); + g_lua.setField("defenseWheel"); + + g_lua.pushNumber(data.mitigation); + g_lua.setField("mitigation"); + + g_lua.pushNumber(data.mitigationBase); + g_lua.setField("mitigationBase"); + + g_lua.pushNumber(data.mitigationEquipment); + g_lua.setField("mitigationEquipment"); + + g_lua.pushNumber(data.mitigationShield); + g_lua.setField("mitigationShield"); + + g_lua.pushNumber(data.mitigationWheel); + g_lua.setField("mitigationWheel"); + + g_lua.pushNumber(data.mitigationCombatTactics); + g_lua.setField("mitigationCombatTactics"); + + g_lua.createTable(data.resistances.size(), 0); + for (size_t i = 0; i < data.resistances.size(); ++i) { + g_lua.createTable(0, 2); + g_lua.pushInteger(data.resistances[i].element); + g_lua.setField("element"); + g_lua.pushNumber(data.resistances[i].value); + g_lua.setField("value"); + g_lua.rawSeti(i + 1); + } + g_lua.setField("resistances"); + + return 1; +} + +int push_luavalue(const CyclopediaCharacterMiscStats& data) +{ + g_lua.createTable(0, 14); + + g_lua.pushNumber(data.momentumTotal); + g_lua.setField("momentumTotal"); + + g_lua.pushNumber(data.momentumBase); + g_lua.setField("momentumBase"); + + g_lua.pushNumber(data.momentumBonus); + g_lua.setField("momentumBonus"); + + g_lua.pushNumber(data.momentumWheel); + g_lua.setField("momentumWheel"); + + g_lua.pushNumber(data.dodgeTotal); + g_lua.setField("dodgeTotal"); + + g_lua.pushNumber(data.dodgeBase); + g_lua.setField("dodgeBase"); + + g_lua.pushNumber(data.dodgeBonus); + g_lua.setField("dodgeBonus"); + + g_lua.pushNumber(data.dodgeWheel); + g_lua.setField("dodgeWheel"); + + g_lua.pushNumber(data.damageReflectionTotal); + g_lua.setField("damageReflectionTotal"); + + g_lua.pushNumber(data.damageReflectionBase); + g_lua.setField("damageReflectionBase"); + + g_lua.pushNumber(data.damageReflectionBonus); + g_lua.setField("damageReflectionBonus"); + + g_lua.pushInteger(data.haveBlesses); + g_lua.setField("haveBlesses"); + + g_lua.pushInteger(data.totalBlesses); + g_lua.setField("totalBlesses"); + + g_lua.createTable(data.concoctions.size(), 0); + for (size_t i = 0; i < data.concoctions.size(); ++i) { + g_lua.createTable(0, 2); + g_lua.pushInteger(data.concoctions[i].id); + g_lua.setField("id"); + g_lua.pushInteger(data.concoctions[i].duration); + g_lua.setField("duration"); + g_lua.rawSeti(i + 1); + } + g_lua.setField("concoctions"); + + return 1; +} diff --git a/src/client/luavaluecasts_client.h b/src/client/luavaluecasts_client.h index 47200b2347..f663e5eddb 100644 --- a/src/client/luavaluecasts_client.h +++ b/src/client/luavaluecasts_client.h @@ -89,6 +89,9 @@ int push_luavalue(const OutfitColorStruct& currentOutfit); int push_luavalue(const CharacterInfoOutfits& outfit); int push_luavalue(const CharacterInfoMounts& mount); int push_luavalue(const CharacterInfoFamiliar& familiar); +int push_luavalue(const CyclopediaCharacterOffenceStats& data); +int push_luavalue(const CyclopediaCharacterDefenceStats& data); +int push_luavalue(const CyclopediaCharacterMiscStats& data); // bestiary int push_luavalue(const RaceType& raceData); diff --git a/src/client/protocolgame.cpp b/src/client/protocolgame.cpp index d2f2b9cb4f..b75bd007f1 100644 --- a/src/client/protocolgame.cpp +++ b/src/client/protocolgame.cpp @@ -66,7 +66,9 @@ void ProtocolGame::onRecv(const InputMessagePtr& inputMessage) if (m_firstRecv) { m_firstRecv = false; - if (g_game.getFeature(Otc::GameMessageSizeCheck)) { + if (g_game.getClientVersion() >= 1405) { + const int padding = inputMessage->getU8(); + } else if (g_game.getFeature(Otc::GameMessageSizeCheck)) { const int size = inputMessage->getU16(); if (size != inputMessage->getUnreadSize()) { g_logger.traceError("invalid message size"); diff --git a/src/client/protocolgame.h b/src/client/protocolgame.h index 62e0a9e3f6..c59ed14a73 100644 --- a/src/client/protocolgame.h +++ b/src/client/protocolgame.h @@ -207,7 +207,7 @@ class ProtocolGame final : public Protocol void parseSessionEnd(const InputMessagePtr& msg); void parsePing(const InputMessagePtr& msg); void parsePingBack(const InputMessagePtr& msg); - void parseChallenge(const InputMessagePtr& msg); + void parseLoginChallenge(const InputMessagePtr& msg); void parseDeath(const InputMessagePtr& msg); void parseFloorDescription(const InputMessagePtr& msg); void parseMapDescription(const InputMessagePtr& msg); diff --git a/src/client/protocolgameparse.cpp b/src/client/protocolgameparse.cpp index 1229f2e8ad..6bfe00a062 100644 --- a/src/client/protocolgameparse.cpp +++ b/src/client/protocolgameparse.cpp @@ -109,7 +109,7 @@ void ProtocolGame::parseMessage(const InputMessagePtr& msg) } break; case Proto::GameServerChallenge: - parseChallenge(msg); + parseLoginChallenge(msg); break; case Proto::GameServerDeath: parseDeath(msg); @@ -743,7 +743,19 @@ void ProtocolGame::parseRequestPurchaseData(const InputMessagePtr& msg) void ProtocolGame::parseResourceBalance(const InputMessagePtr& msg) const { const auto type = static_cast(msg->getU8()); - const uint64_t value = msg->getU64(); + uint64_t value; + switch (type) { + case Otc::RESOURCE_CHARM: + case Otc::RESOURCE_MINOR_CHARM: + case Otc::RESOURCE_MAX_CHARM: + case Otc::RESOURCE_MAX_MINOR_CHARM: + // 14.10 + value = msg->getU32(); + break; + default: + value = msg->getU64(); + break; + } m_localPlayer->setResourceBalance(type, value); } @@ -1247,11 +1259,15 @@ void ProtocolGame::parseSessionEnd(const InputMessagePtr& msg) void ProtocolGame::parsePing(const InputMessagePtr&) { g_game.processPing(); } void ProtocolGame::parsePingBack(const InputMessagePtr&) { g_game.processPingBack(); } -void ProtocolGame::parseChallenge(const InputMessagePtr& msg) +void ProtocolGame::parseLoginChallenge(const InputMessagePtr& msg) { const uint32_t timestamp = msg->getU32(); const uint8_t random = msg->getU8(); + if (g_game.getClientVersion() >= 1405) { + msg->skipBytes(1); + } + sendLoginPacket(timestamp, random); } @@ -1622,7 +1638,7 @@ void ProtocolGame::parsePlayerGoods(const InputMessagePtr& msg) const // 12.x NOTE: this u64 is parsed only, because TFS stil sends it, we use resource balance in this protocol uint64_t money = 0; if (g_game.getClientVersion() >= 1281) { - money = m_localPlayer->getResourceBalance(Otc::RESOURCE_BANK_BALANCE) + m_localPlayer->getResourceBalance(Otc::RESOURCE_GOLD_EQUIPPED); + money = m_localPlayer->getTotalMoney(); } else { money = g_game.getClientVersion() >= 973 ? msg->getU64() : msg->getU32(); } @@ -2188,15 +2204,21 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg) const if (g_game.getFeature(Otc::GameExperienceBonus)) { if (g_game.getClientVersion() <= 1096) { - msg->getDouble(); // experienceBonus + const double experienceBonus = msg->getDouble(); + m_localPlayer->setExperienceRate(Otc::EXP_BASE, experienceBonus * 100); } else { - msg->getU16(); // baseXpGain + const uint16_t baseXpGain = msg->getU16(); + m_localPlayer->setExperienceRate(Otc::EXP_BASE, baseXpGain); if (g_game.getClientVersion() < 1281) { - msg->getU16(); // voucherAddend + const uint16_t voucherAddend = msg->getU16(); + m_localPlayer->setExperienceRate(Otc::EXP_VOUCHER, voucherAddend); } - msg->getU16(); // grindingAddend - msg->getU16(); // storeBoostAddend - msg->getU16(); // huntingBoostFactor + const uint16_t grindingAddend = msg->getU16(); + m_localPlayer->setExperienceRate(Otc::EXP_LOWLEVEL, grindingAddend); + const uint16_t storeBoostAddend = msg->getU16(); + m_localPlayer->setExperienceRate(Otc::EXP_XPBOOST, storeBoostAddend); + const uint16_t huntingBoostFactor = msg->getU16(); + m_localPlayer->setExperienceRate(Otc::EXP_STANINAMULTIPLIER, huntingBoostFactor); } } @@ -2219,7 +2241,7 @@ void ProtocolGame::parsePlayerStats(const InputMessagePtr& msg) const const uint16_t training = g_game.getFeature(Otc::GameOfflineTrainingTime) ? msg->getU16() : 0; if (g_game.getClientVersion() >= 1097) { - msg->getU16(); // xp boost time (seconds) + m_localPlayer->setStoreExpBoostTime(msg->getU16()); // xp boost time (seconds) msg->getU8(); // enables exp boost in the store } @@ -2302,8 +2324,7 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg) const msg->getU8(); } - if (g_game.getClientVersion() >= 1281) { - // forge skill stats + if (g_game.getFeature(Otc::GameForgeSkillStats)) { const uint8_t lastSkill = g_game.getClientVersion() >= 1332 ? Otc::LastSkill : Otc::Momentum + 1; for (int_fast32_t skill = Otc::Fatal; skill < lastSkill; ++skill) { const uint16_t level = msg->getU16(); @@ -2318,13 +2339,65 @@ void ProtocolGame::parsePlayerSkills(const InputMessagePtr& msg) const m_localPlayer->setTotalCapacity(capacity); } + + if (g_game.getFeature(Otc::GameCharacterSkillStats)) { + //msg->getU8(); // GameConcotions ?? + const uint32_t capacity = msg->getU32(); // base + bonus capacity + msg->getU32(); // base capacity + m_localPlayer->setTotalCapacity(capacity); + // Flat Damage and Healing Total + const uint16_t flatBonus = msg->getU16(); + m_localPlayer->setFlatDamageHealing(flatBonus); + + // Weapon attack and element info + const uint16_t attackValue = msg->getU16(); + const uint8_t attackElement = msg->getU8(); + m_localPlayer->setAttackInfo(attackValue, attackElement); + + // Converted Damage + const double convertedDamage = msg->getDouble(); + const uint8_t convertedElement = msg->getU8(); + m_localPlayer->setConvertedDamage(convertedDamage, convertedElement); + + // Imbuements + const double lifeLeech = msg->getDouble(); + const double manaLeech = msg->getDouble(); + const double critChance = msg->getDouble(); + const double critDamage = msg->getDouble(); + const double onslaught = msg->getDouble(); + m_localPlayer->setImbuements(lifeLeech, manaLeech, critChance, critDamage, onslaught); + + // Defense info + const uint16_t defense = msg->getU16(); + const uint16_t armor = msg->getU16(); + const double mitigation = msg->getDouble(); + const double dodge = msg->getDouble(); + const uint16_t damageReflection = msg->getU16(); + m_localPlayer->setDefenseInfo(defense, armor, mitigation, dodge, damageReflection); + + // Combat absorb values + const uint8_t combatsCount = msg->getU8(); + std::map absorbValues; + for (int i = 0; i < combatsCount; i++) { + const uint8_t combatType = msg->getU8(); + const double value = msg->getDouble(); + absorbValues[combatType] = value; + } + m_localPlayer->setCombatAbsorbValues(absorbValues); + + // Forge bonuses + const double momentum = msg->getDouble(); + const double transcendence = msg->getDouble(); + const double amplification = msg->getDouble(); + m_localPlayer->setForgeBonuses(momentum, transcendence, amplification); + } } void ProtocolGame::parsePlayerState(const InputMessagePtr& msg) const { uint32_t states; if (g_game.getClientVersion() >= 1281) { - states = msg->getU32(); + states = g_game.getClientVersion() >= 1405 ? msg->getU64() : msg->getU32(); if (g_game.getFeature(Otc::GamePlayerStateCounter)) { msg->getU8(); // icons counter } @@ -2786,29 +2859,33 @@ void ProtocolGame::parseQuestTracker(const InputMessagePtr& msg) case 1: { const uint8_t remainingQuests = msg->getU8(); const uint8_t missionCount = msg->getU8(); - std::vector> missions; + std::vector> missions; for (uint8_t i = 0; i < missionCount; ++i) { - const uint16_t missionId = msg->getU16(); - const std::string& questName = msg->getString(); - uint8_t questIsCompleted = 0; + uint8_t questId = 0; if (g_game.getClientVersion() >= 1410) { - questIsCompleted = msg->getU8(); + questId = msg->getU16(); } + const uint16_t missionId = msg->getU16(); + const std::string& questName = msg->getString(); const std::string& missionName = msg->getString(); const std::string& missionDesc = msg->getString(); - missions.emplace_back(missionId, questName, questIsCompleted, missionName, missionDesc); + missions.emplace_back(questId, missionId, questName, missionName, missionDesc); } return g_lua.callGlobalField("g_game", "onQuestTracker", remainingQuests, missions); } case 0: { + uint8_t questId = 0; + if (g_game.getClientVersion() >= 1410) { + questId = msg->getU16(); + } const uint16_t missionId = msg->getU16(); - const std::string& missionName = msg->getString(); - uint8_t questIsCompleted = 0; + std::string questName = ""; if (g_game.getClientVersion() >= 1410) { - questIsCompleted = msg->getU8(); + questName = msg->getString(); } + const std::string& missionName = msg->getString(); const std::string& missionDesc = msg->getString(); - return g_lua.callGlobalField("g_game", "onUpdateQuestTracker", missionId, missionName, questIsCompleted, missionDesc); + return g_lua.callGlobalField("g_game", "onUpdateQuestTracker", questId, missionId, questName, missionName, missionDesc); } } } @@ -2990,14 +3067,15 @@ void ProtocolGame::parseBestiaryMonsterData(const InputMessagePtr& msg) msg->getU16(); data.location = msg->getString(); } - - if (data.currentLevel > 3) { - const bool hasCharm = static_cast(msg->getU8()); - if (hasCharm) { - msg->getU8(); - msg->getU32(); - } else { - msg->getU8(); + if (g_game.getClientVersion() < 1410) { + if (data.currentLevel > 3) { + const bool hasCharm = static_cast(msg->getU8()); + if (hasCharm) { + msg->getU8(); + msg->getU32(); + } else { + msg->getU8(); + } } } @@ -3007,40 +3085,67 @@ void ProtocolGame::parseBestiaryMonsterData(const InputMessagePtr& msg) void ProtocolGame::parseBestiaryCharmsData(const InputMessagePtr& msg) { BestiaryCharmsData charmData; - charmData.points = msg->getU32(); + + if (g_game.getClientVersion() >= 1410) { + charmData.resetAllCharmsCost = msg->getU64(); + } else { + charmData.points = msg->getU32(); + } const uint8_t charmsAmount = msg->getU8(); + charmData.charms.reserve(charmsAmount); + for (auto i = 0; i < charmsAmount; ++i) { CharmData charm; charm.id = msg->getU8(); - charm.name = msg->getString(); - charm.description = msg->getString(); - msg->getU8(); - charm.unlockPrice = msg->getU16(); - charm.unlocked = msg->getU8() == 1; charm.asignedStatus = false; charm.raceId = 0; charm.removeRuneCost = 0; - + if (g_game.getClientVersion() >= 1410) { + charm.tier = msg->getU8(); + charm.unlocked = msg->getU8() == 1; + } else { + charm.name = msg->getString(); + charm.description = msg->getString(); + msg->getU8(); // Unknown byte + charm.unlockPrice = msg->getU16(); + charm.unlocked = msg->getU8() == 1; + charm.tier = charm.unlocked ? 1 : 0; + } if (charm.unlocked) { - const bool asigned = static_cast(msg->getU8()); + bool asigned = true; + if (g_game.getClientVersion() < 1410) { + asigned = static_cast(msg->getU8()); + } + if (asigned) { - charm.asignedStatus = asigned; + charm.asignedStatus = true; charm.raceId = msg->getU16(); charm.removeRuneCost = msg->getU32(); } - } else { - msg->getU8(); + } else if (g_game.getClientVersion() < 1410) { + msg->getU8(); // ?? } charmData.charms.emplace_back(charm); } - msg->getU8(); + if (g_game.getClientVersion() >= 1410) { + charmData.availableCharmSlots = msg->getU8(); + } else { + msg->getU8(); // ?? + } const uint16_t finishedMonstersSize = msg->getU16(); + charmData.finishedMonsters.reserve(finishedMonstersSize); + for (auto i = 0; i < finishedMonstersSize; ++i) { - const uint16_t raceId = msg->getU16(); + uint32_t raceId; + if (g_game.getClientVersion() >= 1410) { + raceId = msg->getU32(); + } else { + raceId = msg->getU16(); + } charmData.finishedMonsters.emplace_back(raceId); } @@ -4074,8 +4179,9 @@ void ProtocolGame::parseSupplyStash(const InputMessagePtr& msg) uint32_t amount = msg->getU32(); stashItems.push_back({ itemId, amount }); } - - msg->getU16(); // free slots + if (g_game.getProtocolVersion() < 1410) { + msg->getU16(); // free slots + } g_lua.callGlobalField("g_game", "onSupplyStashEnter", stashItems); } @@ -4120,21 +4226,27 @@ void ProtocolGame::parseImbuementDurations(const InputMessagePtr& msg) std::vector itemList; for (auto i = 0; i < itemListCount; ++i) { - ImbuementTrackerItem item(msg->getU8()); + ImbuementTrackerItem item(msg->getU8()); // slot item.item = getItem(msg); std::map slots; const uint8_t slotsCount = msg->getU8(); // total amount of imbuing slots on item + if (slotsCount == 0) { + continue; + } + for (auto slotIndex = 0; slotIndex < slotsCount; ++slotIndex) { const bool slotImbued = static_cast(msg->getU8()); // 0 - empty, 1 - imbued - ImbuementSlot slot(slotIndex); - if (slotImbued) { - slot.name = msg->getString(); - slot.iconId = msg->getU16(); - slot.duration = msg->getU32(); - slot.state = msg->getU8(); // 0 - paused, 1 - decaying + if (!slotImbued) { + continue; } + + ImbuementSlot slot(slotIndex); + slot.name = msg->getString(); + slot.iconId = msg->getU16(); + slot.duration = msg->getU32(); + slot.state = msg->getU8(); // 0 - paused, 1 - decaying slots.emplace(slotIndex, slot); } @@ -4385,7 +4497,7 @@ void ProtocolGame::parseCyclopediaCharacterInfo(const InputMessagePtr& msg) std::vector> forgeSkillsArray; - if (g_game.getClientVersion() >= 1281) { + if (g_game.getFeature(Otc::GameForgeSkillStats)) { // forge skill stats const uint8_t lastSkill = g_game.getClientVersion() >= 1332 ? Otc::LastSkill : Otc::Momentum + 1; for (uint16_t skill = Otc::Fatal; skill < lastSkill; ++skill) { @@ -4524,8 +4636,11 @@ void ProtocolGame::parseCyclopediaCharacterInfo(const InputMessagePtr& msg) for (auto i = 0; i < stashItemsCount; ++i) { ItemSummary item; const uint16_t itemId = msg->getU16(); - const auto& itemCreated = Item::create(itemId); - const uint16_t classification = itemCreated->getClassification(); + const auto& thing = g_things.getThingType(itemId, ThingCategoryItem); + if (!thing) { + continue; + } + const uint16_t classification = thing->getClassification(); uint8_t itemTier = 0; if (classification > 0) { @@ -4711,6 +4826,153 @@ void ProtocolGame::parseCyclopediaCharacterInfo(const InputMessagePtr& msg) } break; } + case Otc::CYCLOPEDIA_CHARACTERINFO_OFFENCESTATS: + { + CyclopediaCharacterOffenceStats data; + data.critChance = msg->getDouble(); + msg->getDouble(); // unused + msg->getDouble(); // unused + msg->getDouble(); // unused + msg->getDouble(); // unused + + // Critical hit damage + data.critDamage = msg->getDouble(); + data.critDamageBase = msg->getDouble(); + data.critDamageImbuement = msg->getDouble(); + data.critDamageWheel = msg->getDouble(); + msg->getDouble(); // unused + + // Life leech amount + data.lifeLeech = msg->getDouble(); + data.lifeLeechBase = msg->getDouble(); + data.lifeLeechImbuement = msg->getDouble(); + data.lifeLeechWheel = msg->getDouble(); + msg->getDouble(); // unused + + // Mana leech amount + data.manaLeech = msg->getDouble(); + data.manaLeechBase = msg->getDouble(); + data.manaLeechImbuement = msg->getDouble(); + data.manaLeechWheel = msg->getDouble(); + msg->getDouble(); // unused + + // Onslaught + data.onslaught = msg->getDouble(); + data.onslaughtBase = msg->getDouble(); + data.onslaughtBonus = msg->getDouble(); + msg->getDouble(); // unused + + data.cleavePercent = msg->getDouble(); + + // Perfect shot range + for (int i = 0; i < 5; i++) { + data.perfectShotDamage.push_back(msg->getU16()); + } + + data.flatDamage = msg->getU16(); + data.flatDamageBase = msg->getU16(); + msg->getU16(); // unused + + data.weaponAttack = msg->getU16(); + data.weaponFlatModifier = msg->getU16(); + data.weaponDamage = msg->getU16(); + data.weaponSkillType = msg->getU8(); + data.weaponSkillLevel = msg->getU16(); + data.weaponSkillModifier = msg->getU16(); + data.weaponElement = msg->getU8(); + data.weaponElementDamage = msg->getDouble(); + data.weaponElementType = msg->getU8(); + + const uint8_t accuracyCount = msg->getU8(); + for (int i = 0; i < accuracyCount; i++) { + msg->getU8(); // range + data.weaponAccuracy.push_back(msg->getDouble()); + } + + g_game.processCyclopediaCharacterOffenceStats(data); + break; + } + case Otc::CYCLOPEDIA_CHARACTERINFO_DEFENCESTATS: + { + CyclopediaCharacterDefenceStats data; + + data.dodgeTotal = msg->getDouble(); + data.dodgeBase = msg->getDouble(); + data.dodgeBonus = msg->getDouble(); + msg->getDouble(); // unused + data.dodgeWheel = msg->getDouble(); + + data.magicShieldCapacity = msg->getU32(); + data.magicShieldCapacityFlat = msg->getU16(); + data.magicShieldCapacityPercent = msg->getDouble(); + + data.reflectPhysical = msg->getU16(); + data.armor = msg->getU16(); + + data.defense = msg->getU16(); + data.defenseEquipment = msg->getU16(); + data.defenseSkillType = msg->getU8(); + data.shieldingSkill = msg->getU16(); + data.defenseWheel = msg->getU16(); + msg->getU16(); // unused + + data.mitigation = msg->getDouble(); + data.mitigationBase = msg->getDouble(); + data.mitigationEquipment = msg->getDouble(); + data.mitigationShield = msg->getDouble(); + data.mitigationWheel = msg->getDouble(); + data.mitigationCombatTactics = msg->getDouble(); + const uint8_t combatsCount = msg->getU8(); + for (int i = 0; i < combatsCount; ++i) { + uint8_t elementType = msg->getU8(); + if (elementType == 0x04) { + CyclopediaCharacterDefenceStats::ElementalResistance resistance; + resistance.element = msg->getU8(); + resistance.value = msg->getDouble(); + data.resistances.push_back(resistance); + } + } + + g_game.processCyclopediaCharacterDefenceStats(data); + break; + } + case Otc::CYCLOPEDIA_CHARACTERINFO_MISCSTATS: + { + CyclopediaCharacterMiscStats data; + + data.momentumTotal = msg->getDouble(); + data.momentumBase = msg->getDouble(); + data.momentumBonus = msg->getDouble(); + data.momentumWheel = msg->getDouble(); + msg->getDouble(); // unused + + data.dodgeTotal = msg->getDouble(); + data.dodgeBase = msg->getDouble(); + data.dodgeBonus = msg->getDouble(); + data.dodgeWheel = msg->getDouble(); + + data.damageReflectionTotal = msg->getDouble(); + data.damageReflectionBase = msg->getDouble(); + data.damageReflectionBonus = msg->getDouble(); + + data.haveBlesses = msg->getU8(); + data.totalBlesses = msg->getU8(); + + const uint8_t concoctionsCount = msg->getU8(); + for (int i = 0; i < concoctionsCount; ++i) { + CyclopediaCharacterMiscStats::Concoction concoction; + concoction.id = msg->getU16(); + msg->getU8(); // unused + msg->getU8(); // unused + concoction.duration = msg->getU32(); + data.concoctions.push_back(concoction); + } + + msg->getU8(); // unused + + g_game.processCyclopediaCharacterMiscStats(data); + break; + } } } diff --git a/src/client/protocolgamesend.cpp b/src/client/protocolgamesend.cpp index 67fe6fa95a..49fd63b9c1 100644 --- a/src/client/protocolgamesend.cpp +++ b/src/client/protocolgamesend.cpp @@ -54,8 +54,11 @@ void ProtocolGame::sendLoginPacket(const uint32_t challengeTimestamp, const uint msg->addString(std::to_string(g_game.getClientVersion())); } - if (g_game.getFeature(Otc::GameContentRevision)) + if (g_game.getClientVersion() >= 1334) { + msg->addString("appearancesHash"); + } else if (g_game.getFeature(Otc::GameContentRevision)) { msg->addU16(g_things.getContentRevision()); + } if (g_game.getFeature(Otc::GamePreviewState)) msg->addU8(0); @@ -1205,7 +1208,7 @@ void ProtocolGame::sendRequestStorePremiumBoost() const auto& msg = std::make_shared(); msg->addU8(Proto::ClientRequestStoreOffers); msg->addU8(Otc::Store_Type_Actions_t::OPEN_PREMIUM_BOOST); - msg->addU8(0); + msg->addU8(1); send(msg); } diff --git a/src/framework/net/inputmessage.cpp b/src/framework/net/inputmessage.cpp index 5953783d66..32a5b4e294 100644 --- a/src/framework/net/inputmessage.cpp +++ b/src/framework/net/inputmessage.cpp @@ -23,11 +23,20 @@ #include "inputmessage.h" #include +#include "client/game.h" + +InputMessage::InputMessage() { + m_maxHeaderSize = g_game.getClientVersion() >= 1405 ? 7 : 8; + m_headerPos = m_maxHeaderSize; + m_readPos = m_maxHeaderSize; +} + void InputMessage::reset() { + m_maxHeaderSize = g_game.getClientVersion() >= 1405 ? 7 : 8; m_messageSize = 0; - m_readPos = MAX_HEADER_SIZE; - m_headerPos = MAX_HEADER_SIZE; + m_readPos = m_maxHeaderSize; + m_headerPos = m_maxHeaderSize; } void InputMessage::setBuffer(const std::string& buffer) @@ -112,8 +121,8 @@ void InputMessage::fillBuffer(const uint8_t* buffer, const uint16_t size) void InputMessage::setHeaderSize(const uint16_t size) { - assert(MAX_HEADER_SIZE - size >= 0); - m_headerPos = MAX_HEADER_SIZE - size; + assert(m_maxHeaderSize - size >= 0); + m_headerPos = m_maxHeaderSize - size; m_readPos = m_headerPos; } diff --git a/src/framework/net/inputmessage.h b/src/framework/net/inputmessage.h index f43c680293..040ff09b5c 100644 --- a/src/framework/net/inputmessage.h +++ b/src/framework/net/inputmessage.h @@ -29,15 +29,21 @@ class InputMessage final : public LuaObject { public: + InputMessage(); + enum { - BUFFER_MAXSIZE = 65536, - MAX_HEADER_SIZE = 8 + BUFFER_MAXSIZE = 65536 }; + inline auto getMaxHeaderSize() const + { + return m_maxHeaderSize; + } + void setBuffer(const std::string& buffer); std::string_view getBuffer() { return std::string_view{ (char*)m_buffer + m_headerPos, m_messageSize }; } - std::string getBodyBuffer() { return std::string((char*)m_buffer + MAX_HEADER_SIZE, m_messageSize - getHeaderSize()); } + std::string getBodyBuffer() { return std::string((char*)m_buffer + m_maxHeaderSize, m_messageSize - getHeaderSize()); } void skipBytes(const uint16_t bytes) { m_readPos += bytes; } void setReadPos(const uint16_t readPos) { m_readPos = readPos; } @@ -73,6 +79,11 @@ class InputMessage final : public LuaObject int getUnreadSize() { return m_messageSize - (m_readPos - m_headerPos); } uint16_t getMessageSize() { return m_messageSize; } + void setPaddingSize(uint8_t padding) { m_padding = padding; } + uint8_t getPaddingSize() const { + return m_padding; + } + bool eof() { return (m_readPos - m_headerPos) >= m_messageSize; } protected: @@ -84,8 +95,8 @@ class InputMessage final : public LuaObject uint8_t* getReadBuffer() { return m_buffer + m_readPos; } uint8_t* getHeaderBuffer() { return m_buffer + m_headerPos; } - uint8_t* getDataBuffer() { return m_buffer + MAX_HEADER_SIZE; } - uint16_t getHeaderSize() const { return (MAX_HEADER_SIZE - m_headerPos); } + uint8_t* getDataBuffer() { return m_buffer + m_maxHeaderSize; } + uint16_t getHeaderSize() const { return (m_maxHeaderSize - m_headerPos); } uint16_t readSize() { return getU16(); } bool readChecksum(); @@ -97,8 +108,10 @@ class InputMessage final : public LuaObject void checkRead(int bytes); void checkWrite(int bytes); - uint16_t m_headerPos{ MAX_HEADER_SIZE }; - uint16_t m_readPos{ MAX_HEADER_SIZE }; + uint8_t m_maxHeaderSize = 8; + uint16_t m_headerPos{ m_maxHeaderSize }; + uint16_t m_readPos{ m_maxHeaderSize }; uint16_t m_messageSize{ 0 }; + uint8_t m_padding{ 0 }; uint8_t m_buffer[BUFFER_MAXSIZE]{}; }; diff --git a/src/framework/net/outputmessage.cpp b/src/framework/net/outputmessage.cpp index 26518a5c0f..085b5ce3ae 100644 --- a/src/framework/net/outputmessage.cpp +++ b/src/framework/net/outputmessage.cpp @@ -23,10 +23,19 @@ #include #include +#include "client/game.h" + +OutputMessage::OutputMessage() { + m_maxHeaderSize = g_game.getClientVersion() >= 1405 ? 7 : 8; + m_writePos = m_maxHeaderSize; + m_headerPos = m_maxHeaderSize; +} + void OutputMessage::reset() { - m_writePos = MAX_HEADER_SIZE; - m_headerPos = MAX_HEADER_SIZE; + m_maxHeaderSize = g_game.getClientVersion() >= 1405 ? 7 : 8; + m_writePos = m_maxHeaderSize; + m_headerPos = m_maxHeaderSize; m_messageSize = 0; } @@ -129,6 +138,19 @@ void OutputMessage::writeMessageSize() m_messageSize += 2; } +void OutputMessage::writePaddingAmount() +{ + const uint8_t paddingAmount = 8 - (m_messageSize % 8) - 1; + addPaddingBytes(paddingAmount); + prependU8(paddingAmount); +} + +void OutputMessage::writeHeaderSize() +{ + auto headerSize = static_cast((m_messageSize - 4) / 8); // -4 for checksum + prependU16(headerSize); // Uses writeULE16 and updates `m_headerPos` and `m_messageSize` +} + bool OutputMessage::canWrite(const int bytes) const { return m_writePos + bytes <= BUFFER_MAXSIZE; @@ -138,4 +160,27 @@ void OutputMessage::checkWrite(const int bytes) { if (!canWrite(bytes)) throw stdext::exception("OutputMessage max buffer size reached"); -} \ No newline at end of file +} + +void OutputMessage::prependU8(uint8_t value) +{ + assert(m_headerPos > 0); + m_headerPos--; + m_writePos--; + m_buffer[m_headerPos] = value; + m_messageSize++; +} + +void OutputMessage::prependU16(uint16_t value) +{ + assert(m_headerPos >= 2); + m_headerPos -= 2; + m_writePos -= 2; + stdext::writeULE16(m_buffer + m_headerPos, value); + m_messageSize += 2; +} + +uint8_t* OutputMessage::getXteaEncryptionBuffer() +{ + return g_game.getClientVersion() >= 1405 ? getHeaderBuffer() : getDataBuffer() - 2; +} diff --git a/src/framework/net/outputmessage.h b/src/framework/net/outputmessage.h index d16be7690d..a06b8ad3b0 100644 --- a/src/framework/net/outputmessage.h +++ b/src/framework/net/outputmessage.h @@ -32,10 +32,11 @@ class OutputMessage final : public LuaObject enum { BUFFER_MAXSIZE = 65536, - MAX_STRING_LENGTH = 65536, - MAX_HEADER_SIZE = 8 + MAX_STRING_LENGTH = 65536 }; + OutputMessage(); + void reset(); void setBuffer(const std::string& buffer); @@ -47,6 +48,8 @@ class OutputMessage final : public LuaObject void addU64(uint64_t value); void addString(std::string_view buffer); void addPaddingBytes(int bytes, uint8_t byte = 0); + void prependU8(uint8_t value); + void prependU16(uint16_t value); void encryptRsa(); @@ -56,14 +59,18 @@ class OutputMessage final : public LuaObject void setWritePos(const uint16_t writePos) { m_writePos = writePos; } void setMessageSize(const uint16_t messageSize) { m_messageSize = messageSize; } + uint8_t* getXteaEncryptionBuffer(); + protected: uint8_t* getWriteBuffer() { return m_buffer + m_writePos; } uint8_t* getHeaderBuffer() { return m_buffer + m_headerPos; } - uint8_t* getDataBuffer() { return m_buffer + MAX_HEADER_SIZE; } + uint8_t* getDataBuffer() { return m_buffer + m_maxHeaderSize; } void writeChecksum(); void writeSequence(uint32_t sequence); void writeMessageSize(); + void writePaddingAmount(); + void writeHeaderSize(); friend class Protocol; friend class PacketPlayer; @@ -72,8 +79,9 @@ class OutputMessage final : public LuaObject bool canWrite(int bytes) const; void checkWrite(int bytes); - uint16_t m_headerPos{ MAX_HEADER_SIZE }; - uint16_t m_writePos{ MAX_HEADER_SIZE }; + uint8_t m_maxHeaderSize { 8 }; + uint16_t m_headerPos{ m_maxHeaderSize }; + uint16_t m_writePos{ m_maxHeaderSize }; uint16_t m_messageSize{ 0 }; uint8_t m_buffer[BUFFER_MAXSIZE]; }; diff --git a/src/framework/net/protocol.cpp b/src/framework/net/protocol.cpp index 9ecacedc52..2d1abffcb6 100644 --- a/src/framework/net/protocol.cpp +++ b/src/framework/net/protocol.cpp @@ -28,6 +28,7 @@ #include "inputmessage.h" #include "outputmessage.h" #include "framework/core/graphicalapplication.h" +#include "client/game.h" #ifdef __EMSCRIPTEN__ #include "webconnection.h" #else @@ -131,19 +132,29 @@ void Protocol::send(const OutputMessagePtr& outputMessage) m_recorder->addOutputPacket(outputMessage); } + // padding + if (g_game.getClientVersion() >= 1405) { + outputMessage->writePaddingAmount(); + } // encrypt - if (m_xteaEncryptionEnabled) + if (m_xteaEncryptionEnabled) { xteaEncrypt(outputMessage); + } // write checksum - if (m_sequencedPackets) + if (m_sequencedPackets) { outputMessage->writeSequence(m_packetNumber++); - else if (m_checksumEnabled) + } else if (m_checksumEnabled) { outputMessage->writeChecksum(); + } // write message size - outputMessage->writeMessageSize(); + if (g_game.getClientVersion() >= 1405) { + outputMessage->writeHeaderSize(); + } else { + outputMessage->writeMessageSize(); + } onSend(); @@ -174,8 +185,11 @@ void Protocol::recv() int headerSize = 2; // 2 bytes for message size if (m_checksumEnabled) headerSize += 4; // 4 bytes for checksum - if (m_xteaEncryptionEnabled) + if (g_game.getClientVersion() >= 1405) { + headerSize += 1; // 1 bytes for padding size + } else if (m_xteaEncryptionEnabled) { headerSize += 2; // 2 bytes for XTEA encrypted message size + } m_inputMessage->setHeaderSize(headerSize); // read the first 2 bytes which contain the message size @@ -190,7 +204,16 @@ void Protocol::internalRecvHeader(const uint8_t* buffer, const uint16_t size) { // read message size m_inputMessage->fillBuffer(buffer, size); - const uint16_t remainingSize = m_inputMessage->readSize(); + uint16_t remainingSize = m_inputMessage->readSize(); + if (g_game.getClientVersion() >= 1405) { + remainingSize = remainingSize * 8 + 4; + } + + constexpr uint32_t MAX_PACKET = InputMessage::BUFFER_MAXSIZE; + if (remainingSize == 0 || remainingSize > MAX_PACKET) { + g_logger.error(fmt::format("invalid packet size = {}", remainingSize)); + return; + } // read remaining message data if (m_connection) @@ -214,10 +237,14 @@ void Protocol::internalRecvData(const uint8_t* buffer, const uint16_t size) if (m_sequencedPackets) { decompress = (m_inputMessage->getU32() & 1 << 31); } else if (m_checksumEnabled && !m_inputMessage->readChecksum()) { - g_logger.traceError( - "got a network message with invalid checksum, size: {}", - static_cast(m_inputMessage->getMessageSize()) - ); + std::string headerHex; + headerHex.reserve(m_inputMessage->getHeaderSize() * 3); // 2 chars + space por byte + + for (size_t i = 0; i < m_inputMessage->getHeaderSize(); ++i) { + fmt::format_to(std::back_inserter(headerHex), "{:02X} ", static_cast(m_inputMessage->getBuffer()[i])); + } + + g_logger.traceError(fmt::format("got a network message with invalid checksum, header: {}, size: {}", headerHex, static_cast(m_inputMessage->getMessageSize()))); return; } @@ -306,20 +333,31 @@ bool Protocol::xteaDecrypt(const InputMessagePtr& inputMessage) const }); } - const uint16_t decryptedSize = inputMessage->getU16() + 2; - const int sizeDelta = decryptedSize - encryptedSize; - if (sizeDelta > 0 || -sizeDelta > encryptedSize) { - g_logger.traceError("invalid decrypted network message"); - return false; + uint16_t decryptedSize; + if (g_game.getClientVersion() >= 1405) { + const uint8_t paddingSize = inputMessage->getU8(); + inputMessage->setPaddingSize(paddingSize); + decryptedSize = encryptedSize - paddingSize - 1; + inputMessage->setMessageSize(inputMessage->getHeaderSize() + decryptedSize); + } else { + decryptedSize = inputMessage->getU16() + 2; + const int sizeDelta = decryptedSize - encryptedSize; + if (sizeDelta > 0 || -sizeDelta > encryptedSize) { + g_logger.traceError("invalid decrypted network message"); + return false; + } + inputMessage->setMessageSize(inputMessage->getMessageSize() + sizeDelta); } - inputMessage->setMessageSize(inputMessage->getMessageSize() + sizeDelta); return true; } void Protocol::xteaEncrypt(const OutputMessagePtr& outputMessage) const { - outputMessage->writeMessageSize(); + if (g_game.getClientVersion() < 1405) { + outputMessage->writeMessageSize(); + } + uint16_t encryptedSize = outputMessage->getMessageSize(); //add bytes until reach 8 multiple @@ -330,7 +368,7 @@ void Protocol::xteaEncrypt(const OutputMessagePtr& outputMessage) const } for (uint32_t i = 0, sum = 0, next_sum = sum + delta; i < 32; ++i, sum = next_sum, next_sum += delta) { - apply_rounds(outputMessage->getDataBuffer() - 2, encryptedSize, [&](uint32_t& left, uint32_t& right) { + apply_rounds(outputMessage->getXteaEncryptionBuffer(), encryptedSize, [&, sum, next_sum, this](uint32_t& left, uint32_t& right) mutable { left += ((right << 4 ^ right >> 5) + right) ^ (sum + m_xteaKey[sum & 3]); right += ((left << 4 ^ left >> 5) + left) ^ (next_sum + m_xteaKey[(next_sum >> 11) & 3]); }); @@ -364,8 +402,11 @@ void Protocol::onProxyPacket(const std::shared_ptr>& packet int headerSize = 2; // 2 bytes for message size if (m_checksumEnabled) headerSize += 4; // 4 bytes for checksum - if (m_xteaEncryptionEnabled) + if (g_game.getClientVersion() >= 1405) { + headerSize += 1; // 1 bytes for padding size + } else if (m_xteaEncryptionEnabled) { headerSize += 2; // 2 bytes for XTEA encrypted message size + } m_inputMessage->setHeaderSize(headerSize); m_inputMessage->fillBuffer(packet->data(), 2); m_inputMessage->readSize(); diff --git a/src/protobuf/appearances.proto b/src/protobuf/appearances.proto index 80499a1dc4..465c08b67e 100644 --- a/src/protobuf/appearances.proto +++ b/src/protobuf/appearances.proto @@ -47,6 +47,7 @@ enum ITEM_CATEGORY { ITEM_CATEGORY_BACKPACK = 28; ITEM_CATEGORY_ONEHANDWEAPON = 29; ITEM_CATEGORY_ARROW = 30; + ITEM_CATEGORY_SOULCORES = 31; } enum PLAYER_PROFESSION { @@ -56,6 +57,7 @@ enum PLAYER_PROFESSION { PLAYER_PROFESSION_PALADIN = 2; PLAYER_PROFESSION_SORCERER = 3; PLAYER_PROFESSION_DRUID = 4; + PLAYER_PROFESSION_MONK = 5; PLAYER_PROFESSION_PROMOTED = 10; } @@ -183,12 +185,21 @@ message AppearanceFlags { optional bool expire = 55; optional bool expirestop = 56; optional bool deco_kit = 57; + optional bool dual_wielding = 59; + reserved 58, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69; + optional bool hook_south = 70; + optional bool hook_east = 71; + optional AppearanceFlagTransparencyLevel transparencylevel = 72; } message AppearanceFlagUpgradeClassification { optional uint32 upgrade_classification = 1; } +message AppearanceFlagTransparencyLevel { + optional uint32 level = 1; +} + message AppearanceFlagBank { optional uint32 waypoints = 1; }