diff --git a/english_localization_set/panorama_decompile/decompiled_layout/custom_loading_screen.xml b/english_localization_set/panorama_decompile/decompiled_layout/custom_loading_screen.xml new file mode 100644 index 0000000..3d6a3a3 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/custom_loading_screen.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/english_localization_set/panorama_decompile/decompiled_layout/custom_ui_manifest.xml b/english_localization_set/panorama_decompile/decompiled_layout/custom_ui_manifest.xml new file mode 100644 index 0000000..ed0f662 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/custom_ui_manifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/english_localization_set/panorama_decompile/decompiled_layout/dps_panel.xml b/english_localization_set/panorama_decompile/decompiled_layout/dps_panel.xml new file mode 100644 index 0000000..f6ad793 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/dps_panel.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/english_localization_set/panorama_decompile/decompiled_layout/end_screen.xml b/english_localization_set/panorama_decompile/decompiled_layout/end_screen.xml new file mode 100644 index 0000000..da35880 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/end_screen.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.css b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.css new file mode 100644 index 0000000..c3bbeb1 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.css @@ -0,0 +1,172 @@ +#CosmeticMenu { + horizontal-align: center; + vertical-align: bottom; + flow-children: down; + width: 100%; + height: 30px; + margin-right: 58px; + margin-left: 58px; + margin-bottom: 145px; + transition-property: height; + transition-duration: 0.2s; +} + +.Open #CosmeticMenu { + height: 400px; +} + +#CosmeticMenuButton { + horizontal-align: right; + width: 60px; + height: 30px; + border-radius: 5px 5px 0px 0px; + background-color: "#000E"; + background-image: url( "s2r://panorama/images/backgrounds/background_play_button_2x_png.vtex" ); +} + +#CosmeticMenuButtonText { + color: #ffffff; + horizontal-align: center; + vertical-align: middle; + font-size: 20px; +} + +#CosmeticMenuMain { + horizontal-align: center; + vertical-align: bottom; + width: 100%; + height: 100%; + background-color: #263238; + background-image: url( "s2r://panorama/images/hud/reborn/ability_bg_psd.vtex" ); + background-size: 100%; + padding: 30px 60px 60px; +} + +#CosmeticContainer { + flow-children: right; + horizontal-align: center; + vertical-align: center; + height: 100%; +} + +#AnimationPanel { + width: 185px; + height: 280px; +} + +#AnimationContainer { + width: 100%; + height: 100%; + background-color: rgba( 8, 8, 8, 0.9 ); +} + +#BorderAnimation { + width: 100%; + height: 100%; + background-image: url( "s2r://panorama/images/hud/passive_ability_border_png.vtex" ); + background-size: 100% 100%; +} + +#CosmeticAbilitiesContainer { + margin-left: 30px; + flow-children: down; +} + +.AbilitiesRow { + margin-top: 10px; + flow-children: right; +} + +#ImagePreview { + margin: 0px 3px; + width: 60px; + height: 60px; +} + +#BarOverAbilities { + vertical-align: bottom; + margin-left: 285px; + margin-bottom: 145px; + flow-children: right; +} + +#BarOverAbilities.FiveAbilities { + margin-left: 283px; +} + +.SlotOverAbility { + vertical-align: bottom; + width: 58px; + height: 58px; + margin-right: 7px; +} + +.FiveAbilities .SlotOverAbility { + vertical-align: bottom; + width: 54px; + height: 54px; + margin-right: 4px; +} + +#BarOverItems { + horizontal-align: right; + vertical-align: bottom; + margin-right: 59px; + margin-bottom: 145px; + flow-children: right; +} + +.SlotOverItems { + vertical-align: bottom; + min-width: 60px; + min-height: 60px; + margin-left: 5px; +} + +#Image { + horizontal-align: center; + vertical-align: bottom; +} + +.SlotOverAbility #Image { + width: 58px; + height: 58px; +} + +.SlotOverItems #Image { + width: 60px; + height: 60px; +} + +#Cooldown { + width: 100%; + height: 100%; +} + +#CooldownEffect { + width: 100%; + height: 100%; + background-color: rgba( 0, 0, 0, 0.85 ); +} + +#CooldownCountdown { + vertical-align: bottom; + padding: 0px 2px; + text-shadow: 2px 2px 4px 2 black; + font-size: 16px; + color: #999; +} + +#DeleteButton { + visibility: collapse; + horizontal-align: right; + background-color: rgb( 11, 11, 11 ); + background-image: url( "s2r://panorama/images/control_icons/x_close_png.vtex" ); + width: 16px; + height: 16px; + background-size: 16px 16px; +} + +.Open #DeleteButton { + visibility: visible; +} \ No newline at end of file diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.js b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.js new file mode 100644 index 0000000..07aad11 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.js @@ -0,0 +1,268 @@ +var IMAGES = {} +var cosmeticAbilities = { + "high_five": true, + "seasonal_ti9_banner": true, + "seasonal_summon_cny_balloon": true, + "seasonal_summon_dragon": true, + "seasonal_summon_cny_tree": true, + "seasonal_firecrackers": true, + "seasonal_ti9_shovel": true, + "seasonal_ti9_instruments": true, + "seasonal_ti9_monkey": true, + "seasonal_summon_ti9_balloon": true, + "seasonal_throw_snowball": true, + "seasonal_festive_firework": true, + "seasonal_decorate_tree": true, + "seasonal_summon_snowman": true +} +var permanentAbilitySlots = { + "high_five": 4, + "seasonal_ti9_banner": 5 +} +var abilitiesToTake = [ + "seasonal_summon_cny_balloon", + "seasonal_summon_dragon", + "seasonal_summon_cny_tree", + "seasonal_firecrackers", + "seasonal_ti9_shovel", + "seasonal_ti9_instruments", + "seasonal_ti9_monkey", + "seasonal_summon_ti9_balloon", + "seasonal_throw_snowball", + "seasonal_festive_firework", + "seasonal_decorate_tree", + "seasonal_summon_snowman" +] +var ABILITIES_CANT_BE_REMOVED = { + "high_five": true, + "seasonal_ti9_banner": true, +} +var abillity_name_to_webm = { + "seasonal_summon_cny_balloon":"40XJ9", + "seasonal_summon_dragon":"Ry9Mv", + "seasonal_summon_cny_tree":"rVY9D", + "seasonal_firecrackers":"Lxe64", + "seasonal_ti9_shovel":"b1dNv", + "seasonal_ti9_instruments":"5d1Rr", + "seasonal_ti9_monkey":"XEjX7", + "seasonal_summon_ti9_balloon":"BAEq9", + "seasonal_throw_snowball":"8mBLL", + "seasonal_festive_firework":"voBa5", + "seasonal_decorate_tree":"Px17L", + "seasonal_summon_snowman":"na38r" +} +var showcaseAbilitiesSlot = 6 + +var slots = [] + +var currentUnit = null +var currentAbilitiesCount = 0 +var animation = {} + +function ToggleCosmeticMenu() { + $.GetContextPanel().ToggleClass( "Open" ) +} + +function Ability( slot, abilityName ) { + this.abilityName = abilityName + + var image_path = IMAGES[abilityName] || "file://{images}/spellicons/consumables/" + abilityName + ".png" + + this.image = $.CreatePanel( "Image", slot.panel, "Image" ) + this.image.SetImage( image_path ) + + this.image.SetPanelEvent( "onactivate", function() { + if ( Entities.IsControllableByPlayer( currentUnit, Players.GetLocalPlayer() ) ) { + var ability = Entities.GetAbilityByName( currentUnit, abilityName ) + + if ( Abilities.IsActivated( ability ) ) { + Abilities.ExecuteAbility( ability, currentUnit, false ) + } else { + GameEvents.SendCustomGameEventToServer( "cosmetic_abilities_try_activate", { unit: currentUnit, ability: abilityName } ) + } + } + } ) + + var panel = this.image + + this.image.SetPanelEvent( "onmouseover", function() { + $.DispatchEvent( "DOTAShowAbilityTooltip", panel, abilityName ) + } ) + this.image.SetPanelEvent( "onmouseout", function() { + $.DispatchEvent( "DOTAHideAbilityTooltip", panel ) + } ) + + this.cooldown = $.CreatePanel( "Panel", this.image, "Cooldown" ) + this.cooldownEffect = $.CreatePanel( "Panel", this.cooldown, "CooldownEffect" ) + this.cooldownEffect.style["opacity-mask"] = "url( '" + image_path + "' )" + this.cooldownCountdown = $.CreatePanel( "Label", this.cooldown, "CooldownCountdown" ) + + if ( !ABILITIES_CANT_BE_REMOVED[abilityName] ) { + var deleteButton = $.CreatePanel( "Button", this.image, "DeleteButton" ) + + deleteButton.SetPanelEvent( "onactivate", function() { + if ( Entities.IsControllableByPlayer( currentUnit, Players.GetLocalPlayer() ) ) { + GameEvents.SendCustomGameEventToServer( "cosmetic_abilities_delete", { unit: currentUnit, ability: abilityName } ) + } + } ) + } + + this.Update = function() { + var ability = Entities.GetAbilityByName( currentUnit, this.abilityName ) + + if ( !Abilities.IsCooldownReady( ability ) ) { + var remaining = Abilities.GetCooldownTimeRemaining( ability ) + var progress = remaining / Abilities.GetCooldownLength( ability ) * -360 + + this.cooldown.style.visibility = "visible" + this.cooldownEffect.style.clip = "radial( 50% 75%, 0deg, " + progress + "deg )" + this.cooldownCountdown.text = Math.ceil( remaining ) + } else { + this.cooldown.style.visibility = "collapse" + } + } + + this.Delete = function() { + this.image.DeleteAsync( 0 ) + } +} + +function Slot( parent, index, style ) { + this.panel = $.CreatePanel( "Panel", parent, "Slot" + index ) + this.panel.AddClass( style ) + + this.Update = function() { + if ( this.content && this.content.Update ) { + this.content.Update() + } + } + + this.Clear = function() { + if ( this.content ) { + this.content.Delete() + this.content = null + } + } + + this.AddContent = function( content ) { + this.Clear() + this.content = content + } +} + +function Reload() { + currentUnit = Players.GetLocalPlayerPortraitUnit() + + for ( i in slots ) { + var slot = slots[i] + slot.Clear() + } + + var visible_abilities = 0 + + for ( var i = 0; i < Entities.GetAbilityCount( currentUnit ); i++ ) { + var ability = Entities.GetAbility( currentUnit, i ) + var name = Abilities.GetAbilityName( ability ) + + if ( !Abilities.IsHidden( ability ) && i < 6 ) { + visible_abilities++ + } + + if ( cosmeticAbilities[name] ) { + var permSlot = permanentAbilitySlots[name] + + if ( permSlot ) { + slots[permSlot].AddContent( new Ability( slots[permSlot], name ) ) + } else { + for ( s in slots ) { + var slot = slots[s] + + if ( !slot.content ) { + slot.AddContent( new Ability( slot, name ) ) + break + } + } + } + } + } + + if ( Entities.IsRealHero( currentUnit ) ) { + $( "#CosmeticMenu" ).style.visibility = "visible" + } else { + $( "#CosmeticMenu" ).style.visibility = "collapse" + } + + if ( visible_abilities > 4 ) { + $( "#BarOverAbilities" ).AddClass( "FiveAbilities" ) + } else { + $( "#BarOverAbilities" ).RemoveClass( "FiveAbilities" ) + } +} + +function Update() { + if ( Players.GetLocalPlayerPortraitUnit() != currentUnit ) { + Reload() + } else { + for ( i in slots ) { + var slot = slots[i] + slot.Update() + } + } + + $.Schedule( 1 / 60, Update ) +} + +for ( var i = 0; i < 7; i++ ) { + if ( i > 3 ) { + slots[i] = new Slot( $( "#BarOverItems" ), i, "SlotOverItems" ) + } else { + slots[i] = new Slot( $( "#BarOverAbilities" ), i, "SlotOverAbility" ) + } + +} + +GameEvents.Subscribe( "cosmetic_abilities_reload_hud", Reload ) + +Update() + +function CreateAbilityToTake( row, abilityName ) { + var image = $.CreatePanel( "Image", row, "ImagePreview" ) + image.SetImage( IMAGES[abilityName] || "file://{images}/spellicons/consumables/" + abilityName + ".png") + + animation[abilityName] = $.CreatePanel( "Panel", $( "#AnimationContainer" ), "" ) + animation[abilityName].BLoadLayoutFromString( '', false, false ) + animation[abilityName].visible = false + + image.SetPanelEvent( "onactivate", function() { + if ( Entities.IsControllableByPlayer( currentUnit, Players.GetLocalPlayer() ) ) { + GameEvents.SendCustomGameEventToServer( "cosmetic_abilities_take", { unit: currentUnit, ability: abilityName } ) + } + } ) + + image.SetPanelEvent( "onmouseover", function() { + $.DispatchEvent( "DOTAShowAbilityTooltip", image, abilityName ) + + animation[abilityName].visible = true + } ) + + image.SetPanelEvent( "onmouseout", function() { + $.DispatchEvent( "DOTAHideAbilityTooltip", image ) + + animation[abilityName].visible = false + } ) +} + +function CreateAbilitiesToTake() { + var abilities_row = null + + for ( var i = 0; i < abilitiesToTake.length; i++ ) { + if ( i % 4 == 0 ) { + abilities_row = $.CreatePanel( "Panel", $( "#CosmeticAbilitiesContainer" ), "" ) + abilities_row.AddClass( "AbilitiesRow" ) + } + + CreateAbilityToTake( abilities_row, abilitiesToTake[i] ) + } +} + +CreateAbilitiesToTake() \ No newline at end of file diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.xml b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.xml new file mode 100644 index 0000000..96a9b64 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/cosmetic_abilities.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/creator.js b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/creator.js new file mode 100644 index 0000000..e85d2a5 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/cosmetic_abilities/creator.js @@ -0,0 +1,57 @@ +var COSMETIC_ABILITIES = { + "high_five": true, + "seasonal_ti9_banner": true, + "seasonal_summon_cny_balloon": true, + "seasonal_summon_dragon": true, + "seasonal_summon_cny_tree": true, + "seasonal_firecrackers": true, + "seasonal_ti9_shovel": true, + "seasonal_ti9_instruments": true, + "seasonal_ti9_monkey": true, + "seasonal_summon_ti9_balloon": true, + "seasonal_throw_snowball": true, + "seasonal_festive_firework": true, + "seasonal_decorate_tree": true, + "seasonal_summon_snowman": true +} + +var hud = $.GetContextPanel().GetParent().GetParent().GetParent() +var lower_hud = hud.FindChildTraverse( "HUDElements" ).FindChild( "lower_hud" ) +var center_with_stats = lower_hud.FindChild( "center_with_stats" ) +var center_block = center_with_stats.FindChild( "center_block" ) +var buff_container = lower_hud.FindChild( "BuffContainer" ) + +lower_hud.style.height = "100%" +center_with_stats.style.height = "100%" +center_block.style.height = "100%" + +buff_container.FindChild( "buffs" ).style.transform = "translateY( -50px )" +buff_container.FindChild( "debuffs" ).style.transform = "translateY( -50px )" + +if ( !center_block.FindChild( "CosmeticAbilities" ) ) { + var newPanel = $.CreatePanel( "Panel", center_block, "CosmeticAbilities" ) + newPanel.BLoadLayout( "file://{resources}/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.xml", false, false ) + center_block.MoveChildBefore( newPanel, center_block.FindChild( "center_bg" ) ) +} + +center_block.FindChildrenWithClassTraverse( "TertiaryAbilityContainer" )[0].style.visibility = "collapse" + +var abilities_panel = center_block.FindChild( "AbilitiesAndStatBranch" ).FindChildTraverse( "abilities" ) + +function HideAbilities() { + var abilities = abilities_panel.Children() + + for ( i in abilities ) { + var ability_name = abilities[i].FindChildTraverse( "AbilityImage" ).abilityname + + if ( COSMETIC_ABILITIES[ability_name] ) { + abilities[i].style.visibility = "collapse" + } else { + abilities[i].style.visibility = "visible" + } + } + + $.Schedule( 0.001, HideAbilities ) +} + +HideAbilities() \ No newline at end of file diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.css b/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.css new file mode 100644 index 0000000..9b05078 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.css @@ -0,0 +1,21 @@ +.MessageRoot{ + width: 100%; + height: 100%; +} + +#Content { + margin: 300px 100px; + vertical-align: bottom; + flow-children: down; +} + +.Message { + padding: 5px; +} + +.Message Label { + color: #fff; + width: 500px; + text-shadow: #000 0px 0px 2px 2.0; + font-size: 20px; +} \ No newline at end of file diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.js b/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.js new file mode 100644 index 0000000..18cb819 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.js @@ -0,0 +1,31 @@ +"use strict"; + +function ShowMessage(msg, duration, params, color) { + color = color || "#fff"; + + var msgPanel = $.CreatePanel("Panel", $("#Content"), ""); + msgPanel.AddClass("Message"); + + var label = $.CreatePanel("Label", msgPanel, ""); + + if (params) { + for(var i in params) { + var v = params[i]; + if (typeof v === 'number') { + label.SetDialogVariableInt(i, v); + } else { + label.SetDialogVariable(i, $.Localize(String(v))); + } + } + } + + label.html = true; + label.text = $.Localize(msg, label).replace(/%%/g,"%"); + label.style.color = color; + + msgPanel.DeleteAsync(duration); +} + +GameEvents.Subscribe("show_message", function (data) { + ShowMessage(data.msg, data.duration || 5, data.params, data.color); +}) diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.xml b/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.xml new file mode 100644 index 0000000..cc3a55e --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/message/message.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.css b/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.css new file mode 100644 index 0000000..d5f4433 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.css @@ -0,0 +1,287 @@ +.PaymentRoot{ + width: 800px; + height: 500px; + background-color: #151515; + border-radius: 5px; + border: 1px solid #555; +} + + +.line { + flow-children: right; +} +.center { + horizontal-align: center; +} +.margin-sm { + margin: 10px; +} + +#PetInputPage, +#InputPage, +#HtmlPage { + horizontal-align: center; + vertical-align: middle; + transition-property: opacity, transform; + transition-duration: 0.3s; +} + +#PetInputPage, +#InputPage { + flow-children: down; +} + +#PetInputPage .t1, +#InputPage .t1 { + horizontal-align: center; + margin: 10px; +} + +#PetInputPage .t2, +#InputPage .t2 { + horizontal-align: center; + flow-children: right; +} + +#PetTextPanel, +#AvalonCoinPanel { + width: 120px; + background-color: #000; + vertical-align: middle; + border: 1px solid #FFED83; +} + +#PetLevelText, +#AvalonCoin{ + width: fit-children; + background-color: none; + text-align: right; + text-overflow: clip; + horizontal-align: center; + border: 0; + transform: translateX(6px); + font-size: 25px; + min-width: 20px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); +} + +#PetInputPage .title, +#InputPage .title{ + font-size: 30px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); + vertical-align: middle; +} + +#PetPrePayButton, +#PrePayButton{ + horizontal-align: center; + margin: 30px; + flow-children: down; +} + +#PetPrePayButton .title, +#PrePayButton .title{ + font-size: 20px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); + text-shadow: #000 0px 0px 4px 1.0; + margin-top: 15px; + margin-bottom: 15px; + horizontal-align: center; +} + +#PetPrePayButton .pay-btn, +#PrePayButton .pay-btn{ + padding: 10px 15px; + flow-children: right; + border-radius: 5px; +} + +#PetPrePayButton .pay-btn.alipay, +#PrePayButton .pay-btn.alipay{ + background-color: #108EE9; +} + +#PetPrePayButton .pay-btn.wechatpay, +#PrePayButton .pay-btn.wechatpay{ + background-color: #3eb94e; + margin-left: 10px; +} + +#PetPrePayButton .pay-btn Image, +#PrePayButton .pay-btn Image{ + width: 20px; + height: 20px; +} + +#PetPrePayButton .pay-btn Label, +#PrePayButton .pay-btn Label{ + font-size: 20px; + color: #fff; + margin-left: 5px; +} + +#PetPrePayButton .pay-btn.alipay:hover, +#PrePayButton .pay-btn.alipay:hover{ + box-shadow: #108EE9 0px 0px 4px; +} + +#PetPrePayButton .pay-btn.wechatpay:hover, +#PrePayButton .pay-btn.wechatpay:hover{ + box-shadow: #3eb94e 0px 0px 4px; +} + +#PetPrePayButton .pay-btn:active, +#PrePayButton .pay-btn:active{ + transform: scale3d(0.9,0.9,1); +} + +#AddPetLevel, +#AddAvalonCoin{ + horizontal-align: center; + margin-top: 15px; + flow-children: right; +} + +#AddPetLevel .btn, +#AddAvalonCoin .btn{ + background-color: #414141; + padding: 2px 4px; + border-radius: 5px; + margin: 0px 3px; +} + +#AddPetLevel .btn Label, +#AddAvalonCoin .btn Label{ + margin-top: 3px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); +} + +#AddPetLevel .btn:hover, +#AddAvalonCoin .btn:hover{ + brightness: 2; +} + +#AddPetLevel .btn:active, +#AddAvalonCoin .btn:active{ + transform: scale3d(0.9,0.9,1); +} + +/*=======================*/ +#HtmlPage { + flow-children: down; +} + +#HtmlPage .btn{ + background-color: #414141; + padding: 2px 4px; + border-radius: 5px; + margin: 0px 3px; + horizontal-align: center; +} + +#HtmlPage .btn Label{ + margin-top: 3px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); +} + +#HtmlPage .btn:hover{ + brightness: 2; +} + +#HtmlPage .btn:active{ + transform: scale3d(0.9,0.9,1); +} + + +#Html { + width: 700px; + height: 360px; + background-color: #000; +} + +#GoBackButton { + padding: 5px 15px; + horizontal-align: center; + border-radius: 3px; + margin-top: 10px; + background-color: gradient(linear, 0% 100%, 0% 0%, from(#3F0600), to(#5B0900)); +} +#GoBackButton:hover { + box-shadow: #A41C00 0px 0px 5px; +} +#GoBackButton Label { + color: #fff; +} + +#CloseButton { + width: 30px; + height: 30px; + background-image: url("s2r://panorama/images/control_icons/x_close_png.vtex"); + background-repeat: no-repeat; + background-size: 100%; + wash-color: #eee2dd; + margin-top: 2px; + margin-right: 5px; + horizontal-align: right; + vertical-align: top; +} + +#CloseButton:hover { + wash-color: #fac900; +} + +#PageModeToggle { + horizontal-align: center; + vertical-align: top; + margin-top: 30px; +} + +#PageModeToggle { + flow-children: right; +} + +#PageModeToggle RadioButton { + margin-right: 10px; +} + +#PageModeToggle RadioButton .TickBox { + background-color: #0000; + border: 2px solid #6c9fcb; + box-shadow: #abffff 0px 0px 0px; +} + +#PageModeToggle RadioButton Label { + color: #EEE; +} +#PageModeToggle RadioButton:selected Label { + color: #fff; +} + +#PageModeToggle RadioButton:selected .TickBox { + background-color: #abffff; + border: 2px solid #6c9fcb; + box-shadow: #abffff 0px 0px 6px; +} + + +#PayQrPanel { + width: 700px; + height: 360px; + background-color: #000; + horizontal-align: center; + vertical-align: middle; +} + +#PayQrPanel Label { + color: #EEE; + font-size: 25px; + horizontal-align: center; + vertical-align: middle; +} + +#PayQrImg { + width: 312px; + height: 312px; + horizontal-align: center; + vertical-align: middle; +} diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.js b/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.js new file mode 100644 index 0000000..e4ede3b --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.js @@ -0,0 +1,279 @@ +"use strict"; + +var PayAmount = 0; +var Price = 0.0; + +var PetPayAmount = 0; +var PetPrice = 0; + +var IsInQuery = false; +var SelectedPage = 1; + +var BackTimeOutCount = 60; +var EndTimeOutCount = 300; + +function ShowMessage(text, time, iserror, marginTop) { + var label = $.CreatePanel("Label", $.GetContextPanel(), ""); + label.style["background-color"] = iserror ? "#A22C00" : "#000E"; + label.style["color"] = iserror ? "#fff" : "#F37702"; + label.style["padding"] = "20px"; + label.style["font-size"] = "30px"; + if (marginTop) label.style["margin-top"] = marginTop; + label.style["horizontal-align"] = "center"; + label.style["vertical-align"] = "center"; + label.html = true; + label.text = text; + label.DeleteAsync(time > 0 ? time : 3); +} + +function PageModeSelect(type) { + if (type === "fairy") { + SelectedPage = 1; + $("#InputPage").visible = true; + $("#PetInputPage").visible = false; + $("#HtmlPage").visible = false; + $("#AvalonCoin").text = ""; + return; + } + if (type === "pet") { + SelectedPage = 2; + $("#InputPage").visible = false; + $("#PetInputPage").visible = true; + $("#HtmlPage").visible = false; + $("#PetLevelText").text = ""; + return; + } + +} + +function ShowModeSelectTip(index) { + $.DispatchEvent("DOTAShowTextTooltip", $("#PageModeToggle").GetChild(index), $.Localize("#pay_tip_mode" + index)); +} + +function HideModeSelectTip() { + $.DispatchEvent("DOTAHideTextTooltip") +} + +function GoBack() { + $("#InputPage").visible = SelectedPage === 1; + $("#PetInputPage").visible = SelectedPage === 2; + $("#HtmlPage").visible = false; + $("#AvalonCoin").text = ""; + $("#PetLevelText").text = ""; + EndTimeOutCount = 0; + BackTimeOutCount = 0; + GameEvents.SendCustomGameEventToServer("custom_game_pay_auto_query", {}); +} + +function Pay( method, type ) { + if ((!Price || Price < 0) && (!PetPrice || PetPrice < 0)) return; + + $("#PayQrImg").SetImage(""); + $("#InputPage").visible = false; + $("#PetInputPage").visible = false; + $("#HtmlPage").visible = true; + $("#CompleteButton").visible = false; + $("#GoBackButton").enabled = false; + + BackTimeOutCount = 30; + GoBackTimeCount(); + + EndTimeOutCount = 300; + EndBackTimeCount() + + var payType = 2; + if (method === "alipay") { + payType = 1; + } else if (method === "wechatpay") { + payType = 2; + } + + if (type === "fairy") { + GameEvents.SendCustomGameEventToServer("custom_game_pay_select", {"method":payType, "amount":PayAmount, "price":Price * 100, "type": type}); + } else if (type === "pet") { + GameEvents.SendCustomGameEventToServer("custom_game_pay_select", {"method":payType, "amount":PetPayAmount, "price":PetPrice * 100, "type": type}); + } + +} + +function GoBackTimeCount() { + if (BackTimeOutCount <= 0) { + $("#GoBackButton").enabled = true; + $("#GoBackButton").SetDialogVariableInt("timeout", 0) + $("#CompleteButton").visible = true; + return; + } + BackTimeOutCount--; + $("#GoBackButton").SetDialogVariableInt("timeout", BackTimeOutCount) + $.Schedule(1, GoBackTimeCount); +} + + +function EndBackTimeCount() { + if (EndTimeOutCount <= 0) { + return; + } + EndTimeOutCount--; + var m = Math.floor(EndTimeOutCount/60); + var s = EndTimeOutCount%60; + var note = m > 0 ? (m + "分") : ""; + note += s > 0 ? (s + "秒") : ""; + if (note === "") note = "0秒(已过期,请返回)"; + $("#HtmlPage").SetDialogVariable("LeftTime", note); + $.Schedule(1, EndBackTimeCount); +} + +function PayComplete() { + if (IsInQuery) return; + IsInQuery = true; + GameEvents.SendCustomGameEventToServer("custom_game_pay_query", {}); +} + + + + +// 文本改变 +var AvalonCoinLock = false; +function OnAvalonCoinChange() { + if (AvalonCoinLock) return; + AvalonCoinLock=true; + + var AvalonCoin = $("#AvalonCoin"); + var text = AvalonCoin.text; + var m = text.replace(/\D+/g,""); + var amount = parseInt(m); + if (amount >= 10) { + AvalonCoin.text = parseInt(m); + } + else { + amount = 0; + AvalonCoin.text = ""; + } + + if (amount > 0) { + // $("#PrePayButton").SetDialogVariable("Amount",((100 * amount / 2 - RandomInt(0, 100))/100).toFixed(2).toString()); + if (PayAmount != amount) { + PayAmount = amount; + Price = (100 * amount * 0.5 - RandomInt(0, 100))/100; + $("#PrePayButton").SetDialogVariable("Amount",Price.toString()); + $("#HtmlPage").SetDialogVariable("Amount",Price.toString()); + } + } else { + PayAmount = 0; + Price = 0.0; + $("#PrePayButton").SetDialogVariable("Amount", "0"); + $("#HtmlPage").SetDialogVariable("Amount", "0"); + } + AvalonCoinLock = false; +} + +// 文本改变 +var PetlevelLock = false; +function OnPetLevelChange() { + if (PetlevelLock) return; + PetlevelLock=true; + + var PetLevelText = $("#PetLevelText"); + var text = PetLevelText.text; + var m = text.replace(/\D+/g,""); + var amount = parseInt(m); + if (amount > 0) { + PetLevelText.text = parseInt(m); + } + else { + amount = 0; + PetLevelText.text = ""; + } + + if (amount > 0) { + if (PetPayAmount != amount) { + PetPayAmount = amount; + PetPrice = (100 * amount * 30 - RandomInt(0, 100))/100; + $("#PetPrePayButton").SetDialogVariable("Amount",PetPrice.toString()); + $("#HtmlPage").SetDialogVariable("Amount",PetPrice.toString()); + } + } else { + PetPayAmount = 0; + PetPrice = 0.0; + $("#PetPrePayButton").SetDialogVariable("Amount", "0"); + $("#HtmlPage").SetDialogVariable("Amount", "0"); + } + PetlevelLock = false; +} + +function RandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function AddAvalonCoin(num) { + var AvalonCoin = $("#AvalonCoin"); + var text = AvalonCoin.text; + var amount = parseInt(text); + if (!amount || amount < 0) amount = 0; + if (num > 0) + AvalonCoin.text = (amount + num); + else + AvalonCoin.text = ""; +} + +function AddPetLevel(num) { + var PetLevelText = $("#PetLevelText"); + var text = PetLevelText.text; + var amount = parseInt(text); + if (!amount || amount < 0) amount = 0; + if (num > 0) + PetLevelText.text = (amount + num); + else + PetLevelText.text = ""; +} + +var closeHandle = null; +function Close() { + if (closeHandle) { + closeHandle(); + } +} + +function AvalonCoinInputFocus(){ + // $("#PetTextPanel").SetFocus(); +} + +function PetInputFocus() { + $("#PetLevelText").SetFocus(); +} + + +;(function(){ + $("#PrePayButton").SetDialogVariable("Amount", "0"); + $("#HtmlPage").SetDialogVariable("Amount", "0"); + $("#HtmlPage").visible = false; + $("#AvalonCoin").enabled = false; + $("#InputPage").visible = SelectedPage === 1; + $("#PetInputPage").visible = SelectedPage === 2; + + $.GetContextPanel().OnClose = function (f) { closeHandle = f }; + + GameEvents.Subscribe("thtd_pay_post", function (data){ + if (data.code === 1) { + // $("#Html").SetURL(data.url);占用性能 + // $("#PayQrImg").SetImage(data.url); + $("#PayQrImg").SetImage(data.method === 1 ? "s2r://panorama/images/custom_game/qr_alipay_png.vtex" : "s2r://panorama/images/custom_game/qr_wechat_png.vtex"); + } else { + // $("#Html").SetURL("https://qrcode/error.jpg"); + $("#PayQrImg").SetImage(""); + ShowMessage("获取支付码失败," + data.msg, 10, true); + } + }); + + GameEvents.Subscribe("thtd_pay_result", function (data){ + IsInQuery = false; + if (data.code === "0000") { + ShowMessage(data.msg, 5); + Game.EmitSound("Quest.Completed"); + BackTimeOutCount = 0; + } else { + ShowMessage(data.msg, 5, true); + } + }); + +})(); diff --git a/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.xml b/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.xml new file mode 100644 index 0000000..fcb110e --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/frames/payment/payment.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/english_localization_set/panorama_decompile/decompiled_layout/info.xml b/english_localization_set/panorama_decompile/decompiled_layout/info.xml new file mode 100644 index 0000000..aa426a1 --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/info.xml @@ -0,0 +1,516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/english_localization_set/panorama_decompile/decompiled_layout/team_select.xml b/english_localization_set/panorama_decompile/decompiled_layout/team_select.xml new file mode 100644 index 0000000..994e81b --- /dev/null +++ b/english_localization_set/panorama_decompile/decompiled_layout/team_select.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/custom_loading_screen.xml b/panorama/layout/custom_game/custom_loading_screen.xml new file mode 100644 index 0000000..3d6a3a3 --- /dev/null +++ b/panorama/layout/custom_game/custom_loading_screen.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/panorama/layout/custom_game/custom_ui_manifest.xml b/panorama/layout/custom_game/custom_ui_manifest.xml new file mode 100644 index 0000000..ed0f662 --- /dev/null +++ b/panorama/layout/custom_game/custom_ui_manifest.xml @@ -0,0 +1,46 @@ + + + + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/dps_panel.xml b/panorama/layout/custom_game/dps_panel.xml new file mode 100644 index 0000000..f6ad793 --- /dev/null +++ b/panorama/layout/custom_game/dps_panel.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/end_screen.xml b/panorama/layout/custom_game/end_screen.xml new file mode 100644 index 0000000..da35880 --- /dev/null +++ b/panorama/layout/custom_game/end_screen.xml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.css b/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.css new file mode 100644 index 0000000..c3bbeb1 --- /dev/null +++ b/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.css @@ -0,0 +1,172 @@ +#CosmeticMenu { + horizontal-align: center; + vertical-align: bottom; + flow-children: down; + width: 100%; + height: 30px; + margin-right: 58px; + margin-left: 58px; + margin-bottom: 145px; + transition-property: height; + transition-duration: 0.2s; +} + +.Open #CosmeticMenu { + height: 400px; +} + +#CosmeticMenuButton { + horizontal-align: right; + width: 60px; + height: 30px; + border-radius: 5px 5px 0px 0px; + background-color: "#000E"; + background-image: url( "s2r://panorama/images/backgrounds/background_play_button_2x_png.vtex" ); +} + +#CosmeticMenuButtonText { + color: #ffffff; + horizontal-align: center; + vertical-align: middle; + font-size: 20px; +} + +#CosmeticMenuMain { + horizontal-align: center; + vertical-align: bottom; + width: 100%; + height: 100%; + background-color: #263238; + background-image: url( "s2r://panorama/images/hud/reborn/ability_bg_psd.vtex" ); + background-size: 100%; + padding: 30px 60px 60px; +} + +#CosmeticContainer { + flow-children: right; + horizontal-align: center; + vertical-align: center; + height: 100%; +} + +#AnimationPanel { + width: 185px; + height: 280px; +} + +#AnimationContainer { + width: 100%; + height: 100%; + background-color: rgba( 8, 8, 8, 0.9 ); +} + +#BorderAnimation { + width: 100%; + height: 100%; + background-image: url( "s2r://panorama/images/hud/passive_ability_border_png.vtex" ); + background-size: 100% 100%; +} + +#CosmeticAbilitiesContainer { + margin-left: 30px; + flow-children: down; +} + +.AbilitiesRow { + margin-top: 10px; + flow-children: right; +} + +#ImagePreview { + margin: 0px 3px; + width: 60px; + height: 60px; +} + +#BarOverAbilities { + vertical-align: bottom; + margin-left: 285px; + margin-bottom: 145px; + flow-children: right; +} + +#BarOverAbilities.FiveAbilities { + margin-left: 283px; +} + +.SlotOverAbility { + vertical-align: bottom; + width: 58px; + height: 58px; + margin-right: 7px; +} + +.FiveAbilities .SlotOverAbility { + vertical-align: bottom; + width: 54px; + height: 54px; + margin-right: 4px; +} + +#BarOverItems { + horizontal-align: right; + vertical-align: bottom; + margin-right: 59px; + margin-bottom: 145px; + flow-children: right; +} + +.SlotOverItems { + vertical-align: bottom; + min-width: 60px; + min-height: 60px; + margin-left: 5px; +} + +#Image { + horizontal-align: center; + vertical-align: bottom; +} + +.SlotOverAbility #Image { + width: 58px; + height: 58px; +} + +.SlotOverItems #Image { + width: 60px; + height: 60px; +} + +#Cooldown { + width: 100%; + height: 100%; +} + +#CooldownEffect { + width: 100%; + height: 100%; + background-color: rgba( 0, 0, 0, 0.85 ); +} + +#CooldownCountdown { + vertical-align: bottom; + padding: 0px 2px; + text-shadow: 2px 2px 4px 2 black; + font-size: 16px; + color: #999; +} + +#DeleteButton { + visibility: collapse; + horizontal-align: right; + background-color: rgb( 11, 11, 11 ); + background-image: url( "s2r://panorama/images/control_icons/x_close_png.vtex" ); + width: 16px; + height: 16px; + background-size: 16px 16px; +} + +.Open #DeleteButton { + visibility: visible; +} \ No newline at end of file diff --git a/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.js b/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.js new file mode 100644 index 0000000..07aad11 --- /dev/null +++ b/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.js @@ -0,0 +1,268 @@ +var IMAGES = {} +var cosmeticAbilities = { + "high_five": true, + "seasonal_ti9_banner": true, + "seasonal_summon_cny_balloon": true, + "seasonal_summon_dragon": true, + "seasonal_summon_cny_tree": true, + "seasonal_firecrackers": true, + "seasonal_ti9_shovel": true, + "seasonal_ti9_instruments": true, + "seasonal_ti9_monkey": true, + "seasonal_summon_ti9_balloon": true, + "seasonal_throw_snowball": true, + "seasonal_festive_firework": true, + "seasonal_decorate_tree": true, + "seasonal_summon_snowman": true +} +var permanentAbilitySlots = { + "high_five": 4, + "seasonal_ti9_banner": 5 +} +var abilitiesToTake = [ + "seasonal_summon_cny_balloon", + "seasonal_summon_dragon", + "seasonal_summon_cny_tree", + "seasonal_firecrackers", + "seasonal_ti9_shovel", + "seasonal_ti9_instruments", + "seasonal_ti9_monkey", + "seasonal_summon_ti9_balloon", + "seasonal_throw_snowball", + "seasonal_festive_firework", + "seasonal_decorate_tree", + "seasonal_summon_snowman" +] +var ABILITIES_CANT_BE_REMOVED = { + "high_five": true, + "seasonal_ti9_banner": true, +} +var abillity_name_to_webm = { + "seasonal_summon_cny_balloon":"40XJ9", + "seasonal_summon_dragon":"Ry9Mv", + "seasonal_summon_cny_tree":"rVY9D", + "seasonal_firecrackers":"Lxe64", + "seasonal_ti9_shovel":"b1dNv", + "seasonal_ti9_instruments":"5d1Rr", + "seasonal_ti9_monkey":"XEjX7", + "seasonal_summon_ti9_balloon":"BAEq9", + "seasonal_throw_snowball":"8mBLL", + "seasonal_festive_firework":"voBa5", + "seasonal_decorate_tree":"Px17L", + "seasonal_summon_snowman":"na38r" +} +var showcaseAbilitiesSlot = 6 + +var slots = [] + +var currentUnit = null +var currentAbilitiesCount = 0 +var animation = {} + +function ToggleCosmeticMenu() { + $.GetContextPanel().ToggleClass( "Open" ) +} + +function Ability( slot, abilityName ) { + this.abilityName = abilityName + + var image_path = IMAGES[abilityName] || "file://{images}/spellicons/consumables/" + abilityName + ".png" + + this.image = $.CreatePanel( "Image", slot.panel, "Image" ) + this.image.SetImage( image_path ) + + this.image.SetPanelEvent( "onactivate", function() { + if ( Entities.IsControllableByPlayer( currentUnit, Players.GetLocalPlayer() ) ) { + var ability = Entities.GetAbilityByName( currentUnit, abilityName ) + + if ( Abilities.IsActivated( ability ) ) { + Abilities.ExecuteAbility( ability, currentUnit, false ) + } else { + GameEvents.SendCustomGameEventToServer( "cosmetic_abilities_try_activate", { unit: currentUnit, ability: abilityName } ) + } + } + } ) + + var panel = this.image + + this.image.SetPanelEvent( "onmouseover", function() { + $.DispatchEvent( "DOTAShowAbilityTooltip", panel, abilityName ) + } ) + this.image.SetPanelEvent( "onmouseout", function() { + $.DispatchEvent( "DOTAHideAbilityTooltip", panel ) + } ) + + this.cooldown = $.CreatePanel( "Panel", this.image, "Cooldown" ) + this.cooldownEffect = $.CreatePanel( "Panel", this.cooldown, "CooldownEffect" ) + this.cooldownEffect.style["opacity-mask"] = "url( '" + image_path + "' )" + this.cooldownCountdown = $.CreatePanel( "Label", this.cooldown, "CooldownCountdown" ) + + if ( !ABILITIES_CANT_BE_REMOVED[abilityName] ) { + var deleteButton = $.CreatePanel( "Button", this.image, "DeleteButton" ) + + deleteButton.SetPanelEvent( "onactivate", function() { + if ( Entities.IsControllableByPlayer( currentUnit, Players.GetLocalPlayer() ) ) { + GameEvents.SendCustomGameEventToServer( "cosmetic_abilities_delete", { unit: currentUnit, ability: abilityName } ) + } + } ) + } + + this.Update = function() { + var ability = Entities.GetAbilityByName( currentUnit, this.abilityName ) + + if ( !Abilities.IsCooldownReady( ability ) ) { + var remaining = Abilities.GetCooldownTimeRemaining( ability ) + var progress = remaining / Abilities.GetCooldownLength( ability ) * -360 + + this.cooldown.style.visibility = "visible" + this.cooldownEffect.style.clip = "radial( 50% 75%, 0deg, " + progress + "deg )" + this.cooldownCountdown.text = Math.ceil( remaining ) + } else { + this.cooldown.style.visibility = "collapse" + } + } + + this.Delete = function() { + this.image.DeleteAsync( 0 ) + } +} + +function Slot( parent, index, style ) { + this.panel = $.CreatePanel( "Panel", parent, "Slot" + index ) + this.panel.AddClass( style ) + + this.Update = function() { + if ( this.content && this.content.Update ) { + this.content.Update() + } + } + + this.Clear = function() { + if ( this.content ) { + this.content.Delete() + this.content = null + } + } + + this.AddContent = function( content ) { + this.Clear() + this.content = content + } +} + +function Reload() { + currentUnit = Players.GetLocalPlayerPortraitUnit() + + for ( i in slots ) { + var slot = slots[i] + slot.Clear() + } + + var visible_abilities = 0 + + for ( var i = 0; i < Entities.GetAbilityCount( currentUnit ); i++ ) { + var ability = Entities.GetAbility( currentUnit, i ) + var name = Abilities.GetAbilityName( ability ) + + if ( !Abilities.IsHidden( ability ) && i < 6 ) { + visible_abilities++ + } + + if ( cosmeticAbilities[name] ) { + var permSlot = permanentAbilitySlots[name] + + if ( permSlot ) { + slots[permSlot].AddContent( new Ability( slots[permSlot], name ) ) + } else { + for ( s in slots ) { + var slot = slots[s] + + if ( !slot.content ) { + slot.AddContent( new Ability( slot, name ) ) + break + } + } + } + } + } + + if ( Entities.IsRealHero( currentUnit ) ) { + $( "#CosmeticMenu" ).style.visibility = "visible" + } else { + $( "#CosmeticMenu" ).style.visibility = "collapse" + } + + if ( visible_abilities > 4 ) { + $( "#BarOverAbilities" ).AddClass( "FiveAbilities" ) + } else { + $( "#BarOverAbilities" ).RemoveClass( "FiveAbilities" ) + } +} + +function Update() { + if ( Players.GetLocalPlayerPortraitUnit() != currentUnit ) { + Reload() + } else { + for ( i in slots ) { + var slot = slots[i] + slot.Update() + } + } + + $.Schedule( 1 / 60, Update ) +} + +for ( var i = 0; i < 7; i++ ) { + if ( i > 3 ) { + slots[i] = new Slot( $( "#BarOverItems" ), i, "SlotOverItems" ) + } else { + slots[i] = new Slot( $( "#BarOverAbilities" ), i, "SlotOverAbility" ) + } + +} + +GameEvents.Subscribe( "cosmetic_abilities_reload_hud", Reload ) + +Update() + +function CreateAbilityToTake( row, abilityName ) { + var image = $.CreatePanel( "Image", row, "ImagePreview" ) + image.SetImage( IMAGES[abilityName] || "file://{images}/spellicons/consumables/" + abilityName + ".png") + + animation[abilityName] = $.CreatePanel( "Panel", $( "#AnimationContainer" ), "" ) + animation[abilityName].BLoadLayoutFromString( '', false, false ) + animation[abilityName].visible = false + + image.SetPanelEvent( "onactivate", function() { + if ( Entities.IsControllableByPlayer( currentUnit, Players.GetLocalPlayer() ) ) { + GameEvents.SendCustomGameEventToServer( "cosmetic_abilities_take", { unit: currentUnit, ability: abilityName } ) + } + } ) + + image.SetPanelEvent( "onmouseover", function() { + $.DispatchEvent( "DOTAShowAbilityTooltip", image, abilityName ) + + animation[abilityName].visible = true + } ) + + image.SetPanelEvent( "onmouseout", function() { + $.DispatchEvent( "DOTAHideAbilityTooltip", image ) + + animation[abilityName].visible = false + } ) +} + +function CreateAbilitiesToTake() { + var abilities_row = null + + for ( var i = 0; i < abilitiesToTake.length; i++ ) { + if ( i % 4 == 0 ) { + abilities_row = $.CreatePanel( "Panel", $( "#CosmeticAbilitiesContainer" ), "" ) + abilities_row.AddClass( "AbilitiesRow" ) + } + + CreateAbilityToTake( abilities_row, abilitiesToTake[i] ) + } +} + +CreateAbilitiesToTake() \ No newline at end of file diff --git a/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.xml b/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.xml new file mode 100644 index 0000000..96a9b64 --- /dev/null +++ b/panorama/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/panorama/layout/custom_game/frames/cosmetic_abilities/creator.js b/panorama/layout/custom_game/frames/cosmetic_abilities/creator.js new file mode 100644 index 0000000..e85d2a5 --- /dev/null +++ b/panorama/layout/custom_game/frames/cosmetic_abilities/creator.js @@ -0,0 +1,57 @@ +var COSMETIC_ABILITIES = { + "high_five": true, + "seasonal_ti9_banner": true, + "seasonal_summon_cny_balloon": true, + "seasonal_summon_dragon": true, + "seasonal_summon_cny_tree": true, + "seasonal_firecrackers": true, + "seasonal_ti9_shovel": true, + "seasonal_ti9_instruments": true, + "seasonal_ti9_monkey": true, + "seasonal_summon_ti9_balloon": true, + "seasonal_throw_snowball": true, + "seasonal_festive_firework": true, + "seasonal_decorate_tree": true, + "seasonal_summon_snowman": true +} + +var hud = $.GetContextPanel().GetParent().GetParent().GetParent() +var lower_hud = hud.FindChildTraverse( "HUDElements" ).FindChild( "lower_hud" ) +var center_with_stats = lower_hud.FindChild( "center_with_stats" ) +var center_block = center_with_stats.FindChild( "center_block" ) +var buff_container = lower_hud.FindChild( "BuffContainer" ) + +lower_hud.style.height = "100%" +center_with_stats.style.height = "100%" +center_block.style.height = "100%" + +buff_container.FindChild( "buffs" ).style.transform = "translateY( -50px )" +buff_container.FindChild( "debuffs" ).style.transform = "translateY( -50px )" + +if ( !center_block.FindChild( "CosmeticAbilities" ) ) { + var newPanel = $.CreatePanel( "Panel", center_block, "CosmeticAbilities" ) + newPanel.BLoadLayout( "file://{resources}/layout/custom_game/frames/cosmetic_abilities/cosmetic_abilities.xml", false, false ) + center_block.MoveChildBefore( newPanel, center_block.FindChild( "center_bg" ) ) +} + +center_block.FindChildrenWithClassTraverse( "TertiaryAbilityContainer" )[0].style.visibility = "collapse" + +var abilities_panel = center_block.FindChild( "AbilitiesAndStatBranch" ).FindChildTraverse( "abilities" ) + +function HideAbilities() { + var abilities = abilities_panel.Children() + + for ( i in abilities ) { + var ability_name = abilities[i].FindChildTraverse( "AbilityImage" ).abilityname + + if ( COSMETIC_ABILITIES[ability_name] ) { + abilities[i].style.visibility = "collapse" + } else { + abilities[i].style.visibility = "visible" + } + } + + $.Schedule( 0.001, HideAbilities ) +} + +HideAbilities() \ No newline at end of file diff --git a/panorama/layout/custom_game/frames/message/message.css b/panorama/layout/custom_game/frames/message/message.css new file mode 100644 index 0000000..9b05078 --- /dev/null +++ b/panorama/layout/custom_game/frames/message/message.css @@ -0,0 +1,21 @@ +.MessageRoot{ + width: 100%; + height: 100%; +} + +#Content { + margin: 300px 100px; + vertical-align: bottom; + flow-children: down; +} + +.Message { + padding: 5px; +} + +.Message Label { + color: #fff; + width: 500px; + text-shadow: #000 0px 0px 2px 2.0; + font-size: 20px; +} \ No newline at end of file diff --git a/panorama/layout/custom_game/frames/message/message.js b/panorama/layout/custom_game/frames/message/message.js new file mode 100644 index 0000000..18cb819 --- /dev/null +++ b/panorama/layout/custom_game/frames/message/message.js @@ -0,0 +1,31 @@ +"use strict"; + +function ShowMessage(msg, duration, params, color) { + color = color || "#fff"; + + var msgPanel = $.CreatePanel("Panel", $("#Content"), ""); + msgPanel.AddClass("Message"); + + var label = $.CreatePanel("Label", msgPanel, ""); + + if (params) { + for(var i in params) { + var v = params[i]; + if (typeof v === 'number') { + label.SetDialogVariableInt(i, v); + } else { + label.SetDialogVariable(i, $.Localize(String(v))); + } + } + } + + label.html = true; + label.text = $.Localize(msg, label).replace(/%%/g,"%"); + label.style.color = color; + + msgPanel.DeleteAsync(duration); +} + +GameEvents.Subscribe("show_message", function (data) { + ShowMessage(data.msg, data.duration || 5, data.params, data.color); +}) diff --git a/panorama/layout/custom_game/frames/message/message.xml b/panorama/layout/custom_game/frames/message/message.xml new file mode 100644 index 0000000..cc3a55e --- /dev/null +++ b/panorama/layout/custom_game/frames/message/message.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/frames/payment/payment.css b/panorama/layout/custom_game/frames/payment/payment.css new file mode 100644 index 0000000..d5f4433 --- /dev/null +++ b/panorama/layout/custom_game/frames/payment/payment.css @@ -0,0 +1,287 @@ +.PaymentRoot{ + width: 800px; + height: 500px; + background-color: #151515; + border-radius: 5px; + border: 1px solid #555; +} + + +.line { + flow-children: right; +} +.center { + horizontal-align: center; +} +.margin-sm { + margin: 10px; +} + +#PetInputPage, +#InputPage, +#HtmlPage { + horizontal-align: center; + vertical-align: middle; + transition-property: opacity, transform; + transition-duration: 0.3s; +} + +#PetInputPage, +#InputPage { + flow-children: down; +} + +#PetInputPage .t1, +#InputPage .t1 { + horizontal-align: center; + margin: 10px; +} + +#PetInputPage .t2, +#InputPage .t2 { + horizontal-align: center; + flow-children: right; +} + +#PetTextPanel, +#AvalonCoinPanel { + width: 120px; + background-color: #000; + vertical-align: middle; + border: 1px solid #FFED83; +} + +#PetLevelText, +#AvalonCoin{ + width: fit-children; + background-color: none; + text-align: right; + text-overflow: clip; + horizontal-align: center; + border: 0; + transform: translateX(6px); + font-size: 25px; + min-width: 20px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); +} + +#PetInputPage .title, +#InputPage .title{ + font-size: 30px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); + vertical-align: middle; +} + +#PetPrePayButton, +#PrePayButton{ + horizontal-align: center; + margin: 30px; + flow-children: down; +} + +#PetPrePayButton .title, +#PrePayButton .title{ + font-size: 20px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); + text-shadow: #000 0px 0px 4px 1.0; + margin-top: 15px; + margin-bottom: 15px; + horizontal-align: center; +} + +#PetPrePayButton .pay-btn, +#PrePayButton .pay-btn{ + padding: 10px 15px; + flow-children: right; + border-radius: 5px; +} + +#PetPrePayButton .pay-btn.alipay, +#PrePayButton .pay-btn.alipay{ + background-color: #108EE9; +} + +#PetPrePayButton .pay-btn.wechatpay, +#PrePayButton .pay-btn.wechatpay{ + background-color: #3eb94e; + margin-left: 10px; +} + +#PetPrePayButton .pay-btn Image, +#PrePayButton .pay-btn Image{ + width: 20px; + height: 20px; +} + +#PetPrePayButton .pay-btn Label, +#PrePayButton .pay-btn Label{ + font-size: 20px; + color: #fff; + margin-left: 5px; +} + +#PetPrePayButton .pay-btn.alipay:hover, +#PrePayButton .pay-btn.alipay:hover{ + box-shadow: #108EE9 0px 0px 4px; +} + +#PetPrePayButton .pay-btn.wechatpay:hover, +#PrePayButton .pay-btn.wechatpay:hover{ + box-shadow: #3eb94e 0px 0px 4px; +} + +#PetPrePayButton .pay-btn:active, +#PrePayButton .pay-btn:active{ + transform: scale3d(0.9,0.9,1); +} + +#AddPetLevel, +#AddAvalonCoin{ + horizontal-align: center; + margin-top: 15px; + flow-children: right; +} + +#AddPetLevel .btn, +#AddAvalonCoin .btn{ + background-color: #414141; + padding: 2px 4px; + border-radius: 5px; + margin: 0px 3px; +} + +#AddPetLevel .btn Label, +#AddAvalonCoin .btn Label{ + margin-top: 3px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); +} + +#AddPetLevel .btn:hover, +#AddAvalonCoin .btn:hover{ + brightness: 2; +} + +#AddPetLevel .btn:active, +#AddAvalonCoin .btn:active{ + transform: scale3d(0.9,0.9,1); +} + +/*=======================*/ +#HtmlPage { + flow-children: down; +} + +#HtmlPage .btn{ + background-color: #414141; + padding: 2px 4px; + border-radius: 5px; + margin: 0px 3px; + horizontal-align: center; +} + +#HtmlPage .btn Label{ + margin-top: 3px; + color: gradient( linear, 0% 100%, 0% 0%, from( #FFED83 ), to( #DE8B00 ) ); +} + +#HtmlPage .btn:hover{ + brightness: 2; +} + +#HtmlPage .btn:active{ + transform: scale3d(0.9,0.9,1); +} + + +#Html { + width: 700px; + height: 360px; + background-color: #000; +} + +#GoBackButton { + padding: 5px 15px; + horizontal-align: center; + border-radius: 3px; + margin-top: 10px; + background-color: gradient(linear, 0% 100%, 0% 0%, from(#3F0600), to(#5B0900)); +} +#GoBackButton:hover { + box-shadow: #A41C00 0px 0px 5px; +} +#GoBackButton Label { + color: #fff; +} + +#CloseButton { + width: 30px; + height: 30px; + background-image: url("s2r://panorama/images/control_icons/x_close_png.vtex"); + background-repeat: no-repeat; + background-size: 100%; + wash-color: #eee2dd; + margin-top: 2px; + margin-right: 5px; + horizontal-align: right; + vertical-align: top; +} + +#CloseButton:hover { + wash-color: #fac900; +} + +#PageModeToggle { + horizontal-align: center; + vertical-align: top; + margin-top: 30px; +} + +#PageModeToggle { + flow-children: right; +} + +#PageModeToggle RadioButton { + margin-right: 10px; +} + +#PageModeToggle RadioButton .TickBox { + background-color: #0000; + border: 2px solid #6c9fcb; + box-shadow: #abffff 0px 0px 0px; +} + +#PageModeToggle RadioButton Label { + color: #EEE; +} +#PageModeToggle RadioButton:selected Label { + color: #fff; +} + +#PageModeToggle RadioButton:selected .TickBox { + background-color: #abffff; + border: 2px solid #6c9fcb; + box-shadow: #abffff 0px 0px 6px; +} + + +#PayQrPanel { + width: 700px; + height: 360px; + background-color: #000; + horizontal-align: center; + vertical-align: middle; +} + +#PayQrPanel Label { + color: #EEE; + font-size: 25px; + horizontal-align: center; + vertical-align: middle; +} + +#PayQrImg { + width: 312px; + height: 312px; + horizontal-align: center; + vertical-align: middle; +} diff --git a/panorama/layout/custom_game/frames/payment/payment.js b/panorama/layout/custom_game/frames/payment/payment.js new file mode 100644 index 0000000..e4ede3b --- /dev/null +++ b/panorama/layout/custom_game/frames/payment/payment.js @@ -0,0 +1,279 @@ +"use strict"; + +var PayAmount = 0; +var Price = 0.0; + +var PetPayAmount = 0; +var PetPrice = 0; + +var IsInQuery = false; +var SelectedPage = 1; + +var BackTimeOutCount = 60; +var EndTimeOutCount = 300; + +function ShowMessage(text, time, iserror, marginTop) { + var label = $.CreatePanel("Label", $.GetContextPanel(), ""); + label.style["background-color"] = iserror ? "#A22C00" : "#000E"; + label.style["color"] = iserror ? "#fff" : "#F37702"; + label.style["padding"] = "20px"; + label.style["font-size"] = "30px"; + if (marginTop) label.style["margin-top"] = marginTop; + label.style["horizontal-align"] = "center"; + label.style["vertical-align"] = "center"; + label.html = true; + label.text = text; + label.DeleteAsync(time > 0 ? time : 3); +} + +function PageModeSelect(type) { + if (type === "fairy") { + SelectedPage = 1; + $("#InputPage").visible = true; + $("#PetInputPage").visible = false; + $("#HtmlPage").visible = false; + $("#AvalonCoin").text = ""; + return; + } + if (type === "pet") { + SelectedPage = 2; + $("#InputPage").visible = false; + $("#PetInputPage").visible = true; + $("#HtmlPage").visible = false; + $("#PetLevelText").text = ""; + return; + } + +} + +function ShowModeSelectTip(index) { + $.DispatchEvent("DOTAShowTextTooltip", $("#PageModeToggle").GetChild(index), $.Localize("#pay_tip_mode" + index)); +} + +function HideModeSelectTip() { + $.DispatchEvent("DOTAHideTextTooltip") +} + +function GoBack() { + $("#InputPage").visible = SelectedPage === 1; + $("#PetInputPage").visible = SelectedPage === 2; + $("#HtmlPage").visible = false; + $("#AvalonCoin").text = ""; + $("#PetLevelText").text = ""; + EndTimeOutCount = 0; + BackTimeOutCount = 0; + GameEvents.SendCustomGameEventToServer("custom_game_pay_auto_query", {}); +} + +function Pay( method, type ) { + if ((!Price || Price < 0) && (!PetPrice || PetPrice < 0)) return; + + $("#PayQrImg").SetImage(""); + $("#InputPage").visible = false; + $("#PetInputPage").visible = false; + $("#HtmlPage").visible = true; + $("#CompleteButton").visible = false; + $("#GoBackButton").enabled = false; + + BackTimeOutCount = 30; + GoBackTimeCount(); + + EndTimeOutCount = 300; + EndBackTimeCount() + + var payType = 2; + if (method === "alipay") { + payType = 1; + } else if (method === "wechatpay") { + payType = 2; + } + + if (type === "fairy") { + GameEvents.SendCustomGameEventToServer("custom_game_pay_select", {"method":payType, "amount":PayAmount, "price":Price * 100, "type": type}); + } else if (type === "pet") { + GameEvents.SendCustomGameEventToServer("custom_game_pay_select", {"method":payType, "amount":PetPayAmount, "price":PetPrice * 100, "type": type}); + } + +} + +function GoBackTimeCount() { + if (BackTimeOutCount <= 0) { + $("#GoBackButton").enabled = true; + $("#GoBackButton").SetDialogVariableInt("timeout", 0) + $("#CompleteButton").visible = true; + return; + } + BackTimeOutCount--; + $("#GoBackButton").SetDialogVariableInt("timeout", BackTimeOutCount) + $.Schedule(1, GoBackTimeCount); +} + + +function EndBackTimeCount() { + if (EndTimeOutCount <= 0) { + return; + } + EndTimeOutCount--; + var m = Math.floor(EndTimeOutCount/60); + var s = EndTimeOutCount%60; + var note = m > 0 ? (m + "分") : ""; + note += s > 0 ? (s + "秒") : ""; + if (note === "") note = "0秒(已过期,请返回)"; + $("#HtmlPage").SetDialogVariable("LeftTime", note); + $.Schedule(1, EndBackTimeCount); +} + +function PayComplete() { + if (IsInQuery) return; + IsInQuery = true; + GameEvents.SendCustomGameEventToServer("custom_game_pay_query", {}); +} + + + + +// 文本改变 +var AvalonCoinLock = false; +function OnAvalonCoinChange() { + if (AvalonCoinLock) return; + AvalonCoinLock=true; + + var AvalonCoin = $("#AvalonCoin"); + var text = AvalonCoin.text; + var m = text.replace(/\D+/g,""); + var amount = parseInt(m); + if (amount >= 10) { + AvalonCoin.text = parseInt(m); + } + else { + amount = 0; + AvalonCoin.text = ""; + } + + if (amount > 0) { + // $("#PrePayButton").SetDialogVariable("Amount",((100 * amount / 2 - RandomInt(0, 100))/100).toFixed(2).toString()); + if (PayAmount != amount) { + PayAmount = amount; + Price = (100 * amount * 0.5 - RandomInt(0, 100))/100; + $("#PrePayButton").SetDialogVariable("Amount",Price.toString()); + $("#HtmlPage").SetDialogVariable("Amount",Price.toString()); + } + } else { + PayAmount = 0; + Price = 0.0; + $("#PrePayButton").SetDialogVariable("Amount", "0"); + $("#HtmlPage").SetDialogVariable("Amount", "0"); + } + AvalonCoinLock = false; +} + +// 文本改变 +var PetlevelLock = false; +function OnPetLevelChange() { + if (PetlevelLock) return; + PetlevelLock=true; + + var PetLevelText = $("#PetLevelText"); + var text = PetLevelText.text; + var m = text.replace(/\D+/g,""); + var amount = parseInt(m); + if (amount > 0) { + PetLevelText.text = parseInt(m); + } + else { + amount = 0; + PetLevelText.text = ""; + } + + if (amount > 0) { + if (PetPayAmount != amount) { + PetPayAmount = amount; + PetPrice = (100 * amount * 30 - RandomInt(0, 100))/100; + $("#PetPrePayButton").SetDialogVariable("Amount",PetPrice.toString()); + $("#HtmlPage").SetDialogVariable("Amount",PetPrice.toString()); + } + } else { + PetPayAmount = 0; + PetPrice = 0.0; + $("#PetPrePayButton").SetDialogVariable("Amount", "0"); + $("#HtmlPage").SetDialogVariable("Amount", "0"); + } + PetlevelLock = false; +} + +function RandomInt(min, max) { + return Math.floor(Math.random() * (max - min + 1)) + min; +} + +function AddAvalonCoin(num) { + var AvalonCoin = $("#AvalonCoin"); + var text = AvalonCoin.text; + var amount = parseInt(text); + if (!amount || amount < 0) amount = 0; + if (num > 0) + AvalonCoin.text = (amount + num); + else + AvalonCoin.text = ""; +} + +function AddPetLevel(num) { + var PetLevelText = $("#PetLevelText"); + var text = PetLevelText.text; + var amount = parseInt(text); + if (!amount || amount < 0) amount = 0; + if (num > 0) + PetLevelText.text = (amount + num); + else + PetLevelText.text = ""; +} + +var closeHandle = null; +function Close() { + if (closeHandle) { + closeHandle(); + } +} + +function AvalonCoinInputFocus(){ + // $("#PetTextPanel").SetFocus(); +} + +function PetInputFocus() { + $("#PetLevelText").SetFocus(); +} + + +;(function(){ + $("#PrePayButton").SetDialogVariable("Amount", "0"); + $("#HtmlPage").SetDialogVariable("Amount", "0"); + $("#HtmlPage").visible = false; + $("#AvalonCoin").enabled = false; + $("#InputPage").visible = SelectedPage === 1; + $("#PetInputPage").visible = SelectedPage === 2; + + $.GetContextPanel().OnClose = function (f) { closeHandle = f }; + + GameEvents.Subscribe("thtd_pay_post", function (data){ + if (data.code === 1) { + // $("#Html").SetURL(data.url);占用性能 + // $("#PayQrImg").SetImage(data.url); + $("#PayQrImg").SetImage(data.method === 1 ? "s2r://panorama/images/custom_game/qr_alipay_png.vtex" : "s2r://panorama/images/custom_game/qr_wechat_png.vtex"); + } else { + // $("#Html").SetURL("https://qrcode/error.jpg"); + $("#PayQrImg").SetImage(""); + ShowMessage("获取支付码失败," + data.msg, 10, true); + } + }); + + GameEvents.Subscribe("thtd_pay_result", function (data){ + IsInQuery = false; + if (data.code === "0000") { + ShowMessage(data.msg, 5); + Game.EmitSound("Quest.Completed"); + BackTimeOutCount = 0; + } else { + ShowMessage(data.msg, 5, true); + } + }); + +})(); diff --git a/panorama/layout/custom_game/frames/payment/payment.xml b/panorama/layout/custom_game/frames/payment/payment.xml new file mode 100644 index 0000000..fcb110e --- /dev/null +++ b/panorama/layout/custom_game/frames/payment/payment.xml @@ -0,0 +1,111 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/info.xml b/panorama/layout/custom_game/info.xml new file mode 100644 index 0000000..aa426a1 --- /dev/null +++ b/panorama/layout/custom_game/info.xml @@ -0,0 +1,516 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/panorama/layout/custom_game/team_select.xml b/panorama/layout/custom_game/team_select.xml new file mode 100644 index 0000000..994e81b --- /dev/null +++ b/panorama/layout/custom_game/team_select.xml @@ -0,0 +1,305 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +