From e466cde9d5f6c533fa509b301dfa26409c9ebccb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com> Date: Sat, 19 Jul 2025 17:27:56 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A4=A9=E6=B0=94=E7=B3=BB=E7=BB=9F=E5=AE=8C?= =?UTF-8?q?=E6=88=90?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- GUI/MainMenuPanel.tscn | 4 +- GameManager/WeatherSystem.gd | 51 ++ GameManager/WeatherSystem.gd.uid | 1 + MainGame.gd | 481 +++++-------- MainGame.tscn | 562 ++++++++++++--- Network/TCPNetworkManager.gd | 126 ++++ Scene/BigPanel/DailyCheckInPanel.tscn | 35 +- Scene/BigPanel/LoginPanel.tscn | 381 +++++++--- Scene/BigPanel/LuckyDrawPanel.tscn | 52 +- Script/BigPanel/CropWarehousePanel.gd | 82 ++- Script/BigPanel/LoginPanel.gd | 363 +++++++--- Script/BigPanel/PlayerStorePanel.gd | 374 ++++++++++ Script/BigPanel/PlayerStorePanel.gd.uid | 1 + Script/Dialog/AddProduct2StorePopup.gd | 197 +++++ Script/Dialog/AddProduct2StorePopup.gd.uid | 1 + Script/Dialog/BatchSellPopup.gd | 154 ++++ Script/Dialog/BatchSellPopup.gd.uid | 1 + Script/SmallPanel/CropInformPanel.gd | 216 ++++++ Script/SmallPanel/CropInformPanel.gd.uid | 1 + Script/SmallPanel/DebugPanel.gd | 7 +- Server/QQEmailSend.py | 148 +++- Server/TCPGameServer.py | 670 +++++++++++++++++- .../__pycache__/QQEmailSend.cpython-313.pyc | Bin 14695 -> 19702 bytes .../config/initial_player_data_template.json | 15 +- Server/config/verification_codes.json | 6 - Server/game_saves/2143323382.json | 538 ++++++++------ Shader/PlantSwayShader.gdshader | 22 +- assets/作物/人参/{0.webp => 幼苗.webp} | Bin .../人参/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/仙人掌/{0.webp => 幼苗.webp} | Bin .../仙人掌/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/仙人掌/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/冬虫夏草/1.webp | Bin 24202 -> 0 bytes assets/作物/冬虫夏草/1.webp.import | 34 - assets/作物/冬虫夏草/{0.webp => 幼苗.webp} | Bin .../{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/冬虫夏草/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/凤凰木/{0.webp => 幼苗.webp} | Bin assets/作物/凤凰木/幼苗.webp.import | 34 + assets/作物/凤凰木/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/南瓜/2.webp | Bin 32320 -> 0 bytes assets/作物/南瓜/3.webp | Bin 29570 -> 0 bytes assets/作物/南瓜/3.webp.import | 34 - assets/作物/南瓜/{0.webp => 幼苗.webp} | Bin .../南瓜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/南瓜/{1.webp => 未成熟.webp} | Bin .../南瓜/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/可可豆/{0.webp => 幼苗.webp} | Bin .../可可豆/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/向日葵/2.webp | Bin 31058 -> 0 bytes assets/作物/向日葵/2.webp.import | 34 - assets/作物/向日葵/{0.webp => 幼苗.webp} | Bin .../向日葵/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/向日葵/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/咖啡豆/{0.webp => 幼苗.webp} | Bin .../咖啡豆/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/哈密瓜/1.webp | Bin 21054 -> 0 bytes assets/作物/哈密瓜/1.webp.import | 34 - assets/作物/哈密瓜/{0.webp => 幼苗.webp} | Bin .../哈密瓜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/哈密瓜/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/土豆/0.webp.import | 34 - assets/作物/土豆/2.webp.import | 34 - assets/作物/土豆/{1.webp => 幼苗.webp} | Bin .../土豆/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/土豆/{2.webp => 未成熟.webp} | Bin .../4.webp.import => 土豆/未成熟.webp.import} | 8 +- assets/作物/大白菜/1.webp | Bin 20962 -> 0 bytes assets/作物/大白菜/1.webp.import | 34 - assets/作物/大白菜/{0.webp => 幼苗.webp} | Bin .../大白菜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/大白菜/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/大蒜/{0.webp => 幼苗.webp} | Bin .../大蒜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/大豆/2.webp | Bin 36538 -> 0 bytes assets/作物/大豆/2.webp.import | 34 - assets/作物/大豆/{0.webp => 幼苗.webp} | Bin .../大豆/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/大豆/{1.webp => 未成熟.webp} | Bin .../大豆/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/富贵竹/2.webp | Bin 32760 -> 0 bytes assets/作物/富贵竹/2.webp.import | 34 - assets/作物/富贵竹/{0.webp => 幼苗.webp} | Bin .../富贵竹/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/富贵竹/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/小麦/2.webp | Bin 37050 -> 0 bytes assets/作物/小麦/2.webp.import | 34 - assets/作物/小麦/{0.webp => 幼苗.webp} | Bin .../小麦/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/小麦/{1.webp => 未成熟.webp} | Bin .../小麦/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/山楂/{0.webp => 幼苗.webp} | Bin .../山楂/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/山楂/{1.webp => 未成熟.webp} | Bin .../山楂/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/山葵/1.webp | Bin 19814 -> 0 bytes assets/作物/山葵/1.webp.import | 34 - assets/作物/山葵/{0.webp => 幼苗.webp} | Bin .../山葵/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/山葵/{2.webp => 未成熟.webp} | Bin .../山葵/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/幸运花/1.webp | Bin 20742 -> 0 bytes assets/作物/幸运花/1.webp.import | 34 - assets/作物/幸运花/{0.webp => 幼苗.webp} | Bin .../幸运花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/幸运花/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/幸运草/1.webp | Bin 26188 -> 0 bytes assets/作物/幸运草/1.webp.import | 34 - assets/作物/幸运草/{0.webp => 幼苗.webp} | Bin .../幸运草/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/幸运草/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/康乃馨/1.webp | Bin 28712 -> 0 bytes assets/作物/康乃馨/1.webp.import | 34 - assets/作物/康乃馨/{0.webp => 幼苗.webp} | Bin .../康乃馨/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/康乃馨/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/摇钱树/2.webp | Bin 29566 -> 0 bytes assets/作物/摇钱树/2.webp.import | 34 - assets/作物/摇钱树/{0.webp => 幼苗.webp} | Bin .../摇钱树/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/摇钱树/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/星芒草/{0.webp => 幼苗.webp} | Bin .../星芒草/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/月光草/1.webp.import | 34 - assets/作物/月光草/{0.webp => 幼苗.webp} | Bin .../月光草/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/月光草/{1.webp => 未成熟.webp} | Bin .../2.webp.import => 月光草/未成熟.webp.import} | 8 +- assets/作物/杂交树1/2.webp | Bin 26888 -> 0 bytes assets/作物/杂交树1/2.webp.import | 34 - assets/作物/杂交树1/3.webp | Bin 26888 -> 0 bytes assets/作物/杂交树1/3.webp.import | 34 - assets/作物/杂交树1/{0.webp => 幼苗.webp} | Bin .../杂交树1/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/杂交树1/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/杂交树2/2.webp | Bin 21182 -> 0 bytes assets/作物/杂交树2/2.webp.import | 34 - assets/作物/杂交树2/3.webp | Bin 21182 -> 0 bytes assets/作物/杂交树2/3.webp.import | 34 - assets/作物/杂交树2/{0.webp => 幼苗.webp} | Bin .../杂交树2/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/杂交树2/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/杂草1/{0.webp => 幼苗.webp} | Bin .../杂草1/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/杂草2/{0.webp => 幼苗.webp} | Bin .../杂草2/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/杨桃/1.webp | Bin 29786 -> 0 bytes assets/作物/杨桃/1.webp.import | 34 - assets/作物/杨桃/{0.webp => 幼苗.webp} | Bin .../杨桃/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/杨桃/{2.webp => 未成熟.webp} | Bin .../杨桃/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/松露/1.webp | Bin 25102 -> 0 bytes assets/作物/松露/1.webp.import | 34 - assets/作物/松露/3.webp | Bin 27962 -> 0 bytes assets/作物/松露/3.webp.import | 34 - assets/作物/松露/{0.webp => 幼苗.webp} | Bin .../松露/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/松露/{2.webp => 未成熟.webp} | Bin .../松露/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/枇杷/2.webp | Bin 29392 -> 0 bytes assets/作物/枇杷/2.webp.import | 34 - assets/作物/枇杷/{0.webp => 幼苗.webp} | Bin .../枇杷/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/枇杷/{1.webp => 未成熟.webp} | Bin .../枇杷/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/柠檬/{0.webp => 幼苗.webp} | Bin .../柠檬/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/柠檬/{1.webp => 未成熟.webp} | Bin .../柠檬/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/柿子/2.webp | Bin 26852 -> 0 bytes assets/作物/柿子/2.webp.import | 34 - assets/作物/柿子/{0.webp => 幼苗.webp} | Bin .../柿子/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/柿子/{1.webp => 未成熟.webp} | Bin .../柿子/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/栀子花/2.webp | Bin 20014 -> 0 bytes assets/作物/栀子花/2.webp.import | 34 - assets/作物/栀子花/3.webp | Bin 19298 -> 0 bytes assets/作物/栀子花/3.webp.import | 34 - assets/作物/栀子花/{0.webp => 幼苗.webp} | Bin .../栀子花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/栀子花/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/树莓/1.webp | Bin 23274 -> 0 bytes assets/作物/树莓/1.webp.import | 34 - assets/作物/树莓/2.webp | Bin 35714 -> 0 bytes assets/作物/树莓/2.webp.import | 34 - assets/作物/树莓/{0.webp => 幼苗.webp} | Bin .../树莓/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/树莓/{3.webp => 未成熟.webp} | Bin .../树莓/{3.webp.import => 未成熟.webp.import} | 6 +- assets/作物/桃子/2.webp | Bin 22016 -> 0 bytes assets/作物/桃子/2.webp.import | 34 - assets/作物/桃子/3.webp | Bin 31748 -> 0 bytes assets/作物/桃子/3.webp.import | 34 - assets/作物/桃子/{0.webp => 幼苗.webp} | Bin .../桃子/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/桃子/成熟.webp | Bin 35242 -> 31748 bytes assets/作物/桃子/成熟.webp.import | 2 +- assets/作物/桃子/{1.webp => 未成熟.webp} | Bin .../桃子/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/梨子/0.webp | Bin 34264 -> 0 bytes assets/作物/梨子/0.webp.import | 34 - assets/作物/梨子/{1.webp => 幼苗.webp} | Bin .../梨子/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/梨子/{2.webp => 未成熟.webp} | Bin .../梨子/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/椰子/1.webp | Bin 28116 -> 0 bytes assets/作物/椰子/1.webp.import | 34 - assets/作物/椰子/{0.webp => 幼苗.webp} | Bin .../椰子/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/椰子/{2.webp => 未成熟.webp} | Bin .../椰子/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/橘子/0.webp | Bin 14068 -> 0 bytes assets/作物/橘子/1.webp | Bin 27566 -> 0 bytes assets/作物/橘子/1.webp.import | 34 - assets/作物/橘子/{2.webp => 幼苗.webp} | Bin .../橘子/{2.webp.import => 幼苗.webp.import} | 6 +- assets/作物/橘子/{3.webp => 未成熟.webp} | Bin .../橘子/{3.webp.import => 未成熟.webp.import} | 6 +- assets/作物/橘子树/0.webp | Bin 14068 -> 0 bytes assets/作物/橘子树/0.webp.import | 34 - assets/作物/橘子树/{1.webp => 幼苗.webp} | Bin .../橘子树/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/橘子树/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/洋葱/1.webp | Bin 22886 -> 0 bytes assets/作物/洋葱/1.webp.import | 34 - assets/作物/洋葱/{0.webp => 幼苗.webp} | Bin .../洋葱/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/洋葱/{2.webp => 未成熟.webp} | Bin .../洋葱/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/牵牛花/2.webp | Bin 34078 -> 0 bytes assets/作物/牵牛花/2.webp.import | 34 - assets/作物/牵牛花/{0.webp => 幼苗.webp} | Bin .../牵牛花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/牵牛花/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/{土豆/0.webp => 玉米/幼苗.webp} | Bin .../玉米/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/玉米/{1.webp => 未成熟.webp} | Bin .../玉米/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/玫瑰花/1.webp | Bin 22038 -> 0 bytes assets/作物/玫瑰花/1.webp.import | 34 - assets/作物/玫瑰花/{0.webp => 幼苗.webp} | Bin .../玫瑰花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/玫瑰花/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/甘蔗/0.webp | Bin 23560 -> 0 bytes assets/作物/甘蔗/0.webp.import | 34 - assets/作物/甘蔗/3.webp | Bin 28314 -> 0 bytes assets/作物/甘蔗/3.webp.import | 34 - assets/作物/甘蔗/{1.webp => 幼苗.webp} | Bin .../甘蔗/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/甘蔗/{2.webp => 未成熟.webp} | Bin .../甘蔗/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/甜菜/0.webp | Bin 31068 -> 0 bytes assets/作物/甜菜/0.webp.import | 34 - assets/作物/甜菜/3.webp | Bin 23118 -> 0 bytes assets/作物/甜菜/3.webp.import | 34 - assets/作物/甜菜/{1.webp => 幼苗.webp} | Bin .../甜菜/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/甜菜/{2.webp => 未成熟.webp} | Bin .../甜菜/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/生菜/{0.webp => 幼苗.webp} | Bin .../生菜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/番茄/{1.webp => 幼苗.webp} | Bin .../番茄/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/番茄/{2.webp => 未成熟.webp} | Bin .../番茄/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/百合花/3.webp | Bin 26796 -> 0 bytes assets/作物/百合花/3.webp.import | 34 - assets/作物/百合花/{0.webp => 幼苗.webp} | Bin .../百合花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/百合花/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/石榴/{0.webp => 幼苗.webp} | Bin .../石榴/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/稻谷/2.webp | Bin 34232 -> 0 bytes assets/作物/稻谷/2.webp.import | 34 - assets/作物/稻谷/{0.webp => 幼苗.webp} | Bin .../稻谷/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/稻谷/成熟.webp | Bin 36026 -> 34232 bytes assets/作物/稻谷/成熟.webp.import | 2 +- assets/作物/稻谷/{1.webp => 未成熟.webp} | Bin .../稻谷/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/糖果树/{0.webp => 幼苗.webp} | Bin .../糖果树/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/糖果树/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/胡萝卜/0.webp | Bin 17836 -> 0 bytes assets/作物/胡萝卜/1.webp | Bin 18620 -> 0 bytes assets/作物/胡萝卜/1.webp.import | 34 - assets/作物/{玉米/0.webp => 胡萝卜/幼苗.webp} | Bin .../胡萝卜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/胡萝卜/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/芒果/0.webp | Bin 31008 -> 0 bytes assets/作物/芒果/0.webp.import | 34 - assets/作物/芒果/{1.webp => 幼苗.webp} | Bin .../芒果/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/芒果/{2.webp => 未成熟.webp} | Bin .../芒果/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/芦笋/0.webp | Bin 15234 -> 0 bytes assets/作物/芦笋/0.webp.import | 34 - assets/作物/芦笋/{2.webp => 幼苗.webp} | Bin .../芦笋/{2.webp.import => 幼苗.webp.import} | 6 +- assets/作物/芦笋/{1.webp => 未成熟.webp} | Bin .../芦笋/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/芦荟/0.webp | Bin 15684 -> 0 bytes assets/作物/芦荟/0.webp.import | 34 - assets/作物/芦荟/3.webp | Bin 22470 -> 0 bytes assets/作物/芦荟/3.webp.import | 34 - assets/作物/芦荟/{1.webp => 幼苗.webp} | Bin .../芦荟/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/芦荟/{2.webp => 未成熟.webp} | Bin .../芦荟/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/花椰菜/2.webp | Bin 36786 -> 0 bytes assets/作物/花椰菜/2.webp.import | 34 - assets/作物/花椰菜/3.webp | Bin 24442 -> 0 bytes assets/作物/花椰菜/3.webp.import | 34 - assets/作物/花椰菜/{0.webp => 幼苗.webp} | Bin .../花椰菜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/花椰菜/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/苹果/0.webp | Bin 22234 -> 0 bytes assets/作物/苹果/0.webp.import | 34 - assets/作物/苹果/{1.webp => 幼苗.webp} | Bin .../苹果/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/苹果/{2.webp => 未成熟.webp} | Bin .../苹果/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/茄子/1.webp | Bin 25676 -> 0 bytes assets/作物/茄子/1.webp.import | 34 - assets/作物/茄子/2.webp | Bin 30146 -> 0 bytes assets/作物/茄子/2.webp.import | 34 - assets/作物/茄子/4.webp | Bin 26430 -> 0 bytes assets/作物/茄子/{0.webp => 幼苗.webp} | Bin .../茄子/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/茄子/{3.webp => 未成熟.webp} | Bin .../茄子/{3.webp.import => 未成熟.webp.import} | 6 +- assets/作物/草莓/2.webp | Bin 25022 -> 0 bytes assets/作物/草莓/2.webp.import | 34 - assets/作物/草莓/3.webp | Bin 25388 -> 0 bytes assets/作物/草莓/3.webp.import | 34 - assets/作物/草莓/{0.webp => 幼苗.webp} | Bin .../草莓/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/草莓/成熟.webp | Bin 25388 -> 25022 bytes assets/作物/草莓/成熟.webp.import | 2 +- assets/作物/草莓/{1.webp => 未成熟.webp} | Bin .../草莓/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/荔枝/0.webp | Bin 17530 -> 0 bytes assets/作物/荔枝/0.webp.import | 34 - assets/作物/荔枝/{1.webp => 幼苗.webp} | Bin .../荔枝/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/荔枝/{2.webp => 未成熟.webp} | Bin .../荔枝/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/菊花/1.webp | Bin 29460 -> 0 bytes assets/作物/菊花/1.webp.import | 34 - assets/作物/菊花/3.webp | Bin 26164 -> 0 bytes assets/作物/菊花/3.webp.import | 34 - assets/作物/菊花/{0.webp => 幼苗.webp} | Bin .../菊花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/菊花/{2.webp => 未成熟.webp} | Bin .../菊花/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/菠萝/1.webp | Bin 26314 -> 0 bytes assets/作物/菠萝/1.webp.import | 34 - assets/作物/菠萝/{0.webp => 幼苗.webp} | Bin .../菠萝/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/菠萝/成熟.webp | Bin 27960 -> 26314 bytes assets/作物/菠萝/成熟.webp.import | 2 +- assets/作物/菠萝/{2.webp => 未成熟.webp} | Bin .../菠萝/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/葡萄/2.webp | Bin 28444 -> 0 bytes assets/作物/葡萄/2.webp.import | 34 - assets/作物/葡萄/3.webp | Bin 30896 -> 0 bytes assets/作物/葡萄/3.webp.import | 34 - assets/作物/葡萄/{0.webp => 幼苗.webp} | Bin .../葡萄/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/葡萄/{1.webp => 未成熟.webp} | Bin .../葡萄/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/葫芦/{0.webp => 幼苗.webp} | Bin .../葫芦/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/葫芦/{1.webp => 未成熟.webp} | Bin .../葫芦/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/蓝莓/1.webp | Bin 23412 -> 0 bytes assets/作物/蓝莓/1.webp.import | 34 - assets/作物/蓝莓/3.webp | Bin 25134 -> 0 bytes assets/作物/蓝莓/3.webp.import | 34 - assets/作物/蓝莓/{0.webp => 幼苗.webp} | Bin .../蓝莓/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/蓝莓/{2.webp => 未成熟.webp} | Bin .../蓝莓/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/蕨菜/0.webp | Bin 26886 -> 0 bytes assets/作物/蕨菜/0.webp.import | 34 - assets/作物/蕨菜/{1.webp => 幼苗.webp} | Bin .../蕨菜/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/蕨菜/{2.webp => 未成熟.webp} | Bin .../蕨菜/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/藏红花/{0.webp => 幼苗.webp} | Bin .../藏红花/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/藏羚羊草/{0.webp => 幼苗.webp} | Bin .../{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/西瓜/1.webp | Bin 26444 -> 0 bytes assets/作物/西瓜/1.webp.import | 34 - assets/作物/西瓜/3.webp | Bin 24758 -> 0 bytes assets/作物/西瓜/3.webp.import | 34 - assets/作物/西瓜/{0.webp => 幼苗.webp} | Bin .../西瓜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/西瓜/{2.webp => 未成熟.webp} | Bin .../西瓜/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/豌豆/2.webp | Bin 30310 -> 0 bytes assets/作物/豌豆/2.webp.import | 34 - assets/作物/豌豆/{0.webp => 幼苗.webp} | Bin .../豌豆/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/豌豆/{1.webp => 未成熟.webp} | Bin .../豌豆/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/辣椒/2.webp | Bin 28156 -> 0 bytes assets/作物/辣椒/2.webp.import | 34 - assets/作物/辣椒/{0.webp => 幼苗.webp} | Bin .../辣椒/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/辣椒/{1.webp => 未成熟.webp} | Bin .../辣椒/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/迷迭香/{0.webp => 幼苗.webp} | Bin .../迷迭香/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/郁金香/0.webp | Bin 14756 -> 0 bytes assets/作物/郁金香/0.webp.import | 34 - assets/作物/郁金香/{1.webp => 幼苗.webp} | Bin .../郁金香/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/郁金香/{2.webp => 未成熟.webp} | Bin .../{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/野草1/0.webp.import | 34 - assets/作物/野草1/{0.webp => 幼苗.webp} | Bin .../0.webp.import => 野草1/幼苗.webp.import} | 8 +- assets/作物/野草2/{0.webp => 幼苗.webp} | Bin .../野草2/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/金橘/1.webp | Bin 22212 -> 0 bytes assets/作物/金橘/1.webp.import | 34 - assets/作物/金橘/3.webp | Bin 30694 -> 0 bytes assets/作物/金橘/3.webp.import | 34 - assets/作物/金橘/{0.webp => 幼苗.webp} | Bin .../金橘/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/金橘/{2.webp => 未成熟.webp} | Bin .../金橘/{2.webp.import => 未成熟.webp.import} | 6 +- assets/作物/面包树/2.webp | Bin 16568 -> 0 bytes assets/作物/面包树/2.webp.import | 34 - assets/作物/面包树/{0.webp => 幼苗.webp} | Bin .../面包树/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/面包树/{1.webp => 未成熟.webp} | Bin .../{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/香草/0.webp | Bin 31792 -> 0 bytes assets/作物/香草/0.webp.import | 34 - assets/作物/香草/2.webp | Bin 24420 -> 0 bytes assets/作物/香草/2.webp.import | 34 - assets/作物/香草/{1.webp => 幼苗.webp} | Bin .../香草/{1.webp.import => 幼苗.webp.import} | 6 +- assets/作物/香草/{3.webp => 未成熟.webp} | Bin .../香草/{3.webp.import => 未成熟.webp.import} | 6 +- assets/作物/香蕉/2.webp | Bin 23910 -> 0 bytes assets/作物/香蕉/2.webp.import | 34 - assets/作物/香蕉/{0.webp => 幼苗.webp} | Bin .../香蕉/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/香蕉/{1.webp => 未成熟.webp} | Bin .../香蕉/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/鱼腥草/1.webp.import | 34 - assets/作物/鱼腥草/2.webp | Bin 29862 -> 0 bytes assets/作物/鱼腥草/2.webp.import | 34 - assets/作物/鱼腥草/{0.webp => 幼苗.webp} | Bin .../鱼腥草/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/鱼腥草/{1.webp => 未成熟.webp} | Bin .../0.webp.import => 鱼腥草/未成熟.webp.import} | 8 +- assets/作物/黄瓜/{0.webp => 幼苗.webp} | Bin .../黄瓜/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/黄瓜/{1.webp => 未成熟.webp} | Bin .../黄瓜/{1.webp.import => 未成熟.webp.import} | 6 +- assets/作物/默认/{0.webp => 幼苗.webp} | Bin .../默认/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/默认/收获物.webp | Bin 13716 -> 31150 bytes assets/作物/默认/收获物.webp.import | 2 +- assets/作物/默认/未成熟.webp | Bin 0 -> 31150 bytes assets/作物/默认/未成熟.webp.import | 34 + assets/作物/龙果/2.webp | Bin 22850 -> 0 bytes assets/作物/龙果/2.webp.import | 34 - assets/作物/龙果/{0.webp => 幼苗.webp} | Bin .../龙果/{0.webp.import => 幼苗.webp.import} | 6 +- assets/作物/龙果/{1.webp => 未成熟.webp} | Bin .../龙果/{1.webp.import => 未成熟.webp.import} | 6 +- assets/字体/MapleMono-NF-CN-Bold.ttf | Bin 0 -> 17960056 bytes assets/字体/MapleMono-NF-CN-Bold.ttf.import | 35 + assets/字体/MapleMono-NF-CN-BoldItalic.ttf | Bin 0 -> 18637628 bytes .../MapleMono-NF-CN-BoldItalic.ttf.import | 35 + assets/装饰物图片/宠物商店.webp | Bin 0 -> 12146 bytes assets/装饰物图片/宠物商店.webp.import | 34 + assets/装饰物图片/小卖部.webp | Bin 0 -> 15146 bytes assets/装饰物图片/小卖部.webp.import | 34 + assets/装饰物图片/种子商店.webp | Bin 0 -> 11868 bytes assets/装饰物图片/种子商店.webp.import | 34 + assets/装饰物图片/道具商店.webp | Bin 0 -> 11212 bytes assets/装饰物图片/道具商店.webp.import | 34 + background.gd | 1 + server/game_saves/3205788256.json | 665 ++++++++++------- 514 files changed, 4678 insertions(+), 4389 deletions(-) create mode 100644 GameManager/WeatherSystem.gd create mode 100644 GameManager/WeatherSystem.gd.uid create mode 100644 Script/BigPanel/PlayerStorePanel.gd create mode 100644 Script/BigPanel/PlayerStorePanel.gd.uid create mode 100644 Script/Dialog/AddProduct2StorePopup.gd create mode 100644 Script/Dialog/AddProduct2StorePopup.gd.uid create mode 100644 Script/Dialog/BatchSellPopup.gd create mode 100644 Script/Dialog/BatchSellPopup.gd.uid create mode 100644 Script/SmallPanel/CropInformPanel.gd create mode 100644 Script/SmallPanel/CropInformPanel.gd.uid delete mode 100644 Server/config/verification_codes.json rename assets/作物/人参/{0.webp => 幼苗.webp} (100%) rename assets/作物/人参/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/仙人掌/{0.webp => 幼苗.webp} (100%) rename assets/作物/仙人掌/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/仙人掌/{1.webp => 未成熟.webp} (100%) rename assets/作物/仙人掌/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/冬虫夏草/1.webp delete mode 100644 assets/作物/冬虫夏草/1.webp.import rename assets/作物/冬虫夏草/{0.webp => 幼苗.webp} (100%) rename assets/作物/冬虫夏草/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/冬虫夏草/{2.webp => 未成熟.webp} (100%) rename assets/作物/冬虫夏草/{2.webp.import => 未成熟.webp.import} (70%) rename assets/作物/凤凰木/{0.webp => 幼苗.webp} (100%) create mode 100644 assets/作物/凤凰木/幼苗.webp.import rename assets/作物/凤凰木/{1.webp => 未成熟.webp} (100%) rename assets/作物/凤凰木/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/南瓜/2.webp delete mode 100644 assets/作物/南瓜/3.webp delete mode 100644 assets/作物/南瓜/3.webp.import rename assets/作物/南瓜/{0.webp => 幼苗.webp} (100%) rename assets/作物/南瓜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/南瓜/{1.webp => 未成熟.webp} (100%) rename assets/作物/南瓜/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/可可豆/{0.webp => 幼苗.webp} (100%) rename assets/作物/可可豆/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/向日葵/2.webp delete mode 100644 assets/作物/向日葵/2.webp.import rename assets/作物/向日葵/{0.webp => 幼苗.webp} (100%) rename assets/作物/向日葵/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/向日葵/{1.webp => 未成熟.webp} (100%) rename assets/作物/向日葵/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/咖啡豆/{0.webp => 幼苗.webp} (100%) rename assets/作物/咖啡豆/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/哈密瓜/1.webp delete mode 100644 assets/作物/哈密瓜/1.webp.import rename assets/作物/哈密瓜/{0.webp => 幼苗.webp} (100%) rename assets/作物/哈密瓜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/哈密瓜/{2.webp => 未成熟.webp} (100%) rename assets/作物/哈密瓜/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/土豆/0.webp.import delete mode 100644 assets/作物/土豆/2.webp.import rename assets/作物/土豆/{1.webp => 幼苗.webp} (100%) rename assets/作物/土豆/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/土豆/{2.webp => 未成熟.webp} (100%) rename assets/作物/{茄子/4.webp.import => 土豆/未成熟.webp.import} (75%) delete mode 100644 assets/作物/大白菜/1.webp delete mode 100644 assets/作物/大白菜/1.webp.import rename assets/作物/大白菜/{0.webp => 幼苗.webp} (100%) rename assets/作物/大白菜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/大白菜/{2.webp => 未成熟.webp} (100%) rename assets/作物/大白菜/{2.webp.import => 未成熟.webp.import} (70%) rename assets/作物/大蒜/{0.webp => 幼苗.webp} (100%) rename assets/作物/大蒜/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/大豆/2.webp delete mode 100644 assets/作物/大豆/2.webp.import rename assets/作物/大豆/{0.webp => 幼苗.webp} (100%) rename assets/作物/大豆/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/大豆/{1.webp => 未成熟.webp} (100%) rename assets/作物/大豆/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/富贵竹/2.webp delete mode 100644 assets/作物/富贵竹/2.webp.import rename assets/作物/富贵竹/{0.webp => 幼苗.webp} (100%) rename assets/作物/富贵竹/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/富贵竹/{1.webp => 未成熟.webp} (100%) rename assets/作物/富贵竹/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/小麦/2.webp delete mode 100644 assets/作物/小麦/2.webp.import rename assets/作物/小麦/{0.webp => 幼苗.webp} (100%) rename assets/作物/小麦/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/小麦/{1.webp => 未成熟.webp} (100%) rename assets/作物/小麦/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/山楂/{0.webp => 幼苗.webp} (100%) rename assets/作物/山楂/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/山楂/{1.webp => 未成熟.webp} (100%) rename assets/作物/山楂/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/山葵/1.webp delete mode 100644 assets/作物/山葵/1.webp.import rename assets/作物/山葵/{0.webp => 幼苗.webp} (100%) rename assets/作物/山葵/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/山葵/{2.webp => 未成熟.webp} (100%) rename assets/作物/山葵/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/幸运花/1.webp delete mode 100644 assets/作物/幸运花/1.webp.import rename assets/作物/幸运花/{0.webp => 幼苗.webp} (100%) rename assets/作物/幸运花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/幸运花/{2.webp => 未成熟.webp} (100%) rename assets/作物/幸运花/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/幸运草/1.webp delete mode 100644 assets/作物/幸运草/1.webp.import rename assets/作物/幸运草/{0.webp => 幼苗.webp} (100%) rename assets/作物/幸运草/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/幸运草/{2.webp => 未成熟.webp} (100%) rename assets/作物/幸运草/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/康乃馨/1.webp delete mode 100644 assets/作物/康乃馨/1.webp.import rename assets/作物/康乃馨/{0.webp => 幼苗.webp} (100%) rename assets/作物/康乃馨/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/康乃馨/{2.webp => 未成熟.webp} (100%) rename assets/作物/康乃馨/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/摇钱树/2.webp delete mode 100644 assets/作物/摇钱树/2.webp.import rename assets/作物/摇钱树/{0.webp => 幼苗.webp} (100%) rename assets/作物/摇钱树/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/摇钱树/{1.webp => 未成熟.webp} (100%) rename assets/作物/摇钱树/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/星芒草/{0.webp => 幼苗.webp} (100%) rename assets/作物/星芒草/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/月光草/1.webp.import rename assets/作物/月光草/{0.webp => 幼苗.webp} (100%) rename assets/作物/月光草/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/月光草/{1.webp => 未成熟.webp} (100%) rename assets/作物/{南瓜/2.webp.import => 月光草/未成熟.webp.import} (70%) delete mode 100644 assets/作物/杂交树1/2.webp delete mode 100644 assets/作物/杂交树1/2.webp.import delete mode 100644 assets/作物/杂交树1/3.webp delete mode 100644 assets/作物/杂交树1/3.webp.import rename assets/作物/杂交树1/{0.webp => 幼苗.webp} (100%) rename assets/作物/杂交树1/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/杂交树1/{1.webp => 未成熟.webp} (100%) rename assets/作物/杂交树1/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/杂交树2/2.webp delete mode 100644 assets/作物/杂交树2/2.webp.import delete mode 100644 assets/作物/杂交树2/3.webp delete mode 100644 assets/作物/杂交树2/3.webp.import rename assets/作物/杂交树2/{0.webp => 幼苗.webp} (100%) rename assets/作物/杂交树2/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/杂交树2/{1.webp => 未成熟.webp} (100%) rename assets/作物/杂交树2/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/杂草1/{0.webp => 幼苗.webp} (100%) rename assets/作物/杂草1/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/杂草2/{0.webp => 幼苗.webp} (100%) rename assets/作物/杂草2/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/杨桃/1.webp delete mode 100644 assets/作物/杨桃/1.webp.import rename assets/作物/杨桃/{0.webp => 幼苗.webp} (100%) rename assets/作物/杨桃/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/杨桃/{2.webp => 未成熟.webp} (100%) rename assets/作物/杨桃/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/松露/1.webp delete mode 100644 assets/作物/松露/1.webp.import delete mode 100644 assets/作物/松露/3.webp delete mode 100644 assets/作物/松露/3.webp.import rename assets/作物/松露/{0.webp => 幼苗.webp} (100%) rename assets/作物/松露/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/松露/{2.webp => 未成熟.webp} (100%) rename assets/作物/松露/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/枇杷/2.webp delete mode 100644 assets/作物/枇杷/2.webp.import rename assets/作物/枇杷/{0.webp => 幼苗.webp} (100%) rename assets/作物/枇杷/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/枇杷/{1.webp => 未成熟.webp} (100%) rename assets/作物/枇杷/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/柠檬/{0.webp => 幼苗.webp} (100%) rename assets/作物/柠檬/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/柠檬/{1.webp => 未成熟.webp} (100%) rename assets/作物/柠檬/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/柿子/2.webp delete mode 100644 assets/作物/柿子/2.webp.import rename assets/作物/柿子/{0.webp => 幼苗.webp} (100%) rename assets/作物/柿子/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/柿子/{1.webp => 未成熟.webp} (100%) rename assets/作物/柿子/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/栀子花/2.webp delete mode 100644 assets/作物/栀子花/2.webp.import delete mode 100644 assets/作物/栀子花/3.webp delete mode 100644 assets/作物/栀子花/3.webp.import rename assets/作物/栀子花/{0.webp => 幼苗.webp} (100%) rename assets/作物/栀子花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/栀子花/{1.webp => 未成熟.webp} (100%) rename assets/作物/栀子花/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/树莓/1.webp delete mode 100644 assets/作物/树莓/1.webp.import delete mode 100644 assets/作物/树莓/2.webp delete mode 100644 assets/作物/树莓/2.webp.import rename assets/作物/树莓/{0.webp => 幼苗.webp} (100%) rename assets/作物/树莓/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/树莓/{3.webp => 未成熟.webp} (100%) rename assets/作物/树莓/{3.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/桃子/2.webp delete mode 100644 assets/作物/桃子/2.webp.import delete mode 100644 assets/作物/桃子/3.webp delete mode 100644 assets/作物/桃子/3.webp.import rename assets/作物/桃子/{0.webp => 幼苗.webp} (100%) rename assets/作物/桃子/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/桃子/{1.webp => 未成熟.webp} (100%) rename assets/作物/桃子/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/梨子/0.webp delete mode 100644 assets/作物/梨子/0.webp.import rename assets/作物/梨子/{1.webp => 幼苗.webp} (100%) rename assets/作物/梨子/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/梨子/{2.webp => 未成熟.webp} (100%) rename assets/作物/梨子/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/椰子/1.webp delete mode 100644 assets/作物/椰子/1.webp.import rename assets/作物/椰子/{0.webp => 幼苗.webp} (100%) rename assets/作物/椰子/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/椰子/{2.webp => 未成熟.webp} (100%) rename assets/作物/椰子/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/橘子/0.webp delete mode 100644 assets/作物/橘子/1.webp delete mode 100644 assets/作物/橘子/1.webp.import rename assets/作物/橘子/{2.webp => 幼苗.webp} (100%) rename assets/作物/橘子/{2.webp.import => 幼苗.webp.import} (71%) rename assets/作物/橘子/{3.webp => 未成熟.webp} (100%) rename assets/作物/橘子/{3.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/橘子树/0.webp delete mode 100644 assets/作物/橘子树/0.webp.import rename assets/作物/橘子树/{1.webp => 幼苗.webp} (100%) rename assets/作物/橘子树/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/橘子树/{2.webp => 未成熟.webp} (100%) rename assets/作物/橘子树/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/洋葱/1.webp delete mode 100644 assets/作物/洋葱/1.webp.import rename assets/作物/洋葱/{0.webp => 幼苗.webp} (100%) rename assets/作物/洋葱/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/洋葱/{2.webp => 未成熟.webp} (100%) rename assets/作物/洋葱/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/牵牛花/2.webp delete mode 100644 assets/作物/牵牛花/2.webp.import rename assets/作物/牵牛花/{0.webp => 幼苗.webp} (100%) rename assets/作物/牵牛花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/牵牛花/{1.webp => 未成熟.webp} (100%) rename assets/作物/牵牛花/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/{土豆/0.webp => 玉米/幼苗.webp} (100%) rename assets/作物/玉米/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/玉米/{1.webp => 未成熟.webp} (100%) rename assets/作物/玉米/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/玫瑰花/1.webp delete mode 100644 assets/作物/玫瑰花/1.webp.import rename assets/作物/玫瑰花/{0.webp => 幼苗.webp} (100%) rename assets/作物/玫瑰花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/玫瑰花/{2.webp => 未成熟.webp} (100%) rename assets/作物/玫瑰花/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/甘蔗/0.webp delete mode 100644 assets/作物/甘蔗/0.webp.import delete mode 100644 assets/作物/甘蔗/3.webp delete mode 100644 assets/作物/甘蔗/3.webp.import rename assets/作物/甘蔗/{1.webp => 幼苗.webp} (100%) rename assets/作物/甘蔗/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/甘蔗/{2.webp => 未成熟.webp} (100%) rename assets/作物/甘蔗/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/甜菜/0.webp delete mode 100644 assets/作物/甜菜/0.webp.import delete mode 100644 assets/作物/甜菜/3.webp delete mode 100644 assets/作物/甜菜/3.webp.import rename assets/作物/甜菜/{1.webp => 幼苗.webp} (100%) rename assets/作物/甜菜/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/甜菜/{2.webp => 未成熟.webp} (100%) rename assets/作物/甜菜/{2.webp.import => 未成熟.webp.import} (70%) rename assets/作物/生菜/{0.webp => 幼苗.webp} (100%) rename assets/作物/生菜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/番茄/{1.webp => 幼苗.webp} (100%) rename assets/作物/番茄/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/番茄/{2.webp => 未成熟.webp} (100%) rename assets/作物/番茄/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/百合花/3.webp delete mode 100644 assets/作物/百合花/3.webp.import rename assets/作物/百合花/{0.webp => 幼苗.webp} (100%) rename assets/作物/百合花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/百合花/{1.webp => 未成熟.webp} (100%) rename assets/作物/百合花/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/石榴/{0.webp => 幼苗.webp} (100%) rename assets/作物/石榴/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/稻谷/2.webp delete mode 100644 assets/作物/稻谷/2.webp.import rename assets/作物/稻谷/{0.webp => 幼苗.webp} (100%) rename assets/作物/稻谷/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/稻谷/{1.webp => 未成熟.webp} (100%) rename assets/作物/稻谷/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/糖果树/{0.webp => 幼苗.webp} (100%) rename assets/作物/糖果树/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/糖果树/{1.webp => 未成熟.webp} (100%) rename assets/作物/糖果树/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/胡萝卜/0.webp delete mode 100644 assets/作物/胡萝卜/1.webp delete mode 100644 assets/作物/胡萝卜/1.webp.import rename assets/作物/{玉米/0.webp => 胡萝卜/幼苗.webp} (100%) rename assets/作物/胡萝卜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/胡萝卜/{2.webp => 未成熟.webp} (100%) rename assets/作物/胡萝卜/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/芒果/0.webp delete mode 100644 assets/作物/芒果/0.webp.import rename assets/作物/芒果/{1.webp => 幼苗.webp} (100%) rename assets/作物/芒果/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/芒果/{2.webp => 未成熟.webp} (100%) rename assets/作物/芒果/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/芦笋/0.webp delete mode 100644 assets/作物/芦笋/0.webp.import rename assets/作物/芦笋/{2.webp => 幼苗.webp} (100%) rename assets/作物/芦笋/{2.webp.import => 幼苗.webp.import} (71%) rename assets/作物/芦笋/{1.webp => 未成熟.webp} (100%) rename assets/作物/芦笋/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/芦荟/0.webp delete mode 100644 assets/作物/芦荟/0.webp.import delete mode 100644 assets/作物/芦荟/3.webp delete mode 100644 assets/作物/芦荟/3.webp.import rename assets/作物/芦荟/{1.webp => 幼苗.webp} (100%) rename assets/作物/芦荟/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/芦荟/{2.webp => 未成熟.webp} (100%) rename assets/作物/芦荟/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/花椰菜/2.webp delete mode 100644 assets/作物/花椰菜/2.webp.import delete mode 100644 assets/作物/花椰菜/3.webp delete mode 100644 assets/作物/花椰菜/3.webp.import rename assets/作物/花椰菜/{0.webp => 幼苗.webp} (100%) rename assets/作物/花椰菜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/花椰菜/{1.webp => 未成熟.webp} (100%) rename assets/作物/花椰菜/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/苹果/0.webp delete mode 100644 assets/作物/苹果/0.webp.import rename assets/作物/苹果/{1.webp => 幼苗.webp} (100%) rename assets/作物/苹果/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/苹果/{2.webp => 未成熟.webp} (100%) rename assets/作物/苹果/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/茄子/1.webp delete mode 100644 assets/作物/茄子/1.webp.import delete mode 100644 assets/作物/茄子/2.webp delete mode 100644 assets/作物/茄子/2.webp.import delete mode 100644 assets/作物/茄子/4.webp rename assets/作物/茄子/{0.webp => 幼苗.webp} (100%) rename assets/作物/茄子/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/茄子/{3.webp => 未成熟.webp} (100%) rename assets/作物/茄子/{3.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/草莓/2.webp delete mode 100644 assets/作物/草莓/2.webp.import delete mode 100644 assets/作物/草莓/3.webp delete mode 100644 assets/作物/草莓/3.webp.import rename assets/作物/草莓/{0.webp => 幼苗.webp} (100%) rename assets/作物/草莓/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/草莓/{1.webp => 未成熟.webp} (100%) rename assets/作物/草莓/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/荔枝/0.webp delete mode 100644 assets/作物/荔枝/0.webp.import rename assets/作物/荔枝/{1.webp => 幼苗.webp} (100%) rename assets/作物/荔枝/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/荔枝/{2.webp => 未成熟.webp} (100%) rename assets/作物/荔枝/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/菊花/1.webp delete mode 100644 assets/作物/菊花/1.webp.import delete mode 100644 assets/作物/菊花/3.webp delete mode 100644 assets/作物/菊花/3.webp.import rename assets/作物/菊花/{0.webp => 幼苗.webp} (100%) rename assets/作物/菊花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/菊花/{2.webp => 未成熟.webp} (100%) rename assets/作物/菊花/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/菠萝/1.webp delete mode 100644 assets/作物/菠萝/1.webp.import rename assets/作物/菠萝/{0.webp => 幼苗.webp} (100%) rename assets/作物/菠萝/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/菠萝/{2.webp => 未成熟.webp} (100%) rename assets/作物/菠萝/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/葡萄/2.webp delete mode 100644 assets/作物/葡萄/2.webp.import delete mode 100644 assets/作物/葡萄/3.webp delete mode 100644 assets/作物/葡萄/3.webp.import rename assets/作物/葡萄/{0.webp => 幼苗.webp} (100%) rename assets/作物/葡萄/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/葡萄/{1.webp => 未成熟.webp} (100%) rename assets/作物/葡萄/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/葫芦/{0.webp => 幼苗.webp} (100%) rename assets/作物/葫芦/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/葫芦/{1.webp => 未成熟.webp} (100%) rename assets/作物/葫芦/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/蓝莓/1.webp delete mode 100644 assets/作物/蓝莓/1.webp.import delete mode 100644 assets/作物/蓝莓/3.webp delete mode 100644 assets/作物/蓝莓/3.webp.import rename assets/作物/蓝莓/{0.webp => 幼苗.webp} (100%) rename assets/作物/蓝莓/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/蓝莓/{2.webp => 未成熟.webp} (100%) rename assets/作物/蓝莓/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/蕨菜/0.webp delete mode 100644 assets/作物/蕨菜/0.webp.import rename assets/作物/蕨菜/{1.webp => 幼苗.webp} (100%) rename assets/作物/蕨菜/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/蕨菜/{2.webp => 未成熟.webp} (100%) rename assets/作物/蕨菜/{2.webp.import => 未成熟.webp.import} (70%) rename assets/作物/藏红花/{0.webp => 幼苗.webp} (100%) rename assets/作物/藏红花/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/藏羚羊草/{0.webp => 幼苗.webp} (100%) rename assets/作物/藏羚羊草/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/西瓜/1.webp delete mode 100644 assets/作物/西瓜/1.webp.import delete mode 100644 assets/作物/西瓜/3.webp delete mode 100644 assets/作物/西瓜/3.webp.import rename assets/作物/西瓜/{0.webp => 幼苗.webp} (100%) rename assets/作物/西瓜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/西瓜/{2.webp => 未成熟.webp} (100%) rename assets/作物/西瓜/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/豌豆/2.webp delete mode 100644 assets/作物/豌豆/2.webp.import rename assets/作物/豌豆/{0.webp => 幼苗.webp} (100%) rename assets/作物/豌豆/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/豌豆/{1.webp => 未成熟.webp} (100%) rename assets/作物/豌豆/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/辣椒/2.webp delete mode 100644 assets/作物/辣椒/2.webp.import rename assets/作物/辣椒/{0.webp => 幼苗.webp} (100%) rename assets/作物/辣椒/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/辣椒/{1.webp => 未成熟.webp} (100%) rename assets/作物/辣椒/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/迷迭香/{0.webp => 幼苗.webp} (100%) rename assets/作物/迷迭香/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/郁金香/0.webp delete mode 100644 assets/作物/郁金香/0.webp.import rename assets/作物/郁金香/{1.webp => 幼苗.webp} (100%) rename assets/作物/郁金香/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/郁金香/{2.webp => 未成熟.webp} (100%) rename assets/作物/郁金香/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/野草1/0.webp.import rename assets/作物/野草1/{0.webp => 幼苗.webp} (100%) rename assets/作物/{凤凰木/0.webp.import => 野草1/幼苗.webp.import} (74%) rename assets/作物/野草2/{0.webp => 幼苗.webp} (100%) rename assets/作物/野草2/{0.webp.import => 幼苗.webp.import} (71%) delete mode 100644 assets/作物/金橘/1.webp delete mode 100644 assets/作物/金橘/1.webp.import delete mode 100644 assets/作物/金橘/3.webp delete mode 100644 assets/作物/金橘/3.webp.import rename assets/作物/金橘/{0.webp => 幼苗.webp} (100%) rename assets/作物/金橘/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/金橘/{2.webp => 未成熟.webp} (100%) rename assets/作物/金橘/{2.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/面包树/2.webp delete mode 100644 assets/作物/面包树/2.webp.import rename assets/作物/面包树/{0.webp => 幼苗.webp} (100%) rename assets/作物/面包树/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/面包树/{1.webp => 未成熟.webp} (100%) rename assets/作物/面包树/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/香草/0.webp delete mode 100644 assets/作物/香草/0.webp.import delete mode 100644 assets/作物/香草/2.webp delete mode 100644 assets/作物/香草/2.webp.import rename assets/作物/香草/{1.webp => 幼苗.webp} (100%) rename assets/作物/香草/{1.webp.import => 幼苗.webp.import} (71%) rename assets/作物/香草/{3.webp => 未成熟.webp} (100%) rename assets/作物/香草/{3.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/香蕉/2.webp delete mode 100644 assets/作物/香蕉/2.webp.import rename assets/作物/香蕉/{0.webp => 幼苗.webp} (100%) rename assets/作物/香蕉/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/香蕉/{1.webp => 未成熟.webp} (100%) rename assets/作物/香蕉/{1.webp.import => 未成熟.webp.import} (70%) delete mode 100644 assets/作物/鱼腥草/1.webp.import delete mode 100644 assets/作物/鱼腥草/2.webp delete mode 100644 assets/作物/鱼腥草/2.webp.import rename assets/作物/鱼腥草/{0.webp => 幼苗.webp} (100%) rename assets/作物/鱼腥草/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/鱼腥草/{1.webp => 未成熟.webp} (100%) rename assets/作物/{橘子/0.webp.import => 鱼腥草/未成熟.webp.import} (74%) rename assets/作物/黄瓜/{0.webp => 幼苗.webp} (100%) rename assets/作物/黄瓜/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/黄瓜/{1.webp => 未成熟.webp} (100%) rename assets/作物/黄瓜/{1.webp.import => 未成熟.webp.import} (70%) rename assets/作物/默认/{0.webp => 幼苗.webp} (100%) rename assets/作物/默认/{0.webp.import => 幼苗.webp.import} (71%) create mode 100644 assets/作物/默认/未成熟.webp create mode 100644 assets/作物/默认/未成熟.webp.import delete mode 100644 assets/作物/龙果/2.webp delete mode 100644 assets/作物/龙果/2.webp.import rename assets/作物/龙果/{0.webp => 幼苗.webp} (100%) rename assets/作物/龙果/{0.webp.import => 幼苗.webp.import} (71%) rename assets/作物/龙果/{1.webp => 未成熟.webp} (100%) rename assets/作物/龙果/{1.webp.import => 未成熟.webp.import} (70%) create mode 100644 assets/字体/MapleMono-NF-CN-Bold.ttf create mode 100644 assets/字体/MapleMono-NF-CN-Bold.ttf.import create mode 100644 assets/字体/MapleMono-NF-CN-BoldItalic.ttf create mode 100644 assets/字体/MapleMono-NF-CN-BoldItalic.ttf.import create mode 100644 assets/装饰物图片/宠物商店.webp create mode 100644 assets/装饰物图片/宠物商店.webp.import create mode 100644 assets/装饰物图片/小卖部.webp create mode 100644 assets/装饰物图片/小卖部.webp.import create mode 100644 assets/装饰物图片/种子商店.webp create mode 100644 assets/装饰物图片/种子商店.webp.import create mode 100644 assets/装饰物图片/道具商店.webp create mode 100644 assets/装饰物图片/道具商店.webp.import diff --git a/GUI/MainMenuPanel.tscn b/GUI/MainMenuPanel.tscn index 8ab41ca..8da9e46 100644 --- a/GUI/MainMenuPanel.tscn +++ b/GUI/MainMenuPanel.tscn @@ -184,8 +184,8 @@ theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) theme_override_constants/shadow_offset_x = 5 theme_override_constants/shadow_offset_y = 5 -theme_override_constants/outline_size = 10 -theme_override_constants/shadow_outline_size = 10 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 theme_override_font_sizes/font_size = 45 text = "关于" horizontal_alignment = 1 diff --git a/GameManager/WeatherSystem.gd b/GameManager/WeatherSystem.gd new file mode 100644 index 0000000..138f18e --- /dev/null +++ b/GameManager/WeatherSystem.gd @@ -0,0 +1,51 @@ +extends Node2D + +@onready var cherry_blossom_rain: Node2D = $CherryBlossomRain #栀子花雨 +@onready var gardenia_rain: Node2D = $GardeniaRain #樱花雨 +@onready var willow_leaf_rain: Node2D = $WillowLeafRain #柳叶雨 +@onready var rain: GPUParticles2D = $Rain #下雨 +@onready var snow: GPUParticles2D = $Snow #下雪 + +# 天气系统 +# 要显示哪种天气直接调用相应天气的show()然后一并隐藏其他天气hide() + +# 设置天气的统一方法 +func set_weather(weather_type: String): + # 先隐藏所有天气效果 + hide_all_weather() + + # 根据天气类型显示对应效果 + match weather_type: + "clear", "stop": + # 晴天或停止天气 - 所有天气效果都隐藏 + pass + "rain": + if rain: + rain.show() + "snow": + if snow: + snow.show() + "cherry": + if cherry_blossom_rain: + cherry_blossom_rain.show() + "gardenia": + if gardenia_rain: + gardenia_rain.show() + "willow": + if willow_leaf_rain: + willow_leaf_rain.show() + _: + print("未知的天气类型: ", weather_type) + +# 隐藏所有天气效果 +func hide_all_weather(): + if cherry_blossom_rain: + cherry_blossom_rain.hide() + if gardenia_rain: + gardenia_rain.hide() + if willow_leaf_rain: + willow_leaf_rain.hide() + if rain: + rain.hide() + if snow: + snow.hide() diff --git a/GameManager/WeatherSystem.gd.uid b/GameManager/WeatherSystem.gd.uid new file mode 100644 index 0000000..ca1324b --- /dev/null +++ b/GameManager/WeatherSystem.gd.uid @@ -0,0 +1 @@ +uid://o4mcuqoivqri diff --git a/MainGame.gd b/MainGame.gd index 8d68cfb..c363721 100644 --- a/MainGame.gd +++ b/MainGame.gd @@ -11,7 +11,7 @@ extends Node @onready var show_level : Label = $UI/GUI/GameInfoHBox1/level # 显示当前玩家的等级 @onready var show_tip : Label = $UI/GUI/GameInfoHBox3/tip # 显示小提示 @onready var show_like: Label = $UI/GUI/GameInfoHBox1/like # 显示别人给自己点赞的总赞数 -@onready var show_onlineplayer: Label = $UI/GUI/GameInfoHBox2/onlineplayer # 显示服务器在线人数 +@onready var show_onlineplayer: Label = $UI/GUI/GameInfoHBox3/onlineplayer # 显示服务器在线人数 @onready var show_player_name : Label = $UI/GUI/GameInfoHBox2/player_name # 显示玩家昵称 @onready var show_farm_name : Label = $UI/GUI/GameInfoHBox2/farm_name # 显示农场名称 @onready var show_status_label : Label = $UI/GUI/GameInfoHBox2/StatusLabel # 显示与服务器连接状态 @@ -67,6 +67,7 @@ extends Node @onready var pet_store_panel: Panel = $UI/BigPanel/PetStorePanel #宠物商店面板 @onready var pet_fight_panel: Panel = $UI/BigPanel/PetFightPanel #宠物战斗面板 @onready var pet_inform_panel: Panel = $UI/SmallPanel/PetInformPanel #宠物信息面板 +@onready var player_store_panel: Panel = $UI/BigPanel/PlayerStorePanel #玩家小卖部面板 #小面板 @@ -92,6 +93,9 @@ extends Node @onready var tree_status: Label = $Decoration/WisdomTree/TreeStatus #智慧树状态 只显示 等级和高度 @onready var anonymous_talk: RichTextLabel = $Decoration/WisdomTree/BackgroundPanel/AnonymousTalk #给智慧树听音乐100%会刷新 施肥浇水 +#天气系统 +@onready var weather_system: Node2D = $WeatherSystem #天气系统节点 + #各种弹窗 @onready var accept_dialog: AcceptDialog = $UI/DiaLog/AcceptDialog @@ -200,10 +204,6 @@ func _ready(): # 初始化调试面板(默认隐藏) debug_panel.hide() - debug_panel_script = debug_panel - - # 在加载进度面板上添加调试按钮 - _add_debug_button_to_loading_panel() #未登录时隐藏所有UI game_info_h_box_1.hide() @@ -1158,16 +1158,17 @@ func _handle_item_config_response(response_data): #===============================================作物图片缓存系统=============================================== - ## 优化的作物图片缓存和管理系统 class CropTextureManager: - # 缓存字典 - var texture_cache: Dictionary = {} # 序列帧缓存 {crop_name: [Texture2D]} - var mature_texture_cache: Dictionary = {} # 成熟图片缓存 {crop_name: Texture2D} - var frame_counts: Dictionary = {} # 帧数记录 {crop_name: int} + # 缓存字典 - 改为三阶段图片缓存 + var texture_cache: Dictionary = {} # 阶段图片缓存 {crop_name: {"幼苗": Texture2D, "未成熟": Texture2D, "成熟": Texture2D}} + var default_textures: Dictionary = {} # 默认图片缓存 {"幼苗": Texture2D, "未成熟": Texture2D, "成熟": Texture2D} var failed_resources: Array = [] # 记录加载失败的资源路径 + # 三个生长阶段 + const GROWTH_STAGES = ["幼苗", "未成熟", "成熟"] + # 加载状态 var is_loading: bool = false var load_progress: float = 0.0 @@ -1183,9 +1184,6 @@ class CropTextureManager: var results_mutex: Mutex var completed_results: Array = [] - # 调试面板引用 - var debug_panel_ref = null - # 内存管理 var max_cache_size: int = 300 # 最大缓存图片数量 var cache_access_order: Array = [] # LRU缓存访问顺序 @@ -1196,8 +1194,6 @@ class CropTextureManager: results_mutex = Mutex.new() # 根据设备性能动态调整线程数 _adjust_thread_count() - # 尝试获取调试面板引用 - _connect_debug_panel() ## 根据设备性能调整线程数 func _adjust_thread_count(): @@ -1216,28 +1212,8 @@ class CropTextureManager: print("[CropTextureManager] 设备: %s, CPU核心: %d, 使用线程数: %d" % [platform, processor_count, max_threads]) - ## 连接调试面板 - func _connect_debug_panel(): - # 延迟获取调试面板引用,因为初始化时可能还未创建 - call_deferred("_try_get_debug_panel") - - ## 尝试获取调试面板引用 - func _try_get_debug_panel(): - var main_node = Engine.get_main_loop().current_scene - if main_node: - debug_panel_ref = main_node.get_node_or_null("UI/SmallPanel/DebugPanel") - if debug_panel_ref: - print("[CropTextureManager] 已连接到调试面板") - - ## 向调试面板发送消息 - func _send_debug_message(message: String, color: Color = Color.WHITE): - if debug_panel_ref and debug_panel_ref.has_method("add_debug_message"): - debug_panel_ref.add_debug_message(message, color) - - ## 设置当前加载项目 - func _set_current_loading_item(item_name: String): - if debug_panel_ref and debug_panel_ref.has_method("set_current_loading_item"): - debug_panel_ref.set_current_loading_item(item_name) + + ## 异步预加载所有作物图片 - 主要入口函数 func preload_all_textures_async(crop_data: Dictionary, progress_callback: Callable) -> void: @@ -1254,23 +1230,18 @@ class CropTextureManager: completed_results.clear() print("[CropTextureManager] 开始预加载 %d 种作物图片" % total_crops) - _send_debug_message("开始预加载 %d 种作物图片" % total_crops, Color.CYAN) # 阶段1:加载默认图片 (0-10%) progress_callback.call(0, "正在加载默认图片...") - _send_debug_message("阶段1: 加载默认图片", Color.YELLOW) await _load_default_textures_async() progress_callback.call(10, "默认图片加载完成") - _send_debug_message("默认图片加载完成", Color.GREEN) # 阶段2:多线程批量加载作物图片 (10-90%) - _send_debug_message("阶段2: 多线程加载作物图片", Color.YELLOW) await _load_crops_multithreaded_async(crop_data, progress_callback) # 阶段3:完成 (90-100%) progress_callback.call(100, "所有作物图片加载完成!") _print_cache_stats() - _send_debug_message("所有作物图片加载完成!", Color.GREEN) # 清理线程 await _cleanup_threads() @@ -1278,28 +1249,23 @@ class CropTextureManager: is_loading = false var success_message = "预加载完成,成功: %d, 失败: %d" % [loaded_crops, failed_crops] print("[CropTextureManager] " + success_message) - _send_debug_message(success_message, Color.CYAN) ## 多线程批量异步加载作物图片 func _load_crops_multithreaded_async(crop_data: Dictionary, progress_callback: Callable) -> void: var crop_names = crop_data.keys() - # 准备加载队列 + # 准备加载队列 - 为每个作物的每个阶段创建任务 loading_mutex.lock() loading_queue.clear() for crop_name in crop_names: - loading_queue.append({ - "crop_name": crop_name, - "type": "sequence" - }) - loading_queue.append({ - "crop_name": crop_name, - "type": "mature" - }) + for stage in GROWTH_STAGES: + loading_queue.append({ + "crop_name": crop_name, + "stage": stage + }) loading_mutex.unlock() # 启动工作线程 - _send_debug_message("启动 %d 个工作线程" % max_threads, Color.CYAN) for i in range(max_threads): var thread = Thread.new() worker_threads.append(thread) @@ -1363,66 +1329,42 @@ class CropTextureManager: ## 执行单个纹理加载任务 func _load_texture_task(task: Dictionary) -> Dictionary: var crop_name = task["crop_name"] - var task_type = task["type"] + var stage = task["stage"] var result = { "crop_name": crop_name, - "type": task_type, + "stage": stage, "success": false, - "textures": [], "texture": null, "error": "" } - if task_type == "sequence": - result["textures"] = _load_crop_textures_threadsafe(crop_name) - result["success"] = result["textures"].size() > 0 - elif task_type == "mature": - result["texture"] = _load_mature_texture_threadsafe(crop_name) - result["success"] = result["texture"] != null + result["texture"] = _load_stage_texture_threadsafe(crop_name, stage) + result["success"] = result["texture"] != null # 检查加载是否成功 if not result["success"]: - result["error"] = "加载失败: " + crop_name - failed_resources.append(crop_name) + result["error"] = "加载失败: %s - %s" % [crop_name, stage] + failed_resources.append(crop_name + "/" + stage) return result - ## 线程安全的作物序列帧加载 - func _load_crop_textures_threadsafe(crop_name: String) -> Array: - var textures = [] + ## 线程安全的阶段图片加载 + func _load_stage_texture_threadsafe(crop_name: String, stage: String) -> Texture2D: var crop_path = "res://assets/作物/" + crop_name + "/" + var texture_path = crop_path + stage + ".webp" - # 检查作物文件夹是否存在 - if not DirAccess.dir_exists_absolute(crop_path): - return [] - - # 使用ResourceLoader.load_threaded_request进行异步加载 - var frame_index = 0 - var max_frames = 20 # 限制最大帧数,避免无限循环 - - while frame_index < max_frames: - var texture_path = crop_path + str(frame_index) + ".webp" - - if not ResourceLoader.exists(texture_path): - break - - # 使用线程安全的资源加载 + # 首先尝试加载作物特定的阶段图片 + if ResourceLoader.exists(texture_path): var texture = _load_resource_safe(texture_path) if texture: - textures.append(texture) - frame_index += 1 - else: - break + return texture - return textures - - ## 线程安全的成熟图片加载 - func _load_mature_texture_threadsafe(crop_name: String) -> Texture2D: - var crop_path = "res://assets/作物/" + crop_name + "/" - var mature_path = crop_path + "成熟.webp" - - if ResourceLoader.exists(mature_path): - return _load_resource_safe(mature_path) + # 如果没有找到,尝试使用默认图片 + var default_path = "res://assets/作物/默认/" + stage + ".webp" + if ResourceLoader.exists(default_path): + var default_texture = _load_resource_safe(default_path) + if default_texture: + return default_texture return null @@ -1448,76 +1390,41 @@ class CropTextureManager: ## 应用加载结果到缓存 func _apply_loading_result(result: Dictionary): var crop_name = result["crop_name"] - var task_type = result["type"] + var stage = result["stage"] var success = result["success"] if not success: - var error_msg = "加载失败: %s (%s)" % [crop_name, task_type] - _send_debug_message(error_msg, Color.RED) return - if task_type == "sequence": - var textures = result["textures"] - if textures.size() > 0: - texture_cache[crop_name] = textures - frame_counts[crop_name] = textures.size() - _update_cache_access(crop_name) - _send_debug_message("✓ %s: %d帧" % [crop_name, textures.size()], Color.GREEN) - elif task_type == "mature": - var texture = result["texture"] - if texture: - mature_texture_cache[crop_name] = texture - _update_cache_access(crop_name + "_mature") - _send_debug_message("✓ %s: 成熟图片" % crop_name, Color.GREEN) + var texture = result["texture"] + if texture: + # 确保作物有缓存字典 + if not texture_cache.has(crop_name): + texture_cache[crop_name] = {} + + # 缓存阶段图片 + texture_cache[crop_name][stage] = texture + _update_cache_access(crop_name + "_" + stage) # 检查缓存大小,必要时清理 _check_and_cleanup_cache() ## 立即加载默认图片(同步,但优化) func _load_default_textures_async() -> void: - const DEFAULT_CROP = "默认" const DEFAULT_PATH = "res://assets/作物/默认/" - if texture_cache.has(DEFAULT_CROP): + if default_textures.size() > 0: return - var textures = [] - var frame_index = 0 - - # 限制默认图片帧数 - while frame_index < 10: - var texture_path = DEFAULT_PATH + str(frame_index) + ".webp" + # 加载三个阶段的默认图片 + for stage in GROWTH_STAGES: + var texture_path = DEFAULT_PATH + stage + ".webp" if ResourceLoader.exists(texture_path): var texture = _load_resource_safe(texture_path) if texture: - textures.append(texture) - frame_index += 1 - else: - break - else: - break + default_textures[stage] = texture - # 如果没有序列帧,尝试加载单个图片 - if textures.size() == 0: - var single_path = DEFAULT_PATH + "0.webp" - if ResourceLoader.exists(single_path): - var texture = _load_resource_safe(single_path) - if texture: - textures.append(texture) - - # 缓存结果 - if textures.size() > 0: - texture_cache[DEFAULT_CROP] = textures - frame_counts[DEFAULT_CROP] = textures.size() - - # 加载默认成熟图片 - var mature_path = DEFAULT_PATH + "成熟.webp" - if ResourceLoader.exists(mature_path): - var mature_texture = _load_resource_safe(mature_path) - if mature_texture: - mature_texture_cache[DEFAULT_CROP] = mature_texture - - print("[CropTextureManager] 默认图片加载完成:%d 帧" % textures.size()) + print("[CropTextureManager] 默认图片加载完成:%d 个阶段" % default_textures.size()) # 让出一帧 await Engine.get_main_loop().process_frame @@ -1530,62 +1437,81 @@ class CropTextureManager: ## 检查并清理缓存 func _check_and_cleanup_cache(): - var total_cached = texture_cache.size() + mature_texture_cache.size() + var total_cached = 0 + for crop_name in texture_cache.keys(): + total_cached += texture_cache[crop_name].size() if total_cached > max_cache_size: var to_remove = total_cached - max_cache_size + 10 # 多清理一些 - _send_debug_message("⚠ 缓存超限,开始清理 %d 个项目" % to_remove, Color.ORANGE) for i in range(min(to_remove, cache_access_order.size())): var key = cache_access_order[i] - # 不清理默认图片 + # 不清理默认图片相关的键 if key.begins_with("默认"): continue - if key.ends_with("_mature"): - var crop_name = key.replace("_mature", "") - mature_texture_cache.erase(crop_name) - else: - texture_cache.erase(key) - frame_counts.erase(key) + # 解析键:crop_name_stage + var parts = key.split("_") + if parts.size() >= 2: + var stage = parts[-1] + var crop_name = "_".join(parts.slice(0, -1)) + + if texture_cache.has(crop_name) and texture_cache[crop_name].has(stage): + texture_cache[crop_name].erase(stage) + + # 如果作物的所有阶段都被清理,删除作物条目 + if texture_cache[crop_name].is_empty(): + texture_cache.erase(crop_name) # 更新访问顺序 cache_access_order = cache_access_order.slice(to_remove) - var current_size = texture_cache.size() + mature_texture_cache.size() + var current_size = 0 + for crop_name in texture_cache.keys(): + current_size += texture_cache[crop_name].size() var cleanup_msg = "缓存清理完成,当前缓存: %d" % current_size print("[CropTextureManager] " + cleanup_msg) - _send_debug_message(cleanup_msg, Color.YELLOW) ## 根据生长进度获取作物图片(带缓存优化) func get_texture_by_progress(crop_name: String, progress: float) -> Texture2D: + # 根据进度确定阶段 + var stage = "" + if progress < 0.33: + stage = "幼苗" + elif progress < 0.85: + stage = "未成熟" + else: + stage = "成熟" + # 更新访问记录 - _update_cache_access(crop_name) + var access_key = crop_name + "_" + stage + _update_cache_access(access_key) - # 100%成熟时优先使用成熟图片 - if progress >= 1.0: - var mature_texture = mature_texture_cache.get(crop_name, null) - if mature_texture: - _update_cache_access(crop_name + "_mature") - return mature_texture + # 尝试获取作物特定的阶段图片 + if texture_cache.has(crop_name) and texture_cache[crop_name].has(stage): + var texture = texture_cache[crop_name][stage] + if texture: + return texture - # 使用序列帧图片 - var textures = texture_cache.get(crop_name, []) - if textures.size() == 0: - # 如果没有缓存,尝试使用默认图片 - textures = texture_cache.get("默认", []) - if textures.size() == 0: - return null + # 如果没有缓存,尝试直接从文件夹加载 + var direct_texture = _load_stage_texture_threadsafe(crop_name, stage) + if direct_texture: + # 将直接加载的图片加入缓存,避免下次再次加载 + if not texture_cache.has(crop_name): + texture_cache[crop_name] = {} + texture_cache[crop_name][stage] = direct_texture + _update_cache_access(crop_name + "_" + stage) + return direct_texture - if textures.size() == 1: - return textures[0] + # 最后才回退到默认图片 + if default_textures.has(stage): + var default_texture = default_textures[stage] + if default_texture: + return default_texture - # 根据进度计算帧索引 - var frame_index = int(progress * (textures.size() - 1)) - frame_index = clamp(frame_index, 0, textures.size() - 1) - - return textures[frame_index] + # 如果都没有,返回null + return null ## 清理线程 func _cleanup_threads() -> void: @@ -1599,8 +1525,7 @@ class CropTextureManager: func clear_cache() -> void: await _cleanup_threads() texture_cache.clear() - mature_texture_cache.clear() - frame_counts.clear() + default_textures.clear() cache_access_order.clear() failed_resources.clear() print("[CropTextureManager] 缓存已清理") @@ -1608,13 +1533,13 @@ class CropTextureManager: ## 打印缓存统计信息 func _print_cache_stats() -> void: print("[CropTextureManager] 缓存统计:") - print(" - 序列帧缓存: %d 种作物" % texture_cache.size()) - print(" - 成熟图片缓存: %d 种作物" % mature_texture_cache.size()) + print(" - 阶段图片缓存: %d 种作物" % texture_cache.size()) + print(" - 默认图片缓存: %d 个阶段" % default_textures.size()) print(" - 加载失败: %d 个资源" % failed_resources.size()) - var total_frames = 0 - for count in frame_counts.values(): - total_frames += count - print(" - 总图片帧数: %d 帧" % total_frames) + var total_stages = 0 + for crop_name in texture_cache.keys(): + total_stages += texture_cache[crop_name].size() + print(" - 总阶段图片数: %d 个" % total_stages) if failed_resources.size() > 0: print(" - 失败的资源:") @@ -1625,13 +1550,15 @@ class CropTextureManager: func get_cache_info() -> String: var info = "作物图片缓存详情:\n" for crop_name in texture_cache.keys(): - var frame_count = frame_counts.get(crop_name, 0) - var has_mature = mature_texture_cache.has(crop_name) - info += " - %s: %d帧" % [crop_name, frame_count] - if has_mature: - info += " (含成熟图片)" + var stages = texture_cache[crop_name].keys() + info += " - %s: %s" % [crop_name, "/".join(stages)] info += "\n" + if default_textures.size() > 0: + info += "\n默认图片:\n" + for stage in default_textures.keys(): + info += " - " + stage + "\n" + if failed_resources.size() > 0: info += "\n加载失败的资源:\n" for failed in failed_resources: @@ -1645,25 +1572,19 @@ class CropTextureManager: for crop_name in common_crops: # 确保常用作物在缓存中 if not texture_cache.has(crop_name): - var textures = _load_crop_textures_threadsafe(crop_name) - if textures.size() > 0: - texture_cache[crop_name] = textures - frame_counts[crop_name] = textures.size() - - if not mature_texture_cache.has(crop_name): - var mature = _load_mature_texture_threadsafe(crop_name) - if mature: - mature_texture_cache[crop_name] = mature + texture_cache[crop_name] = {} + + # 加载各个阶段的图片 + for stage in GROWTH_STAGES: + var texture = _load_stage_texture_threadsafe(crop_name, stage) + if texture: + texture_cache[crop_name][stage] = texture + + # 全局作物图片管理器实例 var crop_texture_manager: CropTextureManager -# 资源加载调试器(可选,用于调试) -var resource_debugger = null - -# 调试面板脚本引用 -var debug_panel_script = null - #===============================================作物图片缓存系统=============================================== @@ -1724,11 +1645,7 @@ func _update_load_progress(progress: int, message: String = "") -> void: if message_label and message != "": message_label.text = message - # 向调试面板发送进度信息 - if debug_panel_script and debug_panel_script.has_method("add_debug_message"): - if message != "": - #debug_panel_script.add_debug_message("进度 %d%%: %s" % [progress, message], Color.CYAN) - pass + # 检测卡顿 _check_loading_stuck(progress) @@ -1748,8 +1665,7 @@ func _check_loading_stuck(progress: int): if progress == last_progress_value: var stuck_time = current_time - last_progress_time if stuck_time > 5.0: # 5秒没有进度变化 - if debug_panel_script and debug_panel_script.has_method("add_debug_message"): - debug_panel_script.add_debug_message("⚠ 加载卡顿检测: 在 %d%% 停留了 %.1f 秒" % [progress, stuck_time], Color.ORANGE) + print("⚠ 加载卡顿检测: 在 %d%% 停留了 %.1f 秒" % [progress, stuck_time]) else: # 进度有变化,更新记录 last_progress_value = progress @@ -1797,95 +1713,7 @@ func _wait_for_crop_data() -> void: #===============================================调试和维护工具=============================================== -## 调试:打印缓存信息 -func _debug_print_crop_cache() -> void: - if crop_texture_manager: - print(crop_texture_manager.get_cache_info()) - else: - print("[调试] 作物图片管理器未初始化") -## 调试:强制刷新所有图片 -func _debug_refresh_all_crop_sprites() -> void: - print("[调试] 强制刷新所有地块图片...") - _refresh_all_crop_sprites() - print("[调试] 图片刷新完成") - -## 调试:清理图片缓存 -func _debug_clear_crop_cache() -> void: - if crop_texture_manager: - crop_texture_manager.clear_cache() - print("[调试] 图片缓存已清理") - -## 调试:启用资源加载调试器 -func _debug_enable_resource_debugger() -> void: - if not resource_debugger: - resource_debugger = preload("res://GlobalScript/ResourceLoadingDebugger.gd").new() - add_child(resource_debugger) - print("[调试] 资源加载调试器已启用") - else: - print("[调试] 资源加载调试器已经在运行") - -## 调试:生成资源加载报告 -func _debug_generate_loading_report() -> void: - if resource_debugger: - var report = resource_debugger.generate_loading_report() - print(report) - resource_debugger.export_debug_data_to_file() - else: - print("[调试] 资源加载调试器未启用,请先调用 _debug_enable_resource_debugger()") - -## 调试:检测设备能力 -func _debug_detect_device_capabilities() -> void: - if resource_debugger: - var capabilities = resource_debugger.detect_device_capabilities() - print("[调试] 设备能力检测结果:") - for key in capabilities: - print(" %s: %s" % [key, str(capabilities[key])]) - else: - print("[调试] 资源加载调试器未启用") - -## 调试:强制触发低内存模式 -func _debug_trigger_low_memory_mode() -> void: - if crop_texture_manager: - # 临时降低缓存大小来模拟低内存环境 - crop_texture_manager.max_cache_size = 50 - crop_texture_manager._check_and_cleanup_cache() - print("[调试] 已触发低内存模式,缓存大小限制为50") - -## 调试:恢复正常内存模式 -func _debug_restore_normal_memory_mode() -> void: - if crop_texture_manager: - crop_texture_manager.max_cache_size = 200 - print("[调试] 已恢复正常内存模式,缓存大小限制为200") - -## 在加载进度面板上添加调试按钮 -func _add_debug_button_to_loading_panel(): - # 创建调试按钮 - var debug_button = Button.new() - debug_button.text = "调试信息" - debug_button.size_flags_horizontal = Control.SIZE_SHRINK_CENTER - debug_button.position = Vector2(10, 500) # 左下角位置 - debug_button.size = Vector2(120, 40) - - # 设置按钮样式 - debug_button.modulate = Color(0.8, 0.8, 1.0, 0.9) # 半透明蓝色 - - # 连接点击信号 - debug_button.pressed.connect(_on_debug_button_pressed) - - # 添加到加载进度面板 - load_progress_panel.add_child(debug_button) - - print("[MainGame] 调试按钮已添加到加载进度面板") - -## 调试按钮点击处理 -func _on_debug_button_pressed(): - if debug_panel.visible: - debug_panel.hide() - else: - debug_panel.show() - debug_panel.move_to_front() - print("[MainGame] 调试面板切换显示状态") #===============================================调试和维护工具=============================================== @@ -1894,8 +1722,14 @@ func _on_debug_button_pressed(): #===============================================向后兼容性=============================================== # 为了保持向后兼容,保留一些原来的函数名 func _load_crop_textures(crop_name: String) -> Array: - if crop_texture_manager: - return crop_texture_manager._load_crop_textures_threadsafe(crop_name) + # 返回所有阶段的图片 + if crop_texture_manager and crop_texture_manager.texture_cache.has(crop_name): + var stages = crop_texture_manager.texture_cache[crop_name] + var textures = [] + for stage in crop_texture_manager.GROWTH_STAGES: + if stages.has(stage): + textures.append(stages[stage]) + return textures return [] func _get_crop_texture_by_progress(crop_name: String, progress: float) -> Texture2D: @@ -2398,12 +2232,11 @@ func _handle_broadcast_history_response(data: Dictionary): # 更新主界面大喇叭显示为最新消息 if global_server_broadcast: var latest_message = global_server_broadcast_panel.get_latest_message() - if latest_message != "暂无消息": + if latest_message != "全服大喇叭": global_server_broadcast.text = latest_message print("主界面大喇叭已更新为: ", latest_message) else: - global_server_broadcast.text = "" - print("没有消息,清空主界面大喇叭显示") + global_server_broadcast.text = "全服大喇叭" # 初始化大喇叭显示 @@ -3479,3 +3312,39 @@ func _handle_wisdom_tree_config_response(data): wisdom_tree_panel.wisdom_tree_config = config wisdom_tree_panel.update_ui() # ======================================= 智慧树系统 ========================================= + + +# ======================================= 天气系统 ========================================= +# 处理天气变更 +func _handle_weather_change(weather_type: String, weather_name: String): + """处理服务器发送的天气变更消息""" + if weather_system and weather_system.has_method("set_weather"): + weather_system.set_weather(weather_type) + Toast.show("天气已变更为:" + weather_name, Color.CYAN, 3.0) + print("天气已切换为:", weather_name) + else: + print("天气系统不可用") +# ======================================= 天气系统 ========================================= + + +#打开小卖部面板 +func _on_my_store_button_pressed() -> void: + player_store_panel.show() + pass + +#打开小卖部面板 +func _on_player_store_pressed() -> void: + player_store_panel.show() + pass + +func _on_seed_store_pressed() -> void: + crop_store_panel.show() + pass + +func _on_item_store_pressed() -> void: + item_store_panel.show() + pass + +func _on_pet_store_pressed() -> void: + pet_store_panel.show() + pass diff --git a/MainGame.tscn b/MainGame.tscn index 413b5f0..ab5478b 100644 --- a/MainGame.tscn +++ b/MainGame.tscn @@ -1,8 +1,9 @@ -[gd_scene load_steps=85 format=3 uid="uid://dgh61dttaas5a"] +[gd_scene load_steps=96 format=3 uid="uid://dgh61dttaas5a"] [ext_resource type="Script" uid="uid://2pt11sfcaxf7" path="res://MainGame.gd" id="1_v3yaj"] [ext_resource type="Texture2D" uid="uid://du2pyiojliasy" path="res://assets/游戏UI/经验球.webp" id="2_6jgly"] [ext_resource type="PackedScene" uid="uid://bkivlkirrx6u8" path="res://CopyItems/crop_item.tscn" id="3_isiom"] +[ext_resource type="FontFile" uid="uid://dl31rnmd4lia" path="res://assets/字体/MapleMono-NF-CN-Bold.ttf" id="3_t4s8j"] [ext_resource type="Texture2D" uid="uid://ftv231igtdoq" path="res://assets/游戏UI/等级.webp" id="3_y1hsh"] [ext_resource type="Texture2D" uid="uid://bqib5y8uwg6hx" path="res://assets/游戏UI/钱币.webp" id="4_ql8k3"] [ext_resource type="Texture2D" uid="uid://waqbwo2r33j3" path="res://assets/游戏UI/小提示.webp" id="5_5b81d"] @@ -31,6 +32,7 @@ [ext_resource type="PackedScene" uid="uid://j4ft87o7jk14" path="res://Scene/BigPanel/ItemStorePanel.tscn" id="21_uhubb"] [ext_resource type="PackedScene" uid="uid://cw8am7nnbgca5" path="res://Scene/Pet/PetFightPanel.tscn" id="23_n03md"] [ext_resource type="Script" uid="uid://bdhwvqsmakna2" path="res://Script/BigPanel/PetBagPanel.gd" id="23_uc6q1"] +[ext_resource type="Script" uid="uid://bdavskipn547h" path="res://Script/BigPanel/PlayerStorePanel.gd" id="24_dygid"] [ext_resource type="PackedScene" uid="uid://cnjidcwuv4nn4" path="res://Scene/BigPanel/PetStorePanel.tscn" id="24_uc6q1"] [ext_resource type="PackedScene" uid="uid://d3i0l6ysrde6o" path="res://Scene/SmallPanel/AccountSettingPanel.tscn" id="26_uc6q1"] [ext_resource type="PackedScene" uid="uid://d1lu2yg4xl382" path="res://Scene/SmallPanel/LoadProgressPanel.tscn" id="27_vygm6"] @@ -46,31 +48,33 @@ [ext_resource type="PackedScene" uid="uid://ibl5wbbw3pwc" path="res://CopyItems/item_button.tscn" id="39_cdkxt"] [ext_resource type="Script" uid="uid://dwf28j01ckg3y" path="res://Script/SmallPanel/WisdomTreePanel.gd" id="39_np7ck"] [ext_resource type="Texture2D" uid="uid://bt1i2yhhlor5e" path="res://assets/地块/土块1.webp" id="41_cdkxt"] +[ext_resource type="Script" uid="uid://b185o1hjnlrv5" path="res://Script/SmallPanel/CropInformPanel.gd" id="41_iluto"] [ext_resource type="Texture2D" uid="uid://3ff2lnbc0op7" path="res://assets/稻草人图片/稻草人1.webp" id="43_6rkns"] +[ext_resource type="Script" uid="uid://dsmmxivba06ab" path="res://Script/Dialog/BatchSellPopup.gd" id="44_av1bx"] [ext_resource type="Texture2D" uid="uid://cbdm5e6s8bf6l" path="res://assets/智慧树图片/智慧树4.webp" id="45_xvovi"] +[ext_resource type="Script" uid="uid://cha0uw4ra1trr" path="res://Script/Dialog/AddProduct2StorePopup.gd" id="46_8d602"] [ext_resource type="Texture2D" uid="uid://dilipbs0lncpd" path="res://assets/草地图片/草地10.webp" id="48_2i8fe"] [ext_resource type="Texture2D" uid="uid://du34yctd8bd8m" path="res://assets/灌木丛图片/灌木丛1.webp" id="49_xjiif"] [ext_resource type="Texture2D" uid="uid://dswjorjhf1i6f" path="res://assets/灌木丛图片/灌木丛2.webp" id="50_sqnmr"] [ext_resource type="Texture2D" uid="uid://go3n3qnpancf" path="res://assets/灌木丛图片/灌木丛3.webp" id="51_2i8fe"] [ext_resource type="Texture2D" uid="uid://dk4yl4ghmxaa2" path="res://assets/天气系统图片/雪花.webp" id="53_4ka7t"] -[ext_resource type="PackedScene" uid="uid://cvg38nsrm77jy" path="res://Scene/Particle/WillowLeafRain.tscn" id="53_nf3jg"] [ext_resource type="Texture2D" uid="uid://chcgrmluhfxuk" path="res://assets/天气系统图片/樱花1.webp" id="53_tdq2s"] [ext_resource type="Texture2D" uid="uid://c1qqalp7owgy2" path="res://assets/天气系统图片/栀子花1.webp" id="53_xyeuq"] +[ext_resource type="Texture2D" uid="uid://b7g5ut03rm53n" path="res://assets/装饰物图片/小卖部.webp" id="54_06tja"] [ext_resource type="Texture2D" uid="uid://dfkgmj7e0555q" path="res://assets/天气系统图片/樱花2.webp" id="54_dygjy"] [ext_resource type="Texture2D" uid="uid://bfducebx4c4il" path="res://assets/天气系统图片/柳叶3.webp" id="54_jiccn"] [ext_resource type="Texture2D" uid="uid://cd7x78uyi2csh" path="res://assets/天气系统图片/栀子花2.webp" id="54_t4s8j"] [ext_resource type="Texture2D" uid="uid://dl58ie2lneq77" path="res://assets/天气系统图片/柳叶1.webp" id="55_e8wx8"] [ext_resource type="Texture2D" uid="uid://q0difb6wjkgm" path="res://assets/天气系统图片/樱花3.webp" id="55_edvcq"] +[ext_resource type="Texture2D" uid="uid://j3h0ocagxf8" path="res://assets/装饰物图片/种子商店.webp" id="55_g4i0f"] [ext_resource type="Texture2D" uid="uid://cqqyc3ddwtvpn" path="res://assets/天气系统图片/栀子花3.webp" id="55_iluto"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5dq3w"] - -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_nf3jg"] +[ext_resource type="Texture2D" uid="uid://d4e54gh5iccul" path="res://assets/装饰物图片/道具商店.webp" id="56_rlmnt"] +[ext_resource type="Texture2D" uid="uid://deow5dqdm412v" path="res://assets/装饰物图片/宠物商店.webp" id="57_rlmnt"] +[ext_resource type="Script" uid="uid://o4mcuqoivqri" path="res://GameManager/WeatherSystem.gd" id="62_uyv6e"] +[ext_resource type="Texture2D" uid="uid://bnv6wb0k443fv" path="res://assets/天气系统图片/柳叶2.webp" id="69_uyv6e"] [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_adtqp"] -[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_4ka7t"] - [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n03md"] border_width_left = 5 border_width_top = 5 @@ -83,6 +87,18 @@ corner_radius_bottom_left = 20 corner_detail = 20 shadow_size = 20 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_8d602"] +border_width_left = 5 +border_width_top = 5 +border_width_right = 5 +border_width_bottom = 5 +corner_radius_top_left = 20 +corner_radius_top_right = 20 +corner_radius_bottom_right = 20 +corner_radius_bottom_left = 20 +corner_detail = 20 +shadow_size = 20 + [sub_resource type="StyleBoxFlat" id="StyleBoxFlat_5b81d"] border_width_left = 10 border_width_top = 10 @@ -116,10 +132,22 @@ corner_radius_bottom_left = 20 corner_detail = 20 shadow_size = 30 +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_xyeuq"] +border_width_left = 15 +border_width_top = 15 +border_width_right = 15 +border_width_bottom = 15 +corner_detail = 20 + +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_t4s8j"] +border_width_left = 15 +border_width_top = 15 +border_width_right = 15 +border_width_bottom = 15 + [sub_resource type="Environment" id="Environment_m6fch"] background_mode = 3 ambient_light_energy = 0.0 -glow_enabled = true glow_intensity = 1.0 glow_bloom = 0.3 glow_blend_mode = 0 @@ -164,6 +192,18 @@ turbulence_noise_speed = Vector3(10, 0, 0) turbulence_influence_min = 0.02 turbulence_influence_max = 0.07 +[sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_6fhdl"] +particle_flag_disable_z = true +emission_shape = 3 +emission_box_extents = Vector3(1000, 1, 1) +gravity = Vector3(-30, 80, 0) +scale_min = 0.4 +scale_max = 0.5 +turbulence_enabled = true +turbulence_noise_speed = Vector3(10, 0, 0) +turbulence_influence_min = 0.02 +turbulence_influence_max = 0.07 + [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_e8wx8"] particle_flag_disable_z = true emission_shape = 3 @@ -243,7 +283,7 @@ anchors_preset = 0 [node name="GameInfoHBox1" type="HBoxContainer" parent="UI/GUI"] layout_mode = 0 -offset_right = 1400.0 +offset_right = 1339.0 offset_bottom = 35.0 [node name="experience_image" type="TextureRect" parent="UI/GUI/GameInfoHBox1"] @@ -261,8 +301,10 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "经验:999" +vertical_alignment = 1 [node name="level_image" type="TextureRect" parent="UI/GUI/GameInfoHBox1"] layout_mode = 2 @@ -278,6 +320,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "等级:100" @@ -295,6 +338,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "钱币:999" @@ -312,6 +356,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "体力值:20" @@ -329,13 +374,14 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "点赞数:0" [node name="GameInfoHBox2" type="HBoxContainer" parent="UI/GUI"] layout_mode = 0 offset_top = 35.0 -offset_right = 1400.0 +offset_right = 1336.0 offset_bottom = 70.0 [node name="player_name_image" type="TextureRect" parent="UI/GUI/GameInfoHBox2"] @@ -352,8 +398,9 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 -text = "玩家昵称:树萌芽" +text = "玩家昵称:树萌芽" [node name="farm_name_image" type="TextureRect" parent="UI/GUI/GameInfoHBox2"] layout_mode = 2 @@ -369,6 +416,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "农场名称:树萌芽的农场" @@ -385,6 +433,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "服务器状态:正在检测中" @@ -402,31 +451,15 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "FPS:0" -[node name="onlineplayer_image" type="TextureRect" parent="UI/GUI/GameInfoHBox2"] -layout_mode = 2 -texture = ExtResource("10_vygm6") -expand_mode = 2 - -[node name="onlineplayer" type="Label" parent="UI/GUI/GameInfoHBox2"] -layout_mode = 2 -theme_override_colors/font_color = Color(0.423529, 1, 0.533333, 1) -theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) -theme_override_colors/font_outline_color = Color(0, 0, 0, 1) -theme_override_constants/shadow_offset_x = 3 -theme_override_constants/shadow_offset_y = 3 -theme_override_constants/outline_size = 10 -theme_override_constants/shadow_outline_size = 10 -theme_override_font_sizes/font_size = 25 -text = "检测中..." - [node name="GameInfoHBox3" type="HBoxContainer" parent="UI/GUI"] layout_mode = 0 offset_top = 70.0 -offset_right = 1400.0 -offset_bottom = 105.0 +offset_right = 1335.0 +offset_bottom = 113.0 [node name="tip_image" type="TextureRect" parent="UI/GUI/GameInfoHBox3"] layout_mode = 2 @@ -442,6 +475,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "游戏小提示" @@ -460,6 +494,7 @@ theme_override_constants/shadow_offset_x = 3 theme_override_constants/shadow_offset_y = 3 theme_override_constants/outline_size = 10 theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") theme_override_font_sizes/font_size = 25 text = "全服大喇叭" vertical_alignment = 1 @@ -473,6 +508,24 @@ theme_override_constants/outline_size = 10 theme_override_font_sizes/font_size = 25 text = "查看" +[node name="onlineplayer_image" type="TextureRect" parent="UI/GUI/GameInfoHBox3"] +layout_mode = 2 +texture = ExtResource("10_vygm6") +expand_mode = 2 + +[node name="onlineplayer" type="Label" parent="UI/GUI/GameInfoHBox3"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.423529, 1, 0.533333, 1) +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 10 +theme_override_constants/shadow_outline_size = 10 +theme_override_fonts/font = ExtResource("3_t4s8j") +theme_override_font_sizes/font_size = 25 +text = "检测中..." + [node name="FarmVBox" type="VBoxContainer" parent="UI/GUI"] layout_mode = 0 offset_left = 4.0 @@ -483,6 +536,7 @@ scale = Vector2(0.8, 0.8) alignment = 2 [node name="SeedStoreButton" type="Button" parent="UI/GUI/FarmVBox"] +visible = false modulate = Color(1, 0.564706, 0.647059, 1) layout_mode = 2 theme_override_font_sizes/font_size = 40 @@ -501,6 +555,7 @@ theme_override_font_sizes/font_size = 40 text = "作物仓库" [node name="ItemStoreButton" type="Button" parent="UI/GUI/FarmVBox"] +visible = false modulate = Color(0.231373, 0.772549, 1, 1) layout_mode = 2 theme_override_font_sizes/font_size = 40 @@ -560,6 +615,13 @@ offset_bottom = 408.0 scale = Vector2(0.8, 0.8) alignment = 2 +[node name="MyStoreButton" type="Button" parent="UI/GUI/OtherVBox"] +visible = false +modulate = Color(0.917346, 0.737213, 1, 1) +layout_mode = 2 +theme_override_font_sizes/font_size = 40 +text = "我的小卖部" + [node name="AccountSettingButton" type="Button" parent="UI/GUI/OtherVBox"] modulate = Color(0.843137, 0.772549, 1, 1) layout_mode = 2 @@ -636,6 +698,7 @@ theme_override_font_sizes/font_size = 40 text = "宠物背包" [node name="PetStoreButton" type="Button" parent="UI/GUI/OtherVBox"] +visible = false modulate = Color(0.992157, 0.482353, 0.870588, 1) layout_mode = 2 theme_override_font_sizes/font_size = 40 @@ -650,22 +713,6 @@ text = "稻草人" [node name="BigPanel" type="CanvasLayer" parent="UI"] -[node name="LuckyDrawPanel" parent="UI/BigPanel" instance=ExtResource("17_f21le")] -visible = false -offset_left = 425.0 -offset_top = 2.0 -offset_right = 1025.0 -offset_bottom = 722.0 -theme_override_styles/panel = SubResource("StyleBoxFlat_5dq3w") - -[node name="DailyCheckInPanel" parent="UI/BigPanel" instance=ExtResource("18_m6fch")] -visible = false -offset_left = 442.0 -offset_top = 3.0 -offset_right = 1042.0 -offset_bottom = 723.0 -theme_override_styles/panel = SubResource("StyleBoxFlat_nf3jg") - [node name="TCPNetworkManagerPanel" parent="UI/BigPanel" instance=ExtResource("7_401ut")] visible = false offset_top = 97.0 @@ -688,15 +735,6 @@ offset_bottom = 797.0 [node name="CropWarehousePanel" parent="UI/BigPanel" instance=ExtResource("18_5b81d")] -[node name="LoginPanel" parent="UI/BigPanel" instance=ExtResource("11_6jgly")] -visible = false -top_level = true -offset_left = 342.0 -offset_top = 40.0 -offset_right = 1092.0 -offset_bottom = 700.0 -theme_override_styles/panel = SubResource("StyleBoxFlat_4ka7t") - [node name="PlayerBagPanel" parent="UI/BigPanel" instance=ExtResource("19_8kysg")] visible = false @@ -706,6 +744,88 @@ visible = false [node name="ItemBagPanel" parent="UI/BigPanel" instance=ExtResource("20_n03md")] visible = false +[node name="PlayerStorePanel" type="Panel" parent="UI/BigPanel"] +visible = false +offset_left = 69.0 +offset_top = 56.0 +offset_right = 1635.0 +offset_bottom = 836.0 +scale = Vector2(0.8, 0.8) +size_flags_horizontal = 3 +size_flags_vertical = 3 +theme_override_styles/panel = SubResource("StyleBoxFlat_n03md") +script = ExtResource("24_dygid") + +[node name="TMBackGround" type="ColorRect" parent="UI/BigPanel/PlayerStorePanel"] +layout_mode = 0 +offset_left = -90.0 +offset_top = -71.0 +offset_right = 1672.0 +offset_bottom = 831.0 +color = Color(1, 1, 1, 0) + +[node name="ScrollContainer" type="ScrollContainer" parent="UI/BigPanel/PlayerStorePanel"] +layout_mode = 2 +offset_left = 28.0 +offset_top = 95.0 +offset_right = 3805.0 +offset_bottom = 1723.0 +scale = Vector2(0.4, 0.4) +size_flags_vertical = 3 +horizontal_scroll_mode = 0 + +[node name="Store_Grid" type="GridContainer" parent="UI/BigPanel/PlayerStorePanel/ScrollContainer"] +layout_mode = 2 +size_flags_horizontal = 6 +size_flags_vertical = 3 +columns = 8 + +[node name="Title" type="Label" parent="UI/BigPanel/PlayerStorePanel"] +layout_mode = 2 +offset_right = 1566.0 +offset_bottom = 69.0 +size_flags_horizontal = 3 +theme_override_colors/font_color = Color(1, 1, 0.807843, 1) +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 4 +theme_override_constants/shadow_offset_y = 4 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 50 +text = "玩家小卖部" +horizontal_alignment = 1 + +[node name="QuitButton" type="Button" parent="UI/BigPanel/PlayerStorePanel"] +custom_minimum_size = Vector2(60, 60) +layout_mode = 2 +offset_left = 1478.75 +offset_top = 20.0 +offset_right = 1538.75 +offset_bottom = 83.0 +theme_override_font_sizes/font_size = 40 +text = "X" + +[node name="RefreshButton" type="Button" parent="UI/BigPanel/PlayerStorePanel"] +custom_minimum_size = Vector2(60, 60) +layout_mode = 2 +offset_left = 27.5 +offset_top = 16.25 +offset_right = 115.5 +offset_bottom = 79.25 +theme_override_font_sizes/font_size = 40 +text = "刷新" + +[node name="BuyProductBoothButton" type="Button" parent="UI/BigPanel/PlayerStorePanel"] +custom_minimum_size = Vector2(60, 60) +layout_mode = 2 +offset_left = 207.5 +offset_top = 17.5 +offset_right = 535.5 +offset_bottom = 80.5 +theme_override_font_sizes/font_size = 40 +text = "购买商品摊位" + [node name="PetStorePanel" parent="UI/BigPanel" instance=ExtResource("24_uc6q1")] visible = false @@ -718,7 +838,7 @@ offset_bottom = 836.0 scale = Vector2(0.8, 0.8) size_flags_horizontal = 3 size_flags_vertical = 3 -theme_override_styles/panel = SubResource("StyleBoxFlat_n03md") +theme_override_styles/panel = SubResource("StyleBoxFlat_8d602") script = ExtResource("23_uc6q1") [node name="TMBackGround" type="ColorRect" parent="UI/BigPanel/PetBagPanel"] @@ -784,6 +904,14 @@ text = "刷新" [node name="PetFightPanel" parent="UI/BigPanel" instance=ExtResource("23_n03md")] visible = false +[node name="DailyCheckInPanel" parent="UI/BigPanel" instance=ExtResource("18_m6fch")] +visible = false + +[node name="LuckyDrawPanel" parent="UI/BigPanel" instance=ExtResource("17_f21le")] +visible = false + +[node name="LoginPanel" parent="UI/BigPanel" instance=ExtResource("11_6jgly")] + [node name="SmallPanel" type="CanvasLayer" parent="UI"] [node name="LoadProgressPanel" parent="UI/SmallPanel" instance=ExtResource("27_vygm6")] @@ -1450,6 +1578,101 @@ offset_top = 166.0 offset_right = 979.0 offset_bottom = 482.0 +[node name="CropInformPanel" type="Panel" parent="UI/SmallPanel"] +visible = false +offset_left = 330.0 +offset_right = 958.0 +offset_bottom = 723.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_xyeuq") +script = ExtResource("41_iluto") + +[node name="Title" type="Label" parent="UI/SmallPanel/CropInformPanel"] +layout_mode = 0 +offset_top = 17.0 +offset_right = 628.0 +offset_bottom = 74.0 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 4 +theme_override_constants/shadow_offset_y = 4 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 +theme_override_font_sizes/font_size = 35 +text = "作物信息" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="QuitButton" type="Button" parent="UI/SmallPanel/CropInformPanel"] +custom_minimum_size = Vector2(50, 50) +layout_mode = 0 +offset_left = 561.0 +offset_top = 17.0 +offset_right = 611.0 +offset_bottom = 67.0 +theme_override_font_sizes/font_size = 30 +text = "X" + +[node name="VBox" type="VBoxContainer" parent="UI/SmallPanel/CropInformPanel"] +layout_mode = 0 +offset_left = 17.0 +offset_top = 74.0 +offset_right = 615.0 +offset_bottom = 708.0 + +[node name="CropImage" type="TextureRect" parent="UI/SmallPanel/CropInformPanel/VBox"] +layout_mode = 2 +texture = ExtResource("31_uc6q1") +stretch_mode = 3 + +[node name="CropName" type="Label" parent="UI/SmallPanel/CropInformPanel/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "名称:" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="CropDescription" type="Label" parent="UI/SmallPanel/CropInformPanel/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 25 +text = "描述:" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="CropPrice" type="Label" parent="UI/SmallPanel/CropInformPanel/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 25 +text = "收购价:" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="CropQuality" type="Label" parent="UI/SmallPanel/CropInformPanel/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 25 +text = "品质:" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="HBox" type="HBoxContainer" parent="UI/SmallPanel/CropInformPanel/VBox"] +layout_mode = 2 +size_flags_vertical = 10 +alignment = 1 + +[node name="SaleProduct" type="Button" parent="UI/SmallPanel/CropInformPanel/VBox/HBox"] +custom_minimum_size = Vector2(120, 70) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 10 +theme_override_font_sizes/font_size = 30 +text = "直接出售" + +[node name="AddToStore" type="Button" parent="UI/SmallPanel/CropInformPanel/VBox/HBox"] +custom_minimum_size = Vector2(120, 70) +layout_mode = 2 +size_flags_horizontal = 3 +size_flags_vertical = 10 +theme_override_font_sizes/font_size = 30 +text = "放入小卖部" + [node name="DiaLog" type="CanvasLayer" parent="UI"] [node name="AcceptDialog" parent="UI/DiaLog" instance=ExtResource("16_0igvr")] @@ -1461,6 +1684,7 @@ offset_left = 426.0 offset_top = 90.0 offset_right = 928.0 offset_bottom = 556.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_t4s8j") script = ExtResource("29_5b81d") [node name="VBox" type="VBoxContainer" parent="UI/DiaLog/BatchBuyPopup"] @@ -1499,6 +1723,145 @@ size_flags_horizontal = 3 theme_override_font_sizes/font_size = 30 text = "取消" +[node name="BatchSellPopup" type="PanelContainer" parent="UI/DiaLog"] +visible = false +offset_left = 391.0 +offset_top = 95.0 +offset_right = 893.0 +offset_bottom = 561.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_t4s8j") +script = ExtResource("44_av1bx") + +[node name="VBox" type="VBoxContainer" parent="UI/DiaLog/BatchSellPopup"] +layout_mode = 2 + +[node name="Title" type="Label" parent="UI/DiaLog/BatchSellPopup/VBox"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 +theme_override_font_sizes/font_size = 40 +text = "请选择出售数量" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Contents" type="Label" parent="UI/DiaLog/BatchSellPopup/VBox"] +layout_mode = 2 +size_flags_vertical = 3 +theme_override_font_sizes/font_size = 20 +text = "这是说明内容" +horizontal_alignment = 1 + +[node name="SellNumEdit" type="LineEdit" parent="UI/DiaLog/BatchSellPopup/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 + +[node name="HBox" type="HBoxContainer" parent="UI/DiaLog/BatchSellPopup/VBox"] +layout_mode = 2 + +[node name="SureButton" type="Button" parent="UI/DiaLog/BatchSellPopup/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +text = "确定" + +[node name="CancelButton" type="Button" parent="UI/DiaLog/BatchSellPopup/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +text = "取消" + +[node name="AddProductToStorePopup" type="PanelContainer" parent="UI/DiaLog"] +visible = false +offset_left = 426.0 +offset_top = 59.0 +offset_right = 1026.0 +offset_bottom = 559.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_t4s8j") +script = ExtResource("46_8d602") + +[node name="VBox" type="VBoxContainer" parent="UI/DiaLog/AddProductToStorePopup"] +layout_mode = 2 + +[node name="Title" type="Label" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 +theme_override_font_sizes/font_size = 40 +text = "弹窗标题" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="Contents" type="Label" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 +theme_override_colors/font_color = Color(0.762404, 0.762404, 0.762404, 1) +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 +theme_override_font_sizes/font_size = 24 +text = "弹窗内容" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="SellNum" type="Label" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 +theme_override_font_sizes/font_size = 24 +text = "请选择售卖的数量" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="SellNumInput" type="LineEdit" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 + +[node name="SellPrice" type="Label" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 3 +theme_override_constants/shadow_offset_y = 3 +theme_override_constants/outline_size = 20 +theme_override_constants/shadow_outline_size = 20 +theme_override_font_sizes/font_size = 24 +text = "请选择售卖的价格" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="SellPriceInput" type="LineEdit" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 + +[node name="HBox" type="HBoxContainer" parent="UI/DiaLog/AddProductToStorePopup/VBox"] +layout_mode = 2 + +[node name="SureButton" type="Button" parent="UI/DiaLog/AddProductToStorePopup/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +text = "确定" + +[node name="CancelButton" type="Button" parent="UI/DiaLog/AddProductToStorePopup/VBox/HBox"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +text = "取消" + [node name="BackgroundUI" type="CanvasLayer" parent="."] layer = -1 @@ -2991,7 +3354,7 @@ autowrap_mode = 2 horizontal_alignment = 1 vertical_alignment = 1 -[node name="Decoration1" type="Button" parent="Decoration"] +[node name="PlayerStore" type="Button" parent="Decoration"] self_modulate = Color(1, 1, 1, 0) custom_minimum_size = Vector2(100, 100) offset_left = 122.0 @@ -3000,18 +3363,17 @@ offset_right = 386.0 offset_bottom = 156.0 scale = Vector2(0.4, 0.4) -[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/Decoration1"] +[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/PlayerStore"] position = Vector2(132, 134) scale = Vector2(1.4, 1.4) texture = ExtResource("48_2i8fe") -[node name="Image" type="Sprite2D" parent="Decoration/Decoration1"] -position = Vector2(132, 48) -scale = Vector2(1.2, 1.2) -texture = ExtResource("49_xjiif") +[node name="Image" type="Sprite2D" parent="Decoration/PlayerStore"] +position = Vector2(140, 57.5) +scale = Vector2(1.5, 1.5) +texture = ExtResource("54_06tja") -[node name="Name" type="RichTextLabel" parent="Decoration/Decoration1"] -visible = false +[node name="Name" type="RichTextLabel" parent="Decoration/PlayerStore"] layout_mode = 0 offset_left = -65.0 offset_top = -145.0 @@ -3023,11 +3385,11 @@ theme_override_font_sizes/mono_font_size = 40 theme_override_font_sizes/normal_font_size = 40 theme_override_font_sizes/bold_font_size = 40 bbcode_enabled = true -text = "稻草人" +text = "小卖部" horizontal_alignment = 1 vertical_alignment = 1 -[node name="Decoration2" type="Button" parent="Decoration"] +[node name="SeedStore" type="Button" parent="Decoration"] self_modulate = Color(1, 1, 1, 0) custom_minimum_size = Vector2(100, 100) offset_left = 244.0 @@ -3036,18 +3398,17 @@ offset_right = 508.0 offset_bottom = 156.0 scale = Vector2(0.4, 0.4) -[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/Decoration2"] +[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/SeedStore"] position = Vector2(132, 134) scale = Vector2(1.4, 1.4) texture = ExtResource("48_2i8fe") -[node name="Image" type="Sprite2D" parent="Decoration/Decoration2"] +[node name="Image" type="Sprite2D" parent="Decoration/SeedStore"] position = Vector2(132, 48) -scale = Vector2(1.2, 1.2) -texture = ExtResource("50_sqnmr") +scale = Vector2(1.5, 1.5) +texture = ExtResource("55_g4i0f") -[node name="Name" type="RichTextLabel" parent="Decoration/Decoration2"] -visible = false +[node name="Name" type="RichTextLabel" parent="Decoration/SeedStore"] layout_mode = 0 offset_left = -65.0 offset_top = -145.0 @@ -3059,11 +3420,11 @@ theme_override_font_sizes/mono_font_size = 40 theme_override_font_sizes/normal_font_size = 40 theme_override_font_sizes/bold_font_size = 40 bbcode_enabled = true -text = "稻草人" +text = "种子商店" horizontal_alignment = 1 vertical_alignment = 1 -[node name="Decoration3" type="Button" parent="Decoration"] +[node name="ItemStore" type="Button" parent="Decoration"] self_modulate = Color(1, 1, 1, 0) custom_minimum_size = Vector2(100, 100) offset_left = 366.0 @@ -3072,18 +3433,17 @@ offset_right = 630.0 offset_bottom = 156.0 scale = Vector2(0.4, 0.4) -[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/Decoration3"] +[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/ItemStore"] position = Vector2(132, 134) scale = Vector2(1.4, 1.4) texture = ExtResource("48_2i8fe") -[node name="Image" type="Sprite2D" parent="Decoration/Decoration3"] +[node name="Image" type="Sprite2D" parent="Decoration/ItemStore"] position = Vector2(132, 48) -scale = Vector2(1.2, 1.2) -texture = ExtResource("51_2i8fe") +scale = Vector2(1.5, 1.5) +texture = ExtResource("56_rlmnt") -[node name="Name" type="RichTextLabel" parent="Decoration/Decoration3"] -visible = false +[node name="Name" type="RichTextLabel" parent="Decoration/ItemStore"] layout_mode = 0 offset_left = -65.0 offset_top = -145.0 @@ -3095,11 +3455,11 @@ theme_override_font_sizes/mono_font_size = 40 theme_override_font_sizes/normal_font_size = 40 theme_override_font_sizes/bold_font_size = 40 bbcode_enabled = true -text = "稻草人" +text = "道具商店" horizontal_alignment = 1 vertical_alignment = 1 -[node name="Decoration4" type="Button" parent="Decoration"] +[node name="PetStore" type="Button" parent="Decoration"] self_modulate = Color(1, 1, 1, 0) custom_minimum_size = Vector2(100, 100) offset_left = 488.0 @@ -3108,18 +3468,17 @@ offset_right = 752.0 offset_bottom = 156.0 scale = Vector2(0.4, 0.4) -[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/Decoration4"] +[node name="GrassGroundImage" type="Sprite2D" parent="Decoration/PetStore"] position = Vector2(132, 134) scale = Vector2(1.4, 1.4) texture = ExtResource("48_2i8fe") -[node name="Image" type="Sprite2D" parent="Decoration/Decoration4"] +[node name="Image" type="Sprite2D" parent="Decoration/PetStore"] position = Vector2(132, 48) -scale = Vector2(1.2, 1.2) -texture = ExtResource("50_sqnmr") +scale = Vector2(1.5, 1.5) +texture = ExtResource("57_rlmnt") -[node name="Name" type="RichTextLabel" parent="Decoration/Decoration4"] -visible = false +[node name="Name" type="RichTextLabel" parent="Decoration/PetStore"] layout_mode = 0 offset_left = -65.0 offset_top = -145.0 @@ -3131,7 +3490,7 @@ theme_override_font_sizes/mono_font_size = 40 theme_override_font_sizes/normal_font_size = 40 theme_override_font_sizes/bold_font_size = 40 bbcode_enabled = true -text = "稻草人" +text = "宠物商店" horizontal_alignment = 1 vertical_alignment = 1 @@ -4344,9 +4703,11 @@ horizontal_alignment = 1 vertical_alignment = 1 [node name="WeatherSystem" type="Node2D" parent="."] +script = ExtResource("62_uyv6e") [node name="CherryBlossomRain" type="Node2D" parent="WeatherSystem"] visible = false +z_index = 10 position = Vector2(934, -469) [node name="CherryBlossomRain1" type="GPUParticles2D" parent="WeatherSystem/CherryBlossomRain"] @@ -4383,6 +4744,8 @@ visibility_rect = Rect2(-900, 0, 2300, 2000) process_material = SubResource("ParticleProcessMaterial_tdq2s") [node name="GardeniaRain" type="Node2D" parent="WeatherSystem"] +visible = false +z_index = 10 position = Vector2(759, -370) [node name="WillowLeafRain1" type="GPUParticles2D" parent="WeatherSystem/GardeniaRain"] @@ -4417,13 +4780,20 @@ process_material = SubResource("ParticleProcessMaterial_dygjy") [node name="WillowLeafRain" type="Node2D" parent="WeatherSystem"] visible = false +z_index = 10 position = Vector2(882, -469) -[node name="WillowLeafRain1" parent="WeatherSystem/WillowLeafRain" instance=ExtResource("53_nf3jg")] -visible = false +[node name="WillowLeafRain1" type="GPUParticles2D" parent="WeatherSystem/WillowLeafRain"] +self_modulate = Color(0.7, 0.7, 0.7, 1) +z_index = 10 +amount = 50 +texture = ExtResource("69_uyv6e") +lifetime = 20.0 +preprocess = 10.0 +visibility_rect = Rect2(-900, 0, 2300, 2000) +process_material = SubResource("ParticleProcessMaterial_6fhdl") [node name="WillowLeafRain2" type="GPUParticles2D" parent="WeatherSystem/WillowLeafRain"] -visible = false self_modulate = Color(0.7, 0.7, 0.7, 1) z_index = 10 amount = 50 @@ -4457,6 +4827,7 @@ process_material = SubResource("ParticleProcessMaterial_jiccn") [node name="Snow" type="GPUParticles2D" parent="WeatherSystem"] visible = false +z_index = 10 position = Vector2(16, -520) amount = 300 texture = ExtResource("53_4ka7t") @@ -4478,6 +4849,7 @@ process_material = SubResource("ParticleProcessMaterial_nf3jg") [connection signal="pressed" from="UI/GUI/FarmVBox/AddNewGroundButton" to="." method="_on_add_new_ground_button_pressed"] [connection signal="pressed" from="UI/GUI/VisitVBox/LikeButton" to="." method="_on_like_button_pressed"] [connection signal="pressed" from="UI/GUI/VisitVBox/ReturnMyFarmButton" to="." method="_on_return_my_farm_button_pressed"] +[connection signal="pressed" from="UI/GUI/OtherVBox/MyStoreButton" to="." method="_on_my_store_button_pressed"] [connection signal="pressed" from="UI/GUI/OtherVBox/AccountSettingButton" to="." method="_on_account_setting_button_pressed"] [connection signal="pressed" from="UI/GUI/OtherVBox/OnlineGiftButton" to="." method="_on_online_gift_button_pressed"] [connection signal="pressed" from="UI/GUI/OtherVBox/NewPlayerGiftButton" to="." method="_on_new_player_gift_button_pressed"] @@ -4494,6 +4866,10 @@ process_material = SubResource("ParticleProcessMaterial_nf3jg") [connection signal="pressed" from="UI/GUI/OtherVBox/ScareCrowButton" to="." method="_on_my_pet_button_pressed"] [connection signal="pressed" from="UI/SmallPanel/DebugPanel/QuitButton" to="UI/SmallPanel/DebugPanel" method="_on_quit_button_pressed"] [connection signal="pressed" from="Decoration/ScareCrow" to="." method="_on_scare_crow_pressed"] +[connection signal="pressed" from="Decoration/PlayerStore" to="." method="_on_player_store_pressed"] +[connection signal="pressed" from="Decoration/SeedStore" to="." method="_on_seed_store_pressed"] +[connection signal="pressed" from="Decoration/ItemStore" to="." method="_on_item_store_pressed"] +[connection signal="pressed" from="Decoration/PetStore" to="." method="_on_pet_store_pressed"] [connection signal="pressed" from="Decoration/WisdomTree" to="." method="_on_wisdom_tree_pressed"] [connection signal="pressed" from="Decoration4/ScareCrow" to="." method="_on_scare_crow_pressed"] [connection signal="pressed" from="Decoration4/WisdomTree" to="." method="_on_wisdom_tree_pressed"] diff --git a/Network/TCPNetworkManager.gd b/Network/TCPNetworkManager.gd index 6112569..9a997a1 100644 --- a/Network/TCPNetworkManager.gd +++ b/Network/TCPNetworkManager.gd @@ -239,6 +239,18 @@ func _on_data_received(data): login_panel._on_verification_code_response(success, message) return + if message_type == "forget_password_verification_code_response": + var success = data.get("success", false) + var message = data.get("message", "") + login_panel._on_verification_code_response(success, message) + return + + if message_type == "reset_password_response": + var success = data.get("success", false) + var message = data.get("message", "") + login_panel._on_forget_password_response_received(success, message) + return + if message_type == "verify_code_response": var success = data.get("success", false) var message = data.get("message", "") @@ -428,6 +440,88 @@ func _on_data_received(data): else: Toast.show(message, Color.RED) + # 出售作物响应 + elif action_type == "sell_crop": + if success: + main_game.money = updated_data["money"] + main_game.crop_warehouse = updated_data["作物仓库"] + main_game.experience = updated_data.get("experience", main_game.experience) + main_game.level = updated_data.get("level", main_game.level) + main_game._update_ui() + main_game.crop_warehouse_panel.update_crop_warehouse_ui() + Toast.show(message, Color.GREEN) + else: + Toast.show(message, Color.RED) + + # 添加商品到小卖部响应 + elif action_type == "add_product_to_store": + if success: + main_game.login_data["玩家小卖部"] = updated_data["玩家小卖部"] + main_game.crop_warehouse = updated_data["作物仓库"] + + # 更新UI + main_game.crop_warehouse_panel.update_crop_warehouse_ui() + var player_store_panel = get_node_or_null("/root/main/UI/BigPanel/PlayerStorePanel") + if player_store_panel and player_store_panel.has_method("update_player_store_ui"): + player_store_panel.update_player_store_ui() + + Toast.show(message, Color.GREEN) + else: + Toast.show(message, Color.RED) + + # 下架小卖部商品响应 + elif action_type == "remove_store_product": + if success: + main_game.login_data["玩家小卖部"] = updated_data["玩家小卖部"] + main_game.crop_warehouse = updated_data["作物仓库"] + + # 更新UI + main_game.crop_warehouse_panel.update_crop_warehouse_ui() + var player_store_panel = get_node_or_null("/root/main/UI/BigPanel/PlayerStorePanel") + if player_store_panel and player_store_panel.has_method("update_player_store_ui"): + player_store_panel.update_player_store_ui() + + Toast.show(message, Color.GREEN) + else: + Toast.show(message, Color.RED) + + # 购买小卖部商品响应 + elif action_type == "buy_store_product": + if success: + main_game.money = updated_data["money"] + main_game.crop_warehouse = updated_data["作物仓库"] + + # 更新UI + main_game._update_ui() + main_game.crop_warehouse_panel.update_crop_warehouse_ui() + + # 如果在访问模式下,刷新被访问玩家的小卖部(通过重新请求访问) + if main_game.is_visiting_mode: + var visited_username = main_game.visited_player_data.get("username", "") + if visited_username != "": + # 重新访问玩家以刷新数据 + sendVisitPlayer(visited_username) + + Toast.show(message, Color.GREEN) + else: + Toast.show(message, Color.RED) + + # 购买小卖部格子响应 + elif action_type == "buy_store_booth": + if success: + main_game.money = updated_data["money"] + main_game.login_data["小卖部格子数"] = updated_data["小卖部格子数"] + + # 更新UI + main_game._update_ui() + var player_store_panel = get_node_or_null("/root/main/UI/BigPanel/PlayerStorePanel") + if player_store_panel and player_store_panel.has_method("update_player_store_ui"): + player_store_panel.update_player_store_ui() + + Toast.show(message, Color.GREEN) + else: + Toast.show(message, Color.RED) + return # 游戏功能响应消息 @@ -502,6 +596,12 @@ func _on_data_received(data): elif message_type == "wisdom_tree_config_response": main_game._handle_wisdom_tree_config_response(data) + + # 天气变更消息 + elif message_type == "weather_change": + var weather_type = data.get("weather_type", "clear") + var weather_name = data.get("weather_name", "晴天") + main_game._handle_weather_change(weather_type, weather_name) # ============================= 客户端与服务端通信核心 ===================================== @@ -771,6 +871,32 @@ func sendVerificationCodeRequest(qq_number): }) return true +#发送忘记密码验证码请求 +func sendForgetPasswordVerificationCode(qq_number): + if not client.is_client_connected(): + return false + + client.send_data({ + "type": "request_forget_password_verification_code", + "qq_number": qq_number, + "timestamp": Time.get_unix_time_from_system() + }) + return true + +#发送忘记密码请求 +func sendForgetPasswordRequest(username, new_password, verification_code): + if not client.is_client_connected(): + return false + + client.send_data({ + "type": "reset_password", + "username": username, + "new_password": new_password, + "verification_code": verification_code, + "timestamp": Time.get_unix_time_from_system() + }) + return true + #发送验证码验证 func sendVerifyCode(qq_number, code): if not client.is_client_connected(): diff --git a/Scene/BigPanel/DailyCheckInPanel.tscn b/Scene/BigPanel/DailyCheckInPanel.tscn index 9695be2..47ef872 100644 --- a/Scene/BigPanel/DailyCheckInPanel.tscn +++ b/Scene/BigPanel/DailyCheckInPanel.tscn @@ -1,17 +1,26 @@ -[gd_scene load_steps=2 format=3 uid="uid://smypui0vyso5"] +[gd_scene load_steps=3 format=3 uid="uid://smypui0vyso5"] [ext_resource type="Script" uid="uid://c0jfbtkh0mj5b" path="res://Script/BigPanel/DailyCheckInPanel.gd" id="1_fj7a7"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_217t6"] +border_width_left = 15 +border_width_top = 15 +border_width_right = 15 +border_width_bottom = 15 +corner_detail = 20 + [node name="DailyCheckInPanel" type="Panel"] -offset_right = 600.0 +offset_left = 441.0 +offset_right = 1041.0 offset_bottom = 720.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_217t6") script = ExtResource("1_fj7a7") [node name="Title" type="Label" parent="."] layout_mode = 0 -offset_top = -1.0 +offset_top = 20.0 offset_right = 600.0 -offset_bottom = 41.0 +offset_bottom = 69.0 theme_override_colors/font_color = Color(0.624759, 0.8051, 0.828302, 1) theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) @@ -37,10 +46,10 @@ vertical_alignment = 1 [node name="QuitButton" type="Button" parent="."] custom_minimum_size = Vector2(57, 57) layout_mode = 0 -offset_left = 543.0 -offset_top = 1.0 -offset_right = 600.0 -offset_bottom = 58.0 +offset_left = 520.0 +offset_top = 22.0 +offset_right = 577.0 +offset_bottom = 79.0 theme_override_font_sizes/font_size = 35 text = "X" @@ -48,16 +57,16 @@ text = "X" modulate = Color(1, 1, 0.52549, 1) custom_minimum_size = Vector2(150, 70) layout_mode = 0 -offset_left = 243.0 -offset_top = 649.0 -offset_right = 393.0 -offset_bottom = 719.0 +offset_left = 239.0 +offset_top = 630.0 +offset_right = 389.0 +offset_bottom = 700.0 theme_override_font_sizes/font_size = 35 text = "签到" [node name="Scroll" type="ScrollContainer" parent="."] layout_mode = 0 -offset_top = 58.0 +offset_top = 77.0 offset_right = 600.0 offset_bottom = 419.0 diff --git a/Scene/BigPanel/LoginPanel.tscn b/Scene/BigPanel/LoginPanel.tscn index ebbc030..086f2ab 100644 --- a/Scene/BigPanel/LoginPanel.tscn +++ b/Scene/BigPanel/LoginPanel.tscn @@ -1,18 +1,26 @@ -[gd_scene load_steps=2 format=3 uid="uid://cbhitturvihqj"] +[gd_scene load_steps=3 format=3 uid="uid://cbhitturvihqj"] [ext_resource type="Script" uid="uid://cka0r4g8tbf0" path="res://Script/BigPanel/LoginPanel.gd" id="1_xnwaq"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_n8m38"] +border_width_left = 15 +border_width_top = 15 +border_width_right = 15 +border_width_bottom = 15 + [node name="LoginPanel" type="PanelContainer"] offset_left = 343.0 -offset_top = 36.0 -offset_right = 1071.0 -offset_bottom = 667.0 +offset_top = 5.0 +offset_right = 1093.0 +offset_bottom = 795.0 +scale = Vector2(0.9, 0.9) +theme_override_styles/panel = SubResource("StyleBoxFlat_n8m38") script = ExtResource("1_xnwaq") -[node name="VBox" type="VBoxContainer" parent="."] +[node name="LoginVBox" type="VBoxContainer" parent="."] layout_mode = 2 -[node name="Title" type="Label" parent="VBox"] +[node name="Title" type="Label" parent="LoginVBox"] modulate = Color(1, 1, 0.537255, 1) layout_mode = 2 theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) @@ -22,153 +30,59 @@ theme_override_constants/shadow_offset_y = 5 theme_override_constants/outline_size = 15 theme_override_constants/shadow_outline_size = 15 theme_override_font_sizes/font_size = 40 -text = "登录/注册面板" +text = "登录面板" horizontal_alignment = 1 vertical_alignment = 1 -[node name="UserName" type="HBoxContainer" parent="VBox"] +[node name="UserName" type="HBoxContainer" parent="LoginVBox"] layout_mode = 2 -[node name="Label" type="Label" parent="VBox/UserName"] +[node name="Label" type="Label" parent="LoginVBox/UserName"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "账号" horizontal_alignment = 1 vertical_alignment = 1 -[node name="username_input" type="LineEdit" parent="VBox/UserName"] +[node name="username_input" type="LineEdit" parent="LoginVBox/UserName"] layout_mode = 2 size_flags_horizontal = 3 theme_override_font_sizes/font_size = 30 placeholder_text = "请输入QQ号" metadata/_edit_use_anchors_ = true -[node name="Password1" type="HBoxContainer" parent="VBox"] +[node name="Password" type="HBoxContainer" parent="LoginVBox"] layout_mode = 2 -[node name="Label2" type="Label" parent="VBox/Password1"] +[node name="Label2" type="Label" parent="LoginVBox/Password"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "密码" horizontal_alignment = 1 vertical_alignment = 1 -[node name="password_input" type="LineEdit" parent="VBox/Password1"] +[node name="password_input" type="LineEdit" parent="LoginVBox/Password"] layout_mode = 2 size_flags_horizontal = 3 theme_override_font_sizes/font_size = 30 placeholder_text = "请输入密码" -[node name="Password2" type="HBoxContainer" parent="VBox"] +[node name="LoginButton" type="Button" parent="LoginVBox"] layout_mode = 2 - -[node name="Label2" type="Label" parent="VBox/Password2"] -layout_mode = 2 -theme_override_font_sizes/font_size = 30 -text = "确认密码" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="password_input2" type="LineEdit" parent="VBox/Password2"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_font_sizes/font_size = 30 -placeholder_text = "请再次输入密码" - -[node name="VerificationCode" type="HBoxContainer" parent="VBox"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBox/VerificationCode"] -layout_mode = 2 -theme_override_font_sizes/font_size = 30 -text = "验证码" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="verificationcode_input" type="LineEdit" parent="VBox/VerificationCode"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_font_sizes/font_size = 30 -placeholder_text = "请输入您的QQ邮箱收到的验证码" -metadata/_edit_use_anchors_ = true - -[node name="SendButton" type="Button" parent="VBox/VerificationCode"] -layout_mode = 2 -theme_override_font_sizes/font_size = 30 -text = "发送验证码" - -[node name="PlayerName" type="HBoxContainer" parent="VBox"] -layout_mode = 2 - -[node name="Label2" type="Label" parent="VBox/PlayerName"] -layout_mode = 2 -theme_override_font_sizes/font_size = 30 -text = "玩家昵称" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="playername_input" type="LineEdit" parent="VBox/PlayerName"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_font_sizes/font_size = 30 -placeholder_text = "请输入您的玩家昵称" - -[node name="FarmName" type="HBoxContainer" parent="VBox"] -layout_mode = 2 - -[node name="Label" type="Label" parent="VBox/FarmName"] -layout_mode = 2 -theme_override_font_sizes/font_size = 30 -text = "农场名称" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="farmname_input" type="LineEdit" parent="VBox/FarmName"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_font_sizes/font_size = 30 -placeholder_text = "请输入您的农场名称" -metadata/_edit_use_anchors_ = true - -[node name="LoginRegister" type="HBoxContainer" parent="VBox"] -layout_mode = 2 - -[node name="login_button" type="Button" parent="VBox/LoginRegister"] -layout_mode = 2 -size_flags_horizontal = 3 theme_override_font_sizes/font_size = 30 text = "登录" -[node name="register_button" type="Button" parent="VBox/LoginRegister"] +[node name="RegisterButton" type="Button" parent="LoginVBox"] layout_mode = 2 -size_flags_horizontal = 3 theme_override_font_sizes/font_size = 30 text = "注册" -[node name="Password3" type="HBoxContainer" parent="VBox"] -visible = false +[node name="ForgetPasswdButton" type="Button" parent="LoginVBox"] layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "忘记密码" -[node name="login_button" type="Button" parent="VBox/Password3"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_font_sizes/font_size = 20 -text = "发送验证码" - -[node name="Label2" type="Label" parent="VBox/Password3"] -layout_mode = 2 -theme_override_font_sizes/font_size = 20 -text = "找回密码" -horizontal_alignment = 1 -vertical_alignment = 1 - -[node name="password_input2" type="LineEdit" parent="VBox/Password3"] -layout_mode = 2 -size_flags_horizontal = 3 -theme_override_font_sizes/font_size = 20 -placeholder_text = "请输入QQ邮箱验证码" - -[node name="Note" type="Label" parent="VBox"] +[node name="Note" type="Label" parent="LoginVBox"] modulate = Color(1, 0.552941, 1, 1) layout_mode = 2 theme_override_font_sizes/font_size = 30 @@ -179,7 +93,246 @@ text = "注意:首次游玩游戏需要注册账号, horizontal_alignment = 1 vertical_alignment = 1 -[node name="status_label" type="Label" parent="VBox"] +[node name="status_label" type="Label" parent="LoginVBox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "连接状态" +horizontal_alignment = 1 + +[node name="RegisterVbox" type="VBoxContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="Title" type="Label" parent="RegisterVbox"] +modulate = Color(1, 1, 0.537255, 1) +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 5 +theme_override_constants/shadow_offset_y = 5 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 40 +text = "注册面板" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="RegisterUserName" type="HBoxContainer" parent="RegisterVbox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="RegisterVbox/RegisterUserName"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "账号" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="username_input" type="LineEdit" parent="RegisterVbox/RegisterUserName"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入QQ号" +metadata/_edit_use_anchors_ = true + +[node name="Password1" type="HBoxContainer" parent="RegisterVbox"] +layout_mode = 2 + +[node name="Label2" type="Label" parent="RegisterVbox/Password1"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "密码" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="password_input" type="LineEdit" parent="RegisterVbox/Password1"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入密码" + +[node name="Password2" type="HBoxContainer" parent="RegisterVbox"] +layout_mode = 2 + +[node name="Label2" type="Label" parent="RegisterVbox/Password2"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "确认密码" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="password_input2" type="LineEdit" parent="RegisterVbox/Password2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请再次输入密码" + +[node name="PlayerName" type="HBoxContainer" parent="RegisterVbox"] +layout_mode = 2 + +[node name="Label2" type="Label" parent="RegisterVbox/PlayerName"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "玩家昵称" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="playername_input" type="LineEdit" parent="RegisterVbox/PlayerName"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入您的玩家昵称" + +[node name="FarmName" type="HBoxContainer" parent="RegisterVbox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="RegisterVbox/FarmName"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "农场名称" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="farmname_input" type="LineEdit" parent="RegisterVbox/FarmName"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入您的农场名称" +metadata/_edit_use_anchors_ = true + +[node name="VerificationCode" type="HBoxContainer" parent="RegisterVbox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="RegisterVbox/VerificationCode"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "验证码" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="verificationcode_input" type="LineEdit" parent="RegisterVbox/VerificationCode"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入您的QQ邮箱收到的验证码" +metadata/_edit_use_anchors_ = true + +[node name="SendButton" type="Button" parent="RegisterVbox/VerificationCode"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "发送验证码" + +[node name="RegisterButton2" type="Button" parent="RegisterVbox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "注册" + +[node name="Note" type="Label" parent="RegisterVbox"] +modulate = Color(1, 0.552941, 1, 1) +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "注意:首次游玩游戏需要注册账号, +账号请直接输入您的QQ号,系统会直接向您的QQ +邮箱发送一串验证码进行注册验证,密码请设置的复杂一 +点,以免被暴力破解(" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="status_label2" type="Label" parent="RegisterVbox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "连接状态" +horizontal_alignment = 1 + +[node name="ForgetPasswordVbox" type="VBoxContainer" parent="."] +visible = false +layout_mode = 2 + +[node name="Title" type="Label" parent="ForgetPasswordVbox"] +modulate = Color(1, 1, 0.537255, 1) +layout_mode = 2 +theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) +theme_override_colors/font_outline_color = Color(0, 0, 0, 1) +theme_override_constants/shadow_offset_x = 5 +theme_override_constants/shadow_offset_y = 5 +theme_override_constants/outline_size = 15 +theme_override_constants/shadow_outline_size = 15 +theme_override_font_sizes/font_size = 40 +text = "忘记密码" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="ForgetPasswordUserName" type="HBoxContainer" parent="ForgetPasswordVbox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="ForgetPasswordVbox/ForgetPasswordUserName"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "账号" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="username_input" type="LineEdit" parent="ForgetPasswordVbox/ForgetPasswordUserName"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入QQ号" +metadata/_edit_use_anchors_ = true + +[node name="NewPassword" type="HBoxContainer" parent="ForgetPasswordVbox"] +layout_mode = 2 + +[node name="Label2" type="Label" parent="ForgetPasswordVbox/NewPassword"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "新密码" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="password_input" type="LineEdit" parent="ForgetPasswordVbox/NewPassword"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请设置新的密码" + +[node name="VerificationCode2" type="HBoxContainer" parent="ForgetPasswordVbox"] +layout_mode = 2 + +[node name="Label" type="Label" parent="ForgetPasswordVbox/VerificationCode2"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "验证码" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="verificationcode_input" type="LineEdit" parent="ForgetPasswordVbox/VerificationCode2"] +layout_mode = 2 +size_flags_horizontal = 3 +theme_override_font_sizes/font_size = 30 +placeholder_text = "请输入您的QQ邮箱收到的验证码" +metadata/_edit_use_anchors_ = true + +[node name="SendButton" type="Button" parent="ForgetPasswordVbox/VerificationCode2"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "发送验证码" + +[node name="ForgetPasswordButton" type="Button" parent="ForgetPasswordVbox"] +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "确认" + +[node name="Note" type="Label" parent="ForgetPasswordVbox"] +modulate = Color(1, 0.552941, 1, 1) +layout_mode = 2 +theme_override_font_sizes/font_size = 30 +text = "注意:首次游玩游戏需要注册账号, +账号请直接输入您的QQ号,系统会直接向您的QQ +邮箱发送一串验证码进行注册验证,密码请设置的复杂一 +点,以免被暴力破解(" +horizontal_alignment = 1 +vertical_alignment = 1 + +[node name="status_label3" type="Label" parent="ForgetPasswordVbox"] layout_mode = 2 theme_override_font_sizes/font_size = 30 text = "连接状态" diff --git a/Scene/BigPanel/LuckyDrawPanel.tscn b/Scene/BigPanel/LuckyDrawPanel.tscn index 774c876..094d20a 100644 --- a/Scene/BigPanel/LuckyDrawPanel.tscn +++ b/Scene/BigPanel/LuckyDrawPanel.tscn @@ -1,17 +1,27 @@ -[gd_scene load_steps=2 format=3 uid="uid://bndf1e4sgdjr6"] +[gd_scene load_steps=3 format=3 uid="uid://bndf1e4sgdjr6"] [ext_resource type="Script" uid="uid://65e0rl31fx0i" path="res://Script/BigPanel/LuckyDrawPanel.gd" id="1_dcmen"] +[sub_resource type="StyleBoxFlat" id="StyleBoxFlat_ynokl"] +border_width_left = 15 +border_width_top = 15 +border_width_right = 15 +border_width_bottom = 15 +corner_detail = 20 + [node name="LuckyDrawPanel" type="Panel"] -offset_right = 600.0 -offset_bottom = 720.0 +offset_left = 373.0 +offset_top = 1.0 +offset_right = 1045.0 +offset_bottom = 721.0 +theme_override_styles/panel = SubResource("StyleBoxFlat_ynokl") script = ExtResource("1_dcmen") [node name="Title" type="Label" parent="."] layout_mode = 0 -offset_top = -1.0 -offset_right = 600.0 -offset_bottom = 58.0 +offset_top = 19.0 +offset_right = 669.0 +offset_bottom = 78.0 theme_override_colors/font_color = Color(0.624759, 0.8051, 0.828302, 1) theme_override_colors/font_shadow_color = Color(0, 0, 0, 1) theme_override_colors/font_outline_color = Color(0, 0, 0, 1) @@ -28,7 +38,7 @@ vertical_alignment = 1 modulate = Color(0.642982, 0.510828, 1, 1) layout_mode = 0 offset_top = 419.0 -offset_right = 600.0 +offset_right = 671.0 offset_bottom = 468.0 theme_override_font_sizes/font_size = 30 text = "🎉获得奖励🎉" @@ -38,18 +48,19 @@ vertical_alignment = 1 [node name="QuitButton" type="Button" parent="."] custom_minimum_size = Vector2(57, 57) layout_mode = 0 -offset_left = 543.0 -offset_top = 1.0 -offset_right = 600.0 -offset_bottom = 58.0 +offset_left = 595.0 +offset_top = 21.0 +offset_right = 652.0 +offset_bottom = 78.0 theme_override_font_sizes/font_size = 35 text = "X" [node name="LuckyDrawReward" type="RichTextLabel" parent="."] layout_mode = 0 +offset_left = 16.0 offset_top = 481.0 -offset_right = 600.0 -offset_bottom = 649.0 +offset_right = 671.0 +offset_bottom = 633.0 theme_override_font_sizes/normal_font_size = 20 bbcode_enabled = true text = "+500 经验,+400 钱币,+5 普通-番茄种子,+1 传奇-火龙果种子 " @@ -57,14 +68,18 @@ horizontal_alignment = 1 [node name="Grid" type="GridContainer" parent="."] layout_mode = 0 -offset_top = 58.0 -offset_right = 600.0 +offset_left = 16.0 +offset_top = 85.0 +offset_right = 657.0 offset_bottom = 419.0 columns = 5 [node name="RewardItem" type="RichTextLabel" parent="Grid"] custom_minimum_size = Vector2(120, 120) layout_mode = 2 +theme_override_constants/outline_size = 15 +theme_override_font_sizes/normal_font_size = 17 +bbcode_enabled = true text = "+50钱币 +100经验 +4番茄种子 @@ -75,9 +90,10 @@ threaded = true [node name="HBox" type="HBoxContainer" parent="."] layout_mode = 0 -offset_top = 649.0 -offset_right = 600.0 -offset_bottom = 719.0 +offset_left = -2.0 +offset_top = 633.0 +offset_right = 671.0 +offset_bottom = 703.0 alignment = 1 [node name="FiveLuckyDrawButton" type="Button" parent="HBox"] diff --git a/Script/BigPanel/CropWarehousePanel.gd b/Script/BigPanel/CropWarehousePanel.gd index 2088c4a..a054bda 100644 --- a/Script/BigPanel/CropWarehousePanel.gd +++ b/Script/BigPanel/CropWarehousePanel.gd @@ -320,45 +320,51 @@ func _create_crop_button(crop_name: String, crop_quality: String) -> Button: # 正常模式下的成熟物点击处理 func _on_crop_selected(crop_name, crop_count): - # 显示成熟物信息 - var info_text = "" - - if main_game.can_planted_crop.has(crop_name): - var crop = main_game.can_planted_crop[crop_name] - var display_name = crop_name - var mature_name = crop.get("成熟物名称") - if mature_name != null and mature_name != "": - display_name = mature_name - else: - display_name = crop.get("作物名称", crop_name) - var quality = crop.get("品质", "未知") - var price = crop.get("花费", 0) - var grow_time = crop.get("生长时间", 0) - var profit = crop.get("收益", 0) - var level_req = crop.get("等级", 1) - - # 将成熟时间转换为可读格式 - var time_str = "" - var total_seconds = int(grow_time) - var hours = total_seconds / 3600 - var minutes = (total_seconds % 3600) / 60 - var seconds = total_seconds % 60 - - if hours > 0: - time_str += str(hours) + "小时" - if minutes > 0: - time_str += str(minutes) + "分钟" - if seconds > 0: - time_str += str(seconds) + "秒" - - info_text = quality + "-" + display_name + " (数量: " + str(crop_count) + ")\n" - info_text += "原价格: " + str(price) + "元, 原收益: " + str(profit) + "元\n" - info_text += "成熟时间: " + time_str + ", 需求等级: " + str(level_req) + "\n" - info_text += "这是收获的成熟品,可以用于出售或其他用途" + # 获取作物信息面板的引用 + var crop_inform_panel = get_node("/root/main/UI/SmallPanel/CropInformPanel") + if crop_inform_panel and crop_inform_panel.has_method("show_crop_info"): + # 打开作物信息面板并传递作物数据 + crop_inform_panel.show_crop_info(crop_name, crop_count) else: - info_text = crop_name + " (数量: " + str(crop_count) + ")" - - Toast.show(info_text, Color.GOLD, 3.0, 1.0) + # 如果作物信息面板不可用,显示Toast作为后备方案 + var info_text = "" + + if main_game.can_planted_crop.has(crop_name): + var crop = main_game.can_planted_crop[crop_name] + var display_name = crop_name + var mature_name = crop.get("成熟物名称") + if mature_name != null and mature_name != "": + display_name = mature_name + else: + display_name = crop.get("作物名称", crop_name) + var quality = crop.get("品质", "未知") + var price = crop.get("花费", 0) + var grow_time = crop.get("生长时间", 0) + var profit = crop.get("收益", 0) + var level_req = crop.get("等级", 1) + + # 将成熟时间转换为可读格式 + var time_str = "" + var total_seconds = int(grow_time) + var hours = total_seconds / 3600 + var minutes = (total_seconds % 3600) / 60 + var seconds = total_seconds % 60 + + if hours > 0: + time_str += str(hours) + "小时" + if minutes > 0: + time_str += str(minutes) + "分钟" + if seconds > 0: + time_str += str(seconds) + "秒" + + info_text = quality + "-" + display_name + " (数量: " + str(crop_count) + ")\n" + info_text += "原价格: " + str(price) + "元, 原收益: " + str(profit) + "元\n" + info_text += "成熟时间: " + time_str + ", 需求等级: " + str(level_req) + "\n" + info_text += "这是收获的成熟品,可以用于出售或其他用途" + else: + info_text = crop_name + " (数量: " + str(crop_count) + ")" + + Toast.show(info_text, Color.GOLD, 3.0, 1.0) # 访问模式下的成熟物点击处理 func _on_visit_crop_selected(crop_name, crop_count): diff --git a/Script/BigPanel/LoginPanel.gd b/Script/BigPanel/LoginPanel.gd index fe83a7d..64f5672 100644 --- a/Script/BigPanel/LoginPanel.gd +++ b/Script/BigPanel/LoginPanel.gd @@ -1,35 +1,52 @@ #玩家登录注册面板 extends PanelContainer -#玩家登录账号,用QQ号代替 -@onready var username_input : LineEdit = $VBox/UserName/username_input -#用户登录密码 -@onready var password_input : LineEdit = $VBox/Password1/password_input -#登录按钮 -@onready var login_button : Button = $VBox/LoginRegister/login_button +#默认显示登录面板(登录面板可以进入注册面板和忘记密码面板) +@onready var login_v_box: VBoxContainer = $LoginVBox #显示或隐藏登录面板 +@onready var user_name: HBoxContainer = $LoginVBox/UserName #玩家账号 +@onready var password: HBoxContainer = $LoginVBox/Password #玩家密码 +@onready var login_button: Button = $LoginVBox/LoginButton #登录按钮 +@onready var register_button: Button = $LoginVBox/RegisterButton #注册按钮,点击后隐藏登录面板显示注册面板 +@onready var forget_passwd_button: Button = $LoginVBox/ForgetPasswdButton #忘记密码按钮,点击后隐藏登录面板显示忘记密码面板 +@onready var status_label: Label = $LoginVBox/status_label #登录状态 -#下面是注册相关的 -#注册按钮 -@onready var register_button : Button = $VBox/LoginRegister/register_button -#注册账号时二次确认密码 -@onready var password_input_2 : LineEdit = $VBox/Password2/password_input2 -#农场名称 -@onready var farmname_input : LineEdit = $VBox/FarmName/farmname_input -#玩家昵称 -@onready var playername_input :LineEdit = $VBox/PlayerName/playername_input -#邮箱验证码 -@onready var verificationcode_input :LineEdit = $VBox/VerificationCode/verificationcode_input -#发送验证码按钮 -@onready var send_button :Button = $VBox/VerificationCode/SendButton -#状态提示标签 -@onready var status_label : Label = $VBox/status_label +# 登录面板输入框 +@onready var username_input: LineEdit = $LoginVBox/UserName/username_input +@onready var password_input: LineEdit = $LoginVBox/Password/password_input +#注册面板,注册成功跳转回登录面板 +@onready var register_vbox: VBoxContainer = $RegisterVbox #显示或隐藏注册面板 +@onready var register_user_name: HBoxContainer = $RegisterVbox/RegisterUserName #注册玩家账号 +@onready var password_1: HBoxContainer = $RegisterVbox/Password1 #注册密码 +@onready var password_2: HBoxContainer = $RegisterVbox/Password2 #二次确认密码 +@onready var player_name: HBoxContainer = $RegisterVbox/PlayerName #注册玩家昵称 +@onready var farm_name: HBoxContainer = $RegisterVbox/FarmName #注册玩家农场名称 +@onready var verification_code: HBoxContainer = $RegisterVbox/VerificationCode #注册所需验证码 +@onready var register_button_2: Button = $RegisterVbox/RegisterButton2 #注册按钮 +@onready var status_label_2: Label = $RegisterVbox/status_label2 #注册状态 -@onready var password_2: HBoxContainer = $VBox/Password2 -@onready var verification_code: HBoxContainer = $VBox/VerificationCode -@onready var player_name: HBoxContainer = $VBox/PlayerName -@onready var farm_name: HBoxContainer = $VBox/FarmName +# 注册面板输入框 +@onready var register_username_input: LineEdit = $RegisterVbox/RegisterUserName/username_input +@onready var password_input_1: LineEdit = $RegisterVbox/Password1/password_input +@onready var password_input_2: LineEdit = $RegisterVbox/Password2/password_input2 +@onready var playername_input: LineEdit = $RegisterVbox/PlayerName/playername_input +@onready var farmname_input: LineEdit = $RegisterVbox/FarmName/farmname_input +@onready var verificationcode_input: LineEdit = $RegisterVbox/VerificationCode/verificationcode_input +@onready var send_button: Button = $RegisterVbox/VerificationCode/SendButton +#忘记密码面板,设置新密码成功后同样跳转到登录面板 +@onready var forget_password_vbox: VBoxContainer = $ForgetPasswordVbox #显示或隐藏忘记密码面板 +@onready var forget_password_user_name: HBoxContainer = $ForgetPasswordVbox/ForgetPasswordUserName #忘记密码的玩家账号 +@onready var new_password: HBoxContainer = $ForgetPasswordVbox/NewPassword #设置该账号新的密码 +@onready var verification_code_2: HBoxContainer = $ForgetPasswordVbox/VerificationCode2 #忘记密码所需验证码 +@onready var forget_password_button: Button = $ForgetPasswordVbox/ForgetPasswordButton #设置新密码确认按钮 +@onready var status_label_3: Label = $ForgetPasswordVbox/status_label3 #设置新密码状态 + +# 忘记密码面板输入框 +@onready var forget_username_input: LineEdit = $ForgetPasswordVbox/ForgetPasswordUserName/username_input +@onready var new_password_input: LineEdit = $ForgetPasswordVbox/NewPassword/password_input +@onready var forget_verificationcode_input: LineEdit = $ForgetPasswordVbox/VerificationCode2/verificationcode_input +@onready var forget_send_button: Button = $ForgetPasswordVbox/VerificationCode2/SendButton # 记住密码选项 @@ -53,16 +70,20 @@ var remember_password : bool = true # 默认记住密码 # 准备函数 func _ready(): self.show() - #隐藏注册相关UI - password_2.hide() - verification_code.hide() - player_name.hide() - farm_name.hide() + + # 初始状态:只显示登录面板,隐藏注册和忘记密码面板 + login_v_box.show() + register_vbox.hide() + forget_password_vbox.hide() # 连接按钮信号 login_button.pressed.connect(self._on_login_button_pressed) - register_button.pressed.connect(self._on_register_button_pressed) + register_button.pressed.connect(self._on_show_register_panel) + forget_passwd_button.pressed.connect(self._on_forget_password_button_pressed) + register_button_2.pressed.connect(self._on_register_button_2_pressed) + forget_password_button.pressed.connect(self._on_forget_password_confirm_pressed) send_button.pressed.connect(self._on_send_button_pressed) + forget_send_button.pressed.connect(self._on_forget_send_button_pressed) # 加载保存的登录信息 _load_login_info() @@ -70,6 +91,23 @@ func _ready(): # 显示客户端版本号 _display_version_info() +# 面板切换函数 +func _on_show_register_panel(): + """切换到注册面板""" + login_v_box.hide() + register_vbox.show() + forget_password_vbox.hide() + status_label_2.text = "请填写注册信息" + status_label_2.modulate = Color.WHITE + +func _on_forget_password_button_pressed(): + """切换到忘记密码面板""" + login_v_box.hide() + register_vbox.hide() + forget_password_vbox.show() + status_label_3.text = "请输入账号和新密码" + status_label_3.modulate = Color.WHITE + # 处理登录按钮点击 func _on_login_button_pressed(): password_2.hide() @@ -129,32 +167,30 @@ func _on_login_button_pressed(): # 处理验证码发送按钮点击 func _on_send_button_pressed(): - - - var user_name = username_input.text.strip_edges() + var user_name = register_username_input.text.strip_edges() if user_name == "": - status_label.text = "请输入QQ号以接收验证码!" - status_label.modulate = Color.RED + status_label_2.text = "请输入QQ号以接收验证码!" + status_label_2.modulate = Color.RED return if !is_valid_qq_number(user_name): - status_label.text = "请输入正确的QQ号码(5-12位数字)!" - status_label.modulate = Color.RED + status_label_2.text = "请输入正确的QQ号码(5-12位数字)!" + status_label_2.modulate = Color.RED return # 检查网络连接状态 if !tcp_network_manager_panel.client.is_client_connected(): - status_label.text = "未连接到服务器,正在尝试连接..." - status_label.modulate = Color.YELLOW + status_label_2.text = "未连接到服务器,正在尝试连接..." + status_label_2.modulate = Color.YELLOW # 尝试自动连接到服务器 tcp_network_manager_panel.connect_to_current_server() await get_tree().create_timer(2.0).timeout # 再次检查连接状态 if !tcp_network_manager_panel.client.is_client_connected(): - status_label.text = "连接服务器失败,正在尝试其他服务器..." - status_label.modulate = Color.YELLOW + status_label_2.text = "连接服务器失败,正在尝试其他服务器..." + status_label_2.modulate = Color.YELLOW # 等待自动服务器切换完成 await get_tree().create_timer(3.0).timeout @@ -162,8 +198,8 @@ func _on_send_button_pressed(): # 禁用按钮,防止重复点击 send_button.disabled = true - status_label.text = "正在发送验证码,请稍候..." - status_label.modulate = Color.YELLOW + status_label_2.text = "正在发送验证码,请稍候..." + status_label_2.modulate = Color.YELLOW # 发送验证码请求 tcp_network_manager_panel.sendVerificationCodeRequest(user_name) @@ -179,19 +215,14 @@ func _on_send_button_pressed(): send_button.disabled = false send_button.text = "发送验证码" - if status_label.text == "正在发送验证码,请稍候...": - status_label.text = "验证码发送超时,请重试!" - status_label.modulate = Color.RED + if status_label_2.text == "正在发送验证码,请稍候...": + status_label_2.text = "验证码发送超时,请重试!" + status_label_2.modulate = Color.RED # 处理注册按钮点击 -func _on_register_button_pressed(): - password_2.show() - verification_code.show() - player_name.show() - farm_name.show() - - var user_name = username_input.text.strip_edges() - var user_password = password_input.text.strip_edges() +func _on_register_button_2_pressed(): + var user_name = register_username_input.text.strip_edges() + var user_password = password_input_1.text.strip_edges() var user_password_2 = password_input_2.text.strip_edges() var farmname = farmname_input.text.strip_edges() var player_name = playername_input.text.strip_edges() @@ -199,69 +230,180 @@ func _on_register_button_pressed(): # 检查密码格式(只允许数字和字母) if not is_valid_password(user_password): - status_label.text = "密码只能包含数字和字母!" - status_label.modulate = Color.RED + status_label_2.text = "密码只能包含数字和字母!" + status_label_2.modulate = Color.RED return if user_name == "" or user_password == "": - status_label.text = "用户名或密码不能为空!" - status_label.modulate = Color.RED + status_label_2.text = "用户名或密码不能为空!" + status_label_2.modulate = Color.RED return if farmname == "": - status_label.text = "农场名称不能为空!" - status_label.modulate = Color.RED + status_label_2.text = "农场名称不能为空!" + status_label_2.modulate = Color.RED return if user_password != user_password_2: - status_label.text = "两次输入的密码不一致!" - status_label.modulate = Color.RED + status_label_2.text = "两次输入的密码不一致!" + status_label_2.modulate = Color.RED return if !is_valid_qq_number(user_name): - status_label.text = "请输入正确的QQ号码(5-12位数字)!" - status_label.modulate = Color.RED + status_label_2.text = "请输入正确的QQ号码(5-12位数字)!" + status_label_2.modulate = Color.RED return if verification_code == "": - status_label.text = "请输入验证码!" - status_label.modulate = Color.RED + status_label_2.text = "请输入验证码!" + status_label_2.modulate = Color.RED return - # 检查网络连接状态 + # 检查网络连接状态 if !tcp_network_manager_panel.client.is_client_connected(): - status_label.text = "未连接到服务器,正在尝试连接..." - status_label.modulate = Color.YELLOW + status_label_2.text = "未连接到服务器,正在尝试连接..." + status_label_2.modulate = Color.YELLOW # 尝试自动连接到服务器 tcp_network_manager_panel.connect_to_current_server() await get_tree().create_timer(2.0).timeout # 再次检查连接状态 if !tcp_network_manager_panel.client.is_client_connected(): - status_label.text = "连接服务器失败,正在尝试其他服务器..." - status_label.modulate = Color.YELLOW + status_label_2.text = "连接服务器失败,正在尝试其他服务器..." + status_label_2.modulate = Color.YELLOW # 等待自动服务器切换完成 await get_tree().create_timer(3.0).timeout # 禁用按钮,防止重复点击 - register_button.disabled = true + register_button_2.disabled = true - status_label.text = "正在注册,请稍候..." - status_label.modulate = Color.YELLOW + status_label_2.text = "正在注册,请稍候..." + status_label_2.modulate = Color.YELLOW - # 发送注册请求 + # 发送注册请求 tcp_network_manager_panel.sendRegisterInfo(user_name, user_password, farmname, player_name, verification_code) - + # 更新主游戏数据 main_game.user_name = user_name main_game.user_password = user_password - main_game.farmname = farmname + # farmname 直接在注册成功后通过UI更新,这里不需要设置 # 5秒后重新启用按钮(如果没有收到响应) await get_tree().create_timer(5.0).timeout - if register_button.disabled: - register_button.disabled = false - status_label.text = "注册超时,请重试!" - status_label.modulate = Color.RED + if register_button_2.disabled: + register_button_2.disabled = false + status_label_2.text = "注册超时,请重试!" + status_label_2.modulate = Color.RED + +# 忘记密码发送验证码按钮处理 +func _on_forget_send_button_pressed(): + var user_name = forget_username_input.text.strip_edges() + + if user_name == "": + status_label_3.text = "请输入QQ号以接收验证码!" + status_label_3.modulate = Color.RED + return + + if !is_valid_qq_number(user_name): + status_label_3.text = "请输入正确的QQ号码(5-12位数字)!" + status_label_3.modulate = Color.RED + return + + # 检查网络连接状态 + if !tcp_network_manager_panel.client.is_client_connected(): + status_label_3.text = "未连接到服务器,正在尝试连接..." + status_label_3.modulate = Color.YELLOW + # 尝试自动连接到服务器 + tcp_network_manager_panel.connect_to_current_server() + await get_tree().create_timer(2.0).timeout + + # 再次检查连接状态 + if !tcp_network_manager_panel.client.is_client_connected(): + status_label_3.text = "连接服务器失败,正在尝试其他服务器..." + status_label_3.modulate = Color.YELLOW + # 等待自动服务器切换完成 + await get_tree().create_timer(3.0).timeout + + # 禁用按钮,防止重复点击 + forget_send_button.disabled = true + + status_label_3.text = "正在发送验证码,请稍候..." + status_label_3.modulate = Color.YELLOW + + # 发送验证码请求(用于忘记密码) + tcp_network_manager_panel.sendForgetPasswordVerificationCode(user_name) + + # 60秒后重新启用按钮 + var timer = 60 + while timer > 0 and forget_send_button.disabled: + forget_send_button.text = "重新发送(%d)" % timer + await get_tree().create_timer(1.0).timeout + timer -= 1 + + if forget_send_button.disabled: + forget_send_button.disabled = false + forget_send_button.text = "发送验证码" + + if status_label_3.text == "正在发送验证码,请稍候...": + status_label_3.text = "验证码发送超时,请重试!" + status_label_3.modulate = Color.RED + +# 忘记密码确认按钮处理 +func _on_forget_password_confirm_pressed(): + var user_name = forget_username_input.text.strip_edges() + var new_password = new_password_input.text.strip_edges() + var verification_code = forget_verificationcode_input.text.strip_edges() + + # 检查密码格式(只允许数字和字母) + if not is_valid_password(new_password): + status_label_3.text = "密码只能包含数字和字母!" + status_label_3.modulate = Color.RED + return + + if user_name == "" or new_password == "": + status_label_3.text = "用户名或新密码不能为空!" + status_label_3.modulate = Color.RED + return + + if !is_valid_qq_number(user_name): + status_label_3.text = "请输入正确的QQ号码(5-12位数字)!" + status_label_3.modulate = Color.RED + return + + if verification_code == "": + status_label_3.text = "请输入验证码!" + status_label_3.modulate = Color.RED + return + + # 检查网络连接状态 + if !tcp_network_manager_panel.client.is_client_connected(): + status_label_3.text = "未连接到服务器,正在尝试连接..." + status_label_3.modulate = Color.YELLOW + # 尝试自动连接到服务器 + tcp_network_manager_panel.connect_to_current_server() + await get_tree().create_timer(2.0).timeout + + # 再次检查连接状态 + if !tcp_network_manager_panel.client.is_client_connected(): + status_label_3.text = "连接服务器失败,正在尝试其他服务器..." + status_label_3.modulate = Color.YELLOW + # 等待自动服务器切换完成 + await get_tree().create_timer(3.0).timeout + + # 禁用按钮,防止重复点击 + forget_password_button.disabled = true + + status_label_3.text = "正在重置密码,请稍候..." + status_label_3.modulate = Color.YELLOW + + # 发送忘记密码请求 + tcp_network_manager_panel.sendForgetPasswordRequest(user_name, new_password, verification_code) + + # 5秒后重新启用按钮(如果没有收到响应) + await get_tree().create_timer(5.0).timeout + if forget_password_button.disabled: + forget_password_button.disabled = false + status_label_3.text = "重置密码超时,请重试!" + status_label_3.modulate = Color.RED # 处理验证码发送响应 func _on_verification_code_response(success: bool, message: String): @@ -390,24 +532,67 @@ func _on_login_response_received(success: bool, message: String, user_data: Dict # 处理注册响应 func _on_register_response_received(success: bool, message: String): # 启用按钮 - register_button.disabled = false + register_button_2.disabled = false if success: - status_label.text = "注册成功!请登录游戏" - status_label.modulate = Color.GREEN + status_label_2.text = "注册成功!请登录游戏" + status_label_2.modulate = Color.GREEN # 注册成功后,如果启用了记住密码,保存登录信息 if remember_password: - var user_name = username_input.text.strip_edges() - var user_password = password_input.text.strip_edges() + var user_name = register_username_input.text.strip_edges() + var user_password = password_input_1.text.strip_edges() _save_login_info(user_name, user_password) # 清除注册相关的输入框 password_input_2.text = "" verificationcode_input.text = "" + + # 切换回登录面板 + register_vbox.hide() + forget_password_vbox.hide() + login_v_box.show() + + # 如果记住密码,自动填充登录信息 + if remember_password: + username_input.text = register_username_input.text + password_input.text = password_input_1.text else: - status_label.text = "注册失败:" + message - status_label.modulate = Color.RED + status_label_2.text = "注册失败:" + message + status_label_2.modulate = Color.RED + +# 处理忘记密码响应 +func _on_forget_password_response_received(success: bool, message: String): + # 启用按钮 + forget_password_button.disabled = false + + if success: + status_label_3.text = "密码重置成功!请使用新密码登录" + status_label_3.modulate = Color.GREEN + + # 保存新的登录信息 + if remember_password: + var user_name = forget_username_input.text.strip_edges() + var new_password = new_password_input.text.strip_edges() + _save_login_info(user_name, new_password) + + # 清除输入框 + forget_verificationcode_input.text = "" + + # 切换回登录面板并自动填充账号信息 + forget_password_vbox.hide() + register_vbox.hide() + login_v_box.show() + + # 自动填充登录信息 + username_input.text = forget_username_input.text + password_input.text = new_password_input.text + + status_label.text = "密码已重置,请登录" + status_label.modulate = Color.GREEN + else: + status_label_3.text = "密码重置失败:" + message + status_label_3.modulate = Color.RED # 保存登录信息到JSON文件 func _save_login_info(user_name: String, password: String): diff --git a/Script/BigPanel/PlayerStorePanel.gd b/Script/BigPanel/PlayerStorePanel.gd new file mode 100644 index 0000000..5a024db --- /dev/null +++ b/Script/BigPanel/PlayerStorePanel.gd @@ -0,0 +1,374 @@ +extends Panel +#玩家小卖部(目前可以卖道具,种子,成熟作物) +#初始玩家有10个格子 +#然后玩家额外购买格子需要1000元,多加一个格子加500元,最多40个格子,格子满了不能再放了 +#玩家自己点击自己的摊位(商品格子)显示弹窗是否要取消放置商品 +#别人拜访玩家打开小卖部点击被拜访玩家的摊位显示批量购买弹窗 +@onready var quit_button: Button = $QuitButton #关闭玩家小卖部面板 +@onready var refresh_button: Button = $RefreshButton #刷新小卖部按钮 +@onready var store_grid: GridContainer = $ScrollContainer/Store_Grid #小卖部商品格子 +@onready var buy_product_booth_button: Button = $BuyProductBoothButton #购买格子按钮 + +# 获取主游戏引用 +@onready var main_game = get_node("/root/main") + +# 当前小卖部数据 +var player_store_data: Array = [] +var max_store_slots: int = 10 # 默认10个格子 + +func _ready(): + # 连接按钮信号 + quit_button.pressed.connect(_on_quit_button_pressed) + refresh_button.pressed.connect(_on_refresh_button_pressed) + buy_product_booth_button.pressed.connect(_on_buy_product_booth_button_pressed) + + # 连接可见性改变信号 + visibility_changed.connect(_on_visibility_changed) + + # 默认隐藏面板 + self.hide() + +#面板显示与隐藏切换处理 +func _on_visibility_changed(): + if visible: + GlobalVariables.isZoomDisabled = true + # 面板显示时更新小卖部数据 + update_player_store_ui() + else: + GlobalVariables.isZoomDisabled = false + +# 初始化玩家小卖部 +func init_player_store(): + update_player_store_ui() + +# 更新小卖部UI +func update_player_store_ui(): + # 清空商品格子 + for child in store_grid.get_children(): + child.queue_free() + + # 获取小卖部数据 + if main_game.is_visiting_mode: + # 访问模式:显示被访问玩家的小卖部 + player_store_data = main_game.visited_player_data.get("玩家小卖部", []) + max_store_slots = main_game.visited_player_data.get("小卖部格子数", 10) + buy_product_booth_button.hide() # 访问模式下隐藏购买格子按钮 + else: + # 正常模式:显示自己的小卖部 + player_store_data = main_game.login_data.get("玩家小卖部", []) + max_store_slots = main_game.login_data.get("小卖部格子数", 10) + buy_product_booth_button.show() # 正常模式下显示购买格子按钮 + + # 创建商品按钮 + _create_store_buttons() + + # 更新购买格子按钮文本 + _update_buy_booth_button() + +# 创建小卖部商品按钮 +func _create_store_buttons(): + # 为每个格子创建按钮 + for i in range(max_store_slots): + var button = _create_store_slot_button(i) + store_grid.add_child(button) + +# 创建单个商品格子按钮 +func _create_store_slot_button(slot_index: int) -> Button: + var button = main_game.item_button.duplicate() + + # 确保按钮可见并可点击 + button.visible = true + button.disabled = false + button.focus_mode = Control.FOCUS_ALL + + # 检查该格子是否有商品 + var product_data = null + if slot_index < player_store_data.size(): + product_data = player_store_data[slot_index] + + if product_data: + # 有商品的格子 + var product_name = product_data.get("商品名称", "未知商品") + var product_price = product_data.get("商品价格", 0) + var product_count = product_data.get("商品数量", 0) + var product_type = product_data.get("商品类型", "作物") + + # 设置按钮文本 + button.text = str(product_name + "\n" + str(product_price) + "元/个\n库存:" + str(product_count)) + + # 更新商品图片 + _update_button_product_image(button, product_name, product_type) + + # 设置工具提示 + button.tooltip_text = str( + "商品: " + product_name + "\n" + + "类型: " + product_type + "\n" + + "单价: " + str(product_price) + " 元\n" + + "库存: " + str(product_count) + " 个" + ) + + # 连接点击事件 + if main_game.is_visiting_mode: + # 访问模式:显示购买弹窗 + button.pressed.connect(func(): _on_product_buy_selected(product_data, slot_index)) + else: + # 自己的小卖部:显示移除商品弹窗 + button.pressed.connect(func(): _on_product_manage_selected(product_data, slot_index)) + else: + # 空格子 + button.text = "空闲格子\n\n点击添加商品" + + # 设置为灰色样式 + if button.has_node("Title"): + button.get_node("Title").modulate = Color.GRAY + + # 只有在非访问模式下才允许点击空格子 + if not main_game.is_visiting_mode: + button.pressed.connect(func(): _on_empty_slot_selected(slot_index)) + else: + button.disabled = true + + return button + +# 更新商品图片 +func _update_button_product_image(button: Button, product_name: String, product_type: String): + var crop_image = button.get_node_or_null("CropImage") + if not crop_image: + return + + var texture = null + + if product_type == "作物": + # 作物商品:加载收获物图片 + texture = _get_crop_harvest_texture(product_name) + # 未来可以添加其他类型的商品图片加载 + + if texture: + crop_image.texture = texture + crop_image.visible = true + else: + crop_image.visible = false + +# 获取作物的收获物图片 +func _get_crop_harvest_texture(crop_name: String) -> Texture2D: + var crop_path = "res://assets/作物/" + crop_name + "/" + var harvest_texture_path = crop_path + "收获物.webp" + + if ResourceLoader.exists(harvest_texture_path): + var texture = load(harvest_texture_path) + if texture: + return texture + + # 如果没有找到,使用默认的收获物图片 + var default_harvest_path = "res://assets/作物/默认/收获物.webp" + if ResourceLoader.exists(default_harvest_path): + var texture = load(default_harvest_path) + if texture: + return texture + + return null + +# 访问模式:点击商品购买 +func _on_product_buy_selected(product_data: Dictionary, slot_index: int): + var product_name = product_data.get("商品名称", "未知商品") + var product_price = product_data.get("商品价格", 0) + var product_count = product_data.get("商品数量", 0) + var product_type = product_data.get("商品类型", "作物") + + # 检查商品是否还有库存 + if product_count <= 0: + Toast.show("该商品已售罄", Color.RED, 2.0, 1.0) + return + + # 获取批量购买弹窗 + var batch_buy_popup = get_node_or_null("/root/main/UI/DiaLog/BatchBuyPopup") + if batch_buy_popup and batch_buy_popup.has_method("show_buy_popup"): + # 显示批量购买弹窗 + batch_buy_popup.show_buy_popup( + product_name, + product_price, + "玩家小卖部商品", + "store_product", # 特殊类型标识 + _on_confirm_buy_store_product, + _on_cancel_buy_store_product + ) + + # 临时保存购买信息 + batch_buy_popup.set_meta("store_slot_index", slot_index) + batch_buy_popup.set_meta("store_product_data", product_data) + else: + Toast.show("购买功能暂未实现", Color.RED, 2.0, 1.0) + +# 确认购买小卖部商品 +func _on_confirm_buy_store_product(product_name: String, unit_price: int, quantity: int, buy_type: String): + var slot_index = get_node("/root/main/UI/DiaLog/BatchBuyPopup").get_meta("store_slot_index", -1) + var product_data = get_node("/root/main/UI/DiaLog/BatchBuyPopup").get_meta("store_product_data", {}) + + if slot_index == -1 or product_data.is_empty(): + Toast.show("购买信息错误", Color.RED, 2.0, 1.0) + return + + # 发送购买请求到服务器 + var tcp_network_manager = get_node_or_null("/root/main/UI/BigPanel/TCPNetworkManagerPanel") + if tcp_network_manager and tcp_network_manager.has_method("send_message"): + var visited_player_name = main_game.visited_player_data.get("player_name", "") + var message = { + "type": "buy_store_product", + "seller_username": main_game.visited_player_data.get("username", ""), + "slot_index": slot_index, + "product_name": product_name, + "unit_price": unit_price, + "quantity": quantity + } + tcp_network_manager.send_message(message) + + Toast.show("购买请求已发送", Color.YELLOW, 2.0, 1.0) + else: + Toast.show("网络连接异常,无法购买", Color.RED, 2.0, 1.0) + +# 取消购买小卖部商品 +func _on_cancel_buy_store_product(): + # 不需要做任何事情,弹窗会自动关闭 + pass + +# 自己的小卖部:点击商品管理 +func _on_product_manage_selected(product_data: Dictionary, slot_index: int): + var product_name = product_data.get("商品名称", "未知商品") + var product_count = product_data.get("商品数量", 0) + + # 显示管理确认对话框 + _show_product_manage_dialog(product_name, product_count, slot_index) + +# 显示商品管理对话框 +func _show_product_manage_dialog(product_name: String, product_count: int, slot_index: int): + var confirm_dialog = AcceptDialog.new() + confirm_dialog.dialog_text = str( + "商品管理\n\n" + + "商品:" + product_name + "\n" + + "库存:" + str(product_count) + " 个\n\n" + + "确认要下架这个商品吗?\n" + + "商品将返回到您的仓库中。" + ) + confirm_dialog.title = "商品管理" + confirm_dialog.ok_button_text = "下架商品" + confirm_dialog.add_cancel_button("取消") + + # 添加到场景 + add_child(confirm_dialog) + + # 连接信号 + confirm_dialog.confirmed.connect(_on_confirm_remove_product.bind(slot_index, confirm_dialog)) + confirm_dialog.canceled.connect(_on_cancel_remove_product.bind(confirm_dialog)) + + # 显示对话框 + confirm_dialog.popup_centered() + +# 确认下架商品 +func _on_confirm_remove_product(slot_index: int, dialog: AcceptDialog): + # 发送下架商品请求到服务器 + var tcp_network_manager = get_node_or_null("/root/main/UI/BigPanel/TCPNetworkManagerPanel") + if tcp_network_manager and tcp_network_manager.has_method("send_message"): + var message = { + "type": "remove_store_product", + "slot_index": slot_index + } + tcp_network_manager.send_message(message) + + Toast.show("下架请求已发送", Color.YELLOW, 2.0, 1.0) + else: + Toast.show("网络连接异常,无法下架", Color.RED, 2.0, 1.0) + + dialog.queue_free() + +# 取消下架商品 +func _on_cancel_remove_product(dialog: AcceptDialog): + dialog.queue_free() + +# 点击空格子 +func _on_empty_slot_selected(slot_index: int): + Toast.show("请从作物仓库选择商品添加到小卖部", Color.CYAN, 3.0, 1.0) + +# 更新购买格子按钮 +func _update_buy_booth_button(): + if main_game.is_visiting_mode: + return + + var next_slot_cost = 1000 + (max_store_slots - 10) * 500 + if max_store_slots >= 40: + buy_product_booth_button.text = "格子已满(40/40)" + buy_product_booth_button.disabled = true + else: + buy_product_booth_button.text = str("购买格子(+" + str(next_slot_cost) + "元)") + buy_product_booth_button.disabled = false + +# 购买格子按钮处理 +func _on_buy_product_booth_button_pressed(): + if main_game.is_visiting_mode: + return + + if max_store_slots >= 40: + Toast.show("格子数量已达上限", Color.RED, 2.0, 1.0) + return + + var next_slot_cost = 1000 + (max_store_slots - 10) * 500 + + if main_game.money < next_slot_cost: + Toast.show("金钱不足,需要 " + str(next_slot_cost) + " 元", Color.RED, 2.0, 1.0) + return + + # 显示购买确认对话框 + _show_buy_booth_dialog(next_slot_cost) + +# 显示购买格子确认对话框 +func _show_buy_booth_dialog(cost: int): + var confirm_dialog = AcceptDialog.new() + confirm_dialog.dialog_text = str( + "购买小卖部格子\n\n" + + "费用:" + str(cost) + " 元\n" + + "当前格子数:" + str(max_store_slots) + "\n" + + "购买后格子数:" + str(max_store_slots + 1) + "\n\n" + + "确认购买吗?" + ) + confirm_dialog.title = "购买格子" + confirm_dialog.ok_button_text = "确认购买" + confirm_dialog.add_cancel_button("取消") + + # 添加到场景 + add_child(confirm_dialog) + + # 连接信号 + confirm_dialog.confirmed.connect(_on_confirm_buy_booth.bind(cost, confirm_dialog)) + confirm_dialog.canceled.connect(_on_cancel_buy_booth.bind(confirm_dialog)) + + # 显示对话框 + confirm_dialog.popup_centered() + +# 确认购买格子 +func _on_confirm_buy_booth(cost: int, dialog: AcceptDialog): + # 发送购买格子请求到服务器 + var tcp_network_manager = get_node_or_null("/root/main/UI/BigPanel/TCPNetworkManagerPanel") + if tcp_network_manager and tcp_network_manager.has_method("send_message"): + var message = { + "type": "buy_store_booth", + "cost": cost + } + tcp_network_manager.send_message(message) + + Toast.show("购买请求已发送", Color.YELLOW, 2.0, 1.0) + else: + Toast.show("网络连接异常,无法购买", Color.RED, 2.0, 1.0) + + dialog.queue_free() + +# 取消购买格子 +func _on_cancel_buy_booth(dialog: AcceptDialog): + dialog.queue_free() + +# 关闭面板 +func _on_quit_button_pressed(): + self.hide() + +# 刷新小卖部 +func _on_refresh_button_pressed(): + update_player_store_ui() + Toast.show("小卖部已刷新", Color.GREEN, 2.0, 1.0) diff --git a/Script/BigPanel/PlayerStorePanel.gd.uid b/Script/BigPanel/PlayerStorePanel.gd.uid new file mode 100644 index 0000000..22c8560 --- /dev/null +++ b/Script/BigPanel/PlayerStorePanel.gd.uid @@ -0,0 +1 @@ +uid://bdavskipn547h diff --git a/Script/Dialog/AddProduct2StorePopup.gd b/Script/Dialog/AddProduct2StorePopup.gd new file mode 100644 index 0000000..86d09d8 --- /dev/null +++ b/Script/Dialog/AddProduct2StorePopup.gd @@ -0,0 +1,197 @@ +extends PanelContainer +#用于添加商品到玩家小卖部的弹窗 +@onready var title: Label = $VBox/Title #弹窗标题 +@onready var contents: Label = $VBox/Contents #这里显示弹窗内容 +@onready var sell_num_input: LineEdit = $VBox/SellNumInput #这里输入需要放入小卖部的商品数量 +@onready var sell_price_input: LineEdit = $VBox/SellPriceInput #这里输入每件商品的价格 +@onready var sure_button: Button = $VBox/HBox/SureButton #确定放入按钮 +@onready var cancel_button: Button = $VBox/HBox/CancelButton #取消按钮 + +# 当前要添加的商品信息 +var current_product_name: String = "" +var current_max_count: int = 0 +var current_suggested_price: int = 0 +var current_product_desc: String = "" + +# 回调函数,用于处理确认添加 +var confirm_callback: Callable +var cancel_callback: Callable + +func _ready(): + # 连接按钮信号 + sure_button.pressed.connect(_on_sure_button_pressed) + cancel_button.pressed.connect(_on_cancel_button_pressed) + + # 设置输入框的默认值和限制 + sell_num_input.text = "1" + sell_num_input.placeholder_text = "请输入数量" + sell_price_input.placeholder_text = "请输入单价" + + # 只允许输入数字 + sell_num_input.text_changed.connect(_on_sell_num_changed) + sell_price_input.text_changed.connect(_on_sell_price_changed) + + # 连接可见性改变信号 + visibility_changed.connect(_on_visibility_changed) + + # 默认隐藏弹窗 + self.hide() + +#面板显示与隐藏切换处理 +func _on_visibility_changed(): + if visible: + GlobalVariables.isZoomDisabled = true + pass + else: + GlobalVariables.isZoomDisabled = false + pass + +# 显示添加商品弹窗 +func show_add_product_popup(product_name: String, max_count: int, suggested_price: int, product_desc: String, on_confirm: Callable, on_cancel: Callable = Callable()): + current_product_name = product_name + current_max_count = max_count + current_suggested_price = suggested_price + current_product_desc = product_desc + confirm_callback = on_confirm + cancel_callback = on_cancel + + # 设置弹窗内容 + title.text = "添加商品到小卖部" + + contents.text = str( + "商品名称: " + product_name + "\n" + + "可用数量: " + str(max_count) + " 个\n" + + "建议价格: " + str(suggested_price) + " 元/个\n" + + "描述: " + product_desc + "\n\n" + + "请设置出售数量和价格:" + ) + + # 设置默认值 + sell_num_input.text = "1" + sell_price_input.text = str(suggested_price) + + # 显示弹窗并居中 + self.show() + self.move_to_front() + +# 处理数量输入变化 +func _on_sell_num_changed(new_text: String): + # 只允许输入数字 + var filtered_text = "" + for char in new_text: + if char.is_valid_int(): + filtered_text += char + + if filtered_text != new_text: + sell_num_input.text = filtered_text + sell_num_input.caret_column = filtered_text.length() + + # 更新预览信息 + _update_preview_info() + +# 处理价格输入变化 +func _on_sell_price_changed(new_text: String): + # 只允许输入数字 + var filtered_text = "" + for char in new_text: + if char.is_valid_int(): + filtered_text += char + + if filtered_text != new_text: + sell_price_input.text = filtered_text + sell_price_input.caret_column = filtered_text.length() + + # 更新预览信息 + _update_preview_info() + +# 更新预览信息 +func _update_preview_info(): + var quantity = get_sell_quantity() + var unit_price = get_sell_price() + var total_value = quantity * unit_price + + # 检查数量是否超过最大可用数量 + var quantity_status = "" + if quantity > current_max_count: + quantity_status = " (超出库存!)" + + # 检查价格是否合理 + var price_status = "" + if unit_price <= 0: + price_status = " (价格无效!)" + elif unit_price < current_suggested_price * 0.5: + price_status = " (价格偏低)" + elif unit_price > current_suggested_price * 2: + price_status = " (价格偏高)" + + var preview_info = "\n上架数量: " + str(quantity) + " 个" + quantity_status + "\n单价: " + str(unit_price) + " 元/个" + price_status + "\n总价值: " + str(total_value) + " 元" + + # 更新内容显示 + var base_content = str( + "商品名称: " + current_product_name + "\n" + + "可用数量: " + str(current_max_count) + " 个\n" + + "建议价格: " + str(current_suggested_price) + " 元/个\n" + + "描述: " + current_product_desc + "\n\n" + + "请设置出售数量和价格:" + ) + + contents.text = base_content + preview_info + +# 获取出售数量 +func get_sell_quantity() -> int: + var text = sell_num_input.text.strip_edges() + if text.is_empty(): + return 1 + + var quantity = text.to_int() + return max(1, quantity) # 至少出售1个 + +# 获取出售价格 +func get_sell_price() -> int: + var text = sell_price_input.text.strip_edges() + if text.is_empty(): + return current_suggested_price + + var price = text.to_int() + return max(1, price) # 至少1元 + +# 确认添加按钮处理 +func _on_sure_button_pressed(): + var quantity = get_sell_quantity() + var unit_price = get_sell_price() + + if quantity <= 0: + _show_error("数量必须大于0") + return + + if quantity > current_max_count: + _show_error("数量不能超过库存数量(" + str(current_max_count) + ")") + return + + if unit_price <= 0: + _show_error("价格必须大于0") + return + + # 调用确认回调函数 + if confirm_callback.is_valid(): + confirm_callback.call(current_product_name, quantity, unit_price) + + # 隐藏弹窗 + self.hide() + +# 取消添加按钮处理 +func _on_cancel_button_pressed(): + # 调用取消回调函数 + if cancel_callback.is_valid(): + cancel_callback.call() + + # 隐藏弹窗 + self.hide() + +# 显示错误信息 +func _show_error(message: String): + # 显示Toast错误提示 + if has_node("/root/Toast"): + get_node("/root/Toast").show(message, Color.RED, 2.0, 1.0) + else: + print("添加商品弹窗错误: " + message) diff --git a/Script/Dialog/AddProduct2StorePopup.gd.uid b/Script/Dialog/AddProduct2StorePopup.gd.uid new file mode 100644 index 0000000..bc3a94b --- /dev/null +++ b/Script/Dialog/AddProduct2StorePopup.gd.uid @@ -0,0 +1 @@ +uid://cha0uw4ra1trr diff --git a/Script/Dialog/BatchSellPopup.gd b/Script/Dialog/BatchSellPopup.gd new file mode 100644 index 0000000..cf1060a --- /dev/null +++ b/Script/Dialog/BatchSellPopup.gd @@ -0,0 +1,154 @@ +extends PanelContainer +#用于作物批量出售作物弹窗 +@onready var title: Label = $VBox/Title #弹窗标题 +@onready var contents: Label = $VBox/Contents #这里显示弹窗内容 +@onready var sell_num_edit: LineEdit = $VBox/SellNumEdit #出售作物数量 +@onready var sure_button: Button = $VBox/HBox/SureButton #确定按钮 +@onready var cancel_button: Button = $VBox/HBox/CancelButton #取消按钮 + +# 当前出售的作物信息 +var current_crop_name: String = "" +var current_max_count: int = 0 +var current_unit_price: int = 0 +var current_crop_desc: String = "" + +# 回调函数,用于处理确认出售 +var confirm_callback: Callable +var cancel_callback: Callable + +func _ready(): + # 连接按钮信号 + sure_button.pressed.connect(_on_sure_button_pressed) + cancel_button.pressed.connect(_on_cancel_button_pressed) + + # 设置数量输入框的默认值和限制 + sell_num_edit.text = "1" + sell_num_edit.placeholder_text = "请输入出售数量" + + # 只允许输入数字 + sell_num_edit.text_changed.connect(_on_sell_num_changed) + + # 连接可见性改变信号 + visibility_changed.connect(_on_visibility_changed) + + # 默认隐藏弹窗 + self.hide() + +#面板显示与隐藏切换处理 +func _on_visibility_changed(): + if visible: + GlobalVariables.isZoomDisabled = true + pass + else: + GlobalVariables.isZoomDisabled = false + pass + +# 显示批量出售弹窗 +func show_sell_popup(crop_name: String, max_count: int, unit_price: int, crop_desc: String, on_confirm: Callable, on_cancel: Callable = Callable()): + current_crop_name = crop_name + current_max_count = max_count + current_unit_price = unit_price + current_crop_desc = crop_desc + confirm_callback = on_confirm + cancel_callback = on_cancel + + # 设置弹窗内容 + title.text = "批量出售作物" + + contents.text = str( + "作物名称: " + crop_name + "\n" + + "单价: " + str(unit_price) + " 元/个\n" + + "可出售数量: " + str(max_count) + " 个\n" + + "描述: " + crop_desc + "\n\n" + + "请输入出售数量:" + ) + + # 重置出售数量为1 + sell_num_edit.text = "1" + + # 显示弹窗并居中 + self.show() + self.move_to_front() + +# 处理数量输入变化 +func _on_sell_num_changed(new_text: String): + # 只允许输入数字 + var filtered_text = "" + for char in new_text: + if char.is_valid_int(): + filtered_text += char + + if filtered_text != new_text: + sell_num_edit.text = filtered_text + sell_num_edit.caret_column = filtered_text.length() + + # 更新总价显示 + _update_total_income() + +# 更新总收入显示 +func _update_total_income(): + var quantity = get_sell_quantity() + var total_income = quantity * current_unit_price + + # 检查数量是否超过最大可售数量 + var quantity_status = "" + if quantity > current_max_count: + quantity_status = " (超出库存!)" + + var income_info = "\n出售数量: " + str(quantity) + " 个" + quantity_status + "\n总收入: " + str(total_income) + " 元" + + # 更新内容显示 + var base_content = str( + "作物名称: " + current_crop_name + "\n" + + "单价: " + str(current_unit_price) + " 元/个\n" + + "可出售数量: " + str(current_max_count) + " 个\n" + + "描述: " + current_crop_desc + "\n\n" + + "请输入出售数量:" + ) + + contents.text = base_content + income_info + +# 获取出售数量 +func get_sell_quantity() -> int: + var text = sell_num_edit.text.strip_edges() + if text.is_empty(): + return 1 + + var quantity = text.to_int() + return max(1, quantity) # 至少出售1个 + +# 确认出售按钮处理 +func _on_sure_button_pressed(): + var quantity = get_sell_quantity() + + if quantity <= 0: + _show_error("出售数量必须大于0") + return + + if quantity > current_max_count: + _show_error("出售数量不能超过库存数量(" + str(current_max_count) + ")") + return + + # 调用确认回调函数 + if confirm_callback.is_valid(): + confirm_callback.call(current_crop_name, quantity, current_unit_price) + + # 隐藏弹窗 + self.hide() + +# 取消出售按钮处理 +func _on_cancel_button_pressed(): + # 调用取消回调函数 + if cancel_callback.is_valid(): + cancel_callback.call() + + # 隐藏弹窗 + self.hide() + +# 显示错误信息 +func _show_error(message: String): + # 显示Toast错误提示 + if has_node("/root/Toast"): + get_node("/root/Toast").show(message, Color.RED, 2.0, 1.0) + else: + print("批量出售弹窗错误: " + message) diff --git a/Script/Dialog/BatchSellPopup.gd.uid b/Script/Dialog/BatchSellPopup.gd.uid new file mode 100644 index 0000000..e674942 --- /dev/null +++ b/Script/Dialog/BatchSellPopup.gd.uid @@ -0,0 +1 @@ +uid://dsmmxivba06ab diff --git a/Script/SmallPanel/CropInformPanel.gd b/Script/SmallPanel/CropInformPanel.gd new file mode 100644 index 0000000..edfb716 --- /dev/null +++ b/Script/SmallPanel/CropInformPanel.gd @@ -0,0 +1,216 @@ +extends Panel + +@onready var quit_button: Button = $QuitButton #关闭作物信息面板 +@onready var crop_image: TextureRect = $VBox/CropImage #显示作物图片 +@onready var crop_name: Label = $VBox/CropName #作物名称 +@onready var crop_description: Label = $VBox/CropDescription #作物介绍 +@onready var crop_price: Label = $VBox/CropPrice #作物价格 +@onready var crop_quality: Label = $VBox/CropQuality #作物品质 +@onready var sale_product: Button = $VBox/HBox/SaleProduct #直接出售 +@onready var add_to_store: Button = $VBox/HBox/AddToStore #添加到小卖部 + +@onready var add_product_to_store_popup: PanelContainer = $'../../DiaLog/AddProductToStorePopup' + + + +# 当前显示的作物信息 +var current_crop_name: String = "" +var current_crop_count: int = 0 + +# 获取主游戏引用 +@onready var main_game = get_node("/root/main") + +func _ready(): + # 连接按钮信号 + quit_button.pressed.connect(_on_quit_button_pressed) + sale_product.pressed.connect(_on_sale_product_pressed) + add_to_store.pressed.connect(_on_add_to_store_pressed) + + # 默认隐藏面板 + self.hide() + +# 显示作物信息 +func show_crop_info(crop_name: String, crop_count: int): + current_crop_name = crop_name + current_crop_count = crop_count + + # 更新作物信息显示 + _update_crop_display() + + # 显示面板 + self.show() + self.move_to_front() + +# 更新作物信息显示 +func _update_crop_display(): + if not main_game.can_planted_crop.has(current_crop_name): + crop_name.text = "作物名称:" + current_crop_name + crop_description.text = "描述:未知作物" + crop_price.text = "收购价:未知" + crop_quality.text = "品质:未知" + return + + var crop_data = main_game.can_planted_crop[current_crop_name] + + # 获取显示名称 + var display_name = current_crop_name + var mature_name = crop_data.get("成熟物名称") + if mature_name != null and mature_name != "": + display_name = mature_name + else: + display_name = crop_data.get("作物名称", current_crop_name) + + # 更新文本显示 + crop_name.text = "作物名称:" + display_name + " (数量: " + str(current_crop_count) + ")" + crop_description.text = "描述:" + crop_data.get("描述", "美味的作物") + + # 计算出售价格(基于收益) + var sell_price = crop_data.get("收益", 0) + crop_price.text = "收购价:" + str(sell_price) + " 元/个" + + var quality = crop_data.get("品质", "普通") + crop_quality.text = "品质:" + quality + + # 更新作物图片 + _update_crop_image() + +# 更新作物图片 +func _update_crop_image(): + var texture = _get_crop_harvest_texture(current_crop_name) + if texture: + crop_image.texture = texture + else: + # 使用默认图片 + var default_texture = _get_crop_harvest_texture("默认") + if default_texture: + crop_image.texture = default_texture + +# 获取作物的收获物图片 +func _get_crop_harvest_texture(crop_name: String) -> Texture2D: + var crop_path = "res://assets/作物/" + crop_name + "/" + var harvest_texture_path = crop_path + "收获物.webp" + + if ResourceLoader.exists(harvest_texture_path): + var texture = load(harvest_texture_path) + if texture: + return texture + + # 如果没有找到,使用默认的收获物图片 + var default_harvest_path = "res://assets/作物/默认/收获物.webp" + if ResourceLoader.exists(default_harvest_path): + var texture = load(default_harvest_path) + if texture: + return texture + + return null + +# 关闭面板 +func _on_quit_button_pressed(): + self.hide() + +# 直接出售按钮处理 +func _on_sale_product_pressed(): + # 检查是否在访问模式 + if main_game.is_visiting_mode: + Toast.show("访问模式下无法出售作物", Color.ORANGE, 2.0, 1.0) + return + + # 获取批量出售弹窗 + var batch_sell_popup = get_node_or_null("/root/main/UI/DiaLog/BatchSellPopup") + if batch_sell_popup and batch_sell_popup.has_method("show_sell_popup"): + # 获取作物数据以传递给出售弹窗 + var crop_data = main_game.can_planted_crop.get(current_crop_name, {}) + var sell_price = crop_data.get("收益", 0) + var description = crop_data.get("描述", "美味的作物") + + # 显示批量出售弹窗 + batch_sell_popup.show_sell_popup( + current_crop_name, + current_crop_count, + sell_price, + description, + _on_confirm_sell_crop, + _on_cancel_sell_crop + ) + else: + Toast.show("批量出售功能暂未实现", Color.RED, 2.0, 1.0) + print("错误:找不到BatchSellPopup或相关方法") + +# 确认出售作物回调 +func _on_confirm_sell_crop(crop_name: String, sell_count: int, unit_price: int): + # 发送出售请求到服务器 + var tcp_network_manager = get_node_or_null("/root/main/UI/BigPanel/TCPNetworkManagerPanel") + if tcp_network_manager and tcp_network_manager.has_method("send_message"): + var message = { + "type": "sell_crop", + "crop_name": crop_name, + "sell_count": sell_count, + "unit_price": unit_price + } + tcp_network_manager.send_message(message) + + # 关闭作物信息面板 + self.hide() + + Toast.show("出售请求已发送", Color.YELLOW, 2.0, 1.0) + else: + Toast.show("网络连接异常,无法出售", Color.RED, 2.0, 1.0) + +# 取消出售作物回调 +func _on_cancel_sell_crop(): + # 不需要做任何事情,弹窗会自动关闭 + pass + +# 添加到小卖部按钮处理 +func _on_add_to_store_pressed(): + # 检查是否在访问模式 + if main_game.is_visiting_mode: + Toast.show("访问模式下无法操作小卖部", Color.ORANGE, 2.0, 1.0) + return + + # 获取添加商品到小卖部的弹窗 + + if add_product_to_store_popup and add_product_to_store_popup.has_method("show_add_product_popup"): + # 获取作物数据以传递给弹窗 + var crop_data = main_game.can_planted_crop.get(current_crop_name, {}) + var sell_price = crop_data.get("收益", 0) + var description = crop_data.get("描述", "美味的作物") + + # 显示添加商品弹窗 + add_product_to_store_popup.show_add_product_popup( + current_crop_name, + current_crop_count, + sell_price, + description, + _on_confirm_add_to_store, + _on_cancel_add_to_store + ) + else: + Toast.show("添加商品功能暂未实现", Color.RED, 2.0, 1.0) + print("错误:找不到AddProduct2StorePopup或相关方法") + +# 确认添加到小卖部回调 +func _on_confirm_add_to_store(crop_name: String, add_count: int, unit_price: int): + # 发送添加商品到小卖部的请求到服务器 + var tcp_network_manager = get_node_or_null("/root/main/UI/BigPanel/TCPNetworkManagerPanel") + if tcp_network_manager and tcp_network_manager.has_method("send_message"): + var message = { + "type": "add_product_to_store", + "product_type": "作物", + "product_name": crop_name, + "product_count": add_count, + "product_price": unit_price + } + tcp_network_manager.send_message(message) + + # 关闭作物信息面板 + self.hide() + + Toast.show("添加商品请求已发送", Color.YELLOW, 2.0, 1.0) + else: + Toast.show("网络连接异常,无法添加商品", Color.RED, 2.0, 1.0) + +# 取消添加到小卖部回调 +func _on_cancel_add_to_store(): + # 不需要做任何事情,弹窗会自动关闭 + pass diff --git a/Script/SmallPanel/CropInformPanel.gd.uid b/Script/SmallPanel/CropInformPanel.gd.uid new file mode 100644 index 0000000..5b82a8d --- /dev/null +++ b/Script/SmallPanel/CropInformPanel.gd.uid @@ -0,0 +1 @@ +uid://b185o1hjnlrv5 diff --git a/Script/SmallPanel/DebugPanel.gd b/Script/SmallPanel/DebugPanel.gd index 9e316fe..23e733a 100644 --- a/Script/SmallPanel/DebugPanel.gd +++ b/Script/SmallPanel/DebugPanel.gd @@ -98,7 +98,12 @@ func _update_performance_data(): # 从主游戏获取加载信息 if main_game and main_game.crop_texture_manager: var manager = main_game.crop_texture_manager - performance_data["loaded_textures"] = manager.texture_cache.size() + manager.mature_texture_cache.size() + # 计算所有加载的纹理数量(新的三阶段系统) + var total_textures = 0 + for crop_name in manager.texture_cache.keys(): + total_textures += manager.texture_cache[crop_name].size() + total_textures += manager.default_textures.size() + performance_data["loaded_textures"] = total_textures performance_data["failed_textures"] = manager.failed_resources.size() # 加载进度 diff --git a/Server/QQEmailSend.py b/Server/QQEmailSend.py index 6728fa4..66994bf 100644 --- a/Server/QQEmailSend.py +++ b/Server/QQEmailSend.py @@ -131,13 +131,14 @@ class EmailVerification: return ''.join(random.choice(chars) for _ in range(length)) @staticmethod - def send_verification_email(qq_number, verification_code): + def send_verification_email(qq_number, verification_code, email_type="register"): """ 发送验证码邮件到QQ邮箱 参数: qq_number (str): 接收者QQ号 verification_code (str): 验证码 + email_type (str): 邮件类型,"register" 或 "reset_password" 返回: bool: 发送成功返回True,否则返回False @@ -145,15 +146,25 @@ class EmailVerification: """ receiver_email = f"{qq_number}@qq.com" + # 根据邮件类型设置不同的内容 + if email_type == "reset_password": + email_title = "【萌芽农场】密码重置验证码" + email_purpose = "重置萌芽农场游戏账号密码" + email_color = "#FF6B35" # 橙红色,表示警告性操作 + else: + email_title = "【萌芽农场】注册验证码" + email_purpose = "注册萌芽农场游戏账号" + email_color = "#4CAF50" # 绿色,表示正常操作 + # 创建邮件内容 message = MIMEText(f'''
亲爱的玩家,您好!
-您正在注册萌芽农场游戏账号,您的验证码是:
-您正在{email_purpose},您的验证码是:
+该验证码有效期为5分钟,请勿泄露给他人。
@@ -169,7 +180,7 @@ class EmailVerification: # 修正From头格式,符合QQ邮箱的要求 message['From'] = SENDER_EMAIL message['To'] = receiver_email - message['Subject'] = Header('【萌芽农场】注册验证码', 'utf-8') + message['Subject'] = Header(email_title, 'utf-8') try: # 使用SSL/TLS连接而不是STARTTLS @@ -182,7 +193,7 @@ class EmailVerification: return False, f"发送验证码失败: {str(e)}" @staticmethod - def save_verification_code(qq_number, verification_code, expiry_time=300): + def save_verification_code(qq_number, verification_code, expiry_time=300, code_type="register"): """ 保存验证码到缓存文件 @@ -190,6 +201,7 @@ class EmailVerification: qq_number (str): QQ号 verification_code (str): 验证码 expiry_time (int): 过期时间(秒),默认5分钟 + code_type (str): 验证码类型,"register" 或 "reset_password" 返回: bool: 保存成功返回True,否则返回False @@ -205,33 +217,43 @@ class EmailVerification: try: with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file: verification_data = json.load(file) - except: + except Exception as e: + print(f"读取验证码文件失败: {str(e)}") verification_data = {} # 添加新的验证码 expire_at = time.time() + expiry_time + current_time = time.time() + + # 创建验证码记录,包含更多信息用于调试 verification_data[qq_number] = { "code": verification_code, - "expire_at": expire_at + "expire_at": expire_at, + "code_type": code_type, + "created_at": current_time, + "used": False # 新增:标记验证码是否已使用 } # 保存到文件 try: with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: json.dump(verification_data, file, indent=2, ensure_ascii=False) + + print(f"[验证码系统] 为QQ {qq_number} 保存{code_type}验证码: {verification_code}, 过期时间: {expire_at}") return True except Exception as e: print(f"保存验证码失败: {str(e)}") return False @staticmethod - def verify_code(qq_number, input_code): + def verify_code(qq_number, input_code, code_type="register"): """ 验证用户输入的验证码 参数: qq_number (str): QQ号 input_code (str): 用户输入的验证码 + code_type (str): 验证码类型,"register" 或 "reset_password" 返回: bool: 验证成功返回True,否则返回False @@ -241,23 +263,41 @@ class EmailVerification: # 检查缓存文件是否存在 if not os.path.exists(VERIFICATION_CACHE_FILE): + print(f"[验证码系统] QQ {qq_number} 验证失败: 缓存文件不存在") return False, "验证码不存在或已过期" # 读取验证码数据 try: with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file: verification_data = json.load(file) - except: + except Exception as e: + print(f"[验证码系统] 读取验证码文件失败: {str(e)}") return False, "验证码数据损坏" # 检查该QQ号是否有验证码 if qq_number not in verification_data: + print(f"[验证码系统] QQ {qq_number} 验证失败: 没有找到验证码记录") return False, "验证码不存在,请重新获取" # 获取存储的验证码信息 code_info = verification_data[qq_number] stored_code = code_info.get("code", "") expire_at = code_info.get("expire_at", 0) + stored_code_type = code_info.get("code_type", "register") + is_used = code_info.get("used", False) + created_at = code_info.get("created_at", 0) + + print(f"[验证码系统] QQ {qq_number} 验证码详情: 存储码={stored_code}, 输入码={input_code}, 类型={stored_code_type}, 已使用={is_used}, 创建时间={created_at}") + + # 检查验证码类型是否匹配 + if stored_code_type != code_type: + print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码类型不匹配,存储类型={stored_code_type}, 请求类型={code_type}") + return False, f"验证码类型不匹配,请重新获取{code_type}验证码" + + # 检查验证码是否已被使用 + if is_used: + print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码已被使用") + return False, "验证码已被使用,请重新获取" # 检查验证码是否过期 current_time = time.time() @@ -266,22 +306,31 @@ class EmailVerification: del verification_data[qq_number] with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: json.dump(verification_data, file, indent=2, ensure_ascii=False) + print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码已过期") return False, "验证码已过期,请重新获取" # 验证码比较(不区分大小写) if input_code.upper() == stored_code.upper(): - # 验证成功后移除该验证码 - del verification_data[qq_number] - with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: - json.dump(verification_data, file, indent=2, ensure_ascii=False) - return True, "验证码正确" + # 验证成功,标记为已使用而不是删除 + verification_data[qq_number]["used"] = True + verification_data[qq_number]["used_at"] = current_time + + try: + with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: + json.dump(verification_data, file, indent=2, ensure_ascii=False) + print(f"[验证码系统] QQ {qq_number} 验证成功: 验证码已标记为已使用") + return True, "验证码正确" + except Exception as e: + print(f"[验证码系统] 标记验证码已使用时失败: {str(e)}") + return True, "验证码正确" # 即使标记失败,验证还是成功的 else: + print(f"[验证码系统] QQ {qq_number} 验证失败: 验证码不匹配") return False, "验证码错误" @staticmethod def clean_expired_codes(): """ - 清理过期的验证码 + 清理过期的验证码和已使用的验证码 """ import time @@ -295,22 +344,79 @@ class EmailVerification: current_time = time.time() removed_keys = [] - # 找出过期的验证码 + # 找出过期的验证码和已使用的验证码(超过1小时) for qq_number, code_info in verification_data.items(): expire_at = code_info.get("expire_at", 0) + is_used = code_info.get("used", False) + used_at = code_info.get("used_at", 0) + + should_remove = False + + # 过期的验证码 if current_time > expire_at: + should_remove = True + print(f"[验证码清理] 移除过期验证码: QQ {qq_number}") + + # 已使用超过1小时的验证码 + elif is_used and used_at > 0 and (current_time - used_at) > 3600: + should_remove = True + print(f"[验证码清理] 移除已使用的验证码: QQ {qq_number}") + + if should_remove: removed_keys.append(qq_number) - # 移除过期的验证码 + # 移除标记的验证码 for key in removed_keys: del verification_data[key] # 保存更新后的数据 - with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: - json.dump(verification_data, file, indent=2, ensure_ascii=False) + if removed_keys: + with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file: + json.dump(verification_data, file, indent=2, ensure_ascii=False) + print(f"[验证码清理] 共清理了 {len(removed_keys)} 个验证码") except Exception as e: - print(f"清理过期验证码失败: {str(e)}") + print(f"清理验证码失败: {str(e)}") + + @staticmethod + def get_verification_status(qq_number): + """ + 获取验证码状态(用于调试) + + 参数: + qq_number (str): QQ号 + + 返回: + dict: 验证码状态信息 + """ + import time + + if not os.path.exists(VERIFICATION_CACHE_FILE): + return {"status": "no_cache_file"} + + try: + with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file: + verification_data = json.load(file) + + if qq_number not in verification_data: + return {"status": "no_code"} + + code_info = verification_data[qq_number] + current_time = time.time() + + return { + "status": "found", + "code": code_info.get("code", ""), + "code_type": code_info.get("code_type", "unknown"), + "used": code_info.get("used", False), + "expired": current_time > code_info.get("expire_at", 0), + "created_at": code_info.get("created_at", 0), + "expire_at": code_info.get("expire_at", 0), + "used_at": code_info.get("used_at", 0) + } + + except Exception as e: + return {"status": "error", "message": str(e)} # 测试邮件发送 diff --git a/Server/TCPGameServer.py b/Server/TCPGameServer.py index 0a3fd40..49d500b 100644 --- a/Server/TCPGameServer.py +++ b/Server/TCPGameServer.py @@ -94,6 +94,7 @@ class TCPGameServer(TCPServer): self.start_batch_save_timer() self.start_weed_growth_timer() self.start_wisdom_tree_health_decay_timer() + self.start_verification_code_cleanup_timer() #初始化性能操作 def _init_performance_settings(self): @@ -170,6 +171,20 @@ class TCPGameServer(TCPServer): self.wisdom_tree_decay_timer.daemon = True self.wisdom_tree_decay_timer.start() + def start_verification_code_cleanup_timer(self): + """启动验证码清理定时器""" + try: + from QQEmailSend import EmailVerification + EmailVerification.clean_expired_codes() + self.log('INFO', "验证码清理完成", 'SERVER') + except Exception as e: + self.log('ERROR', f"验证码清理时出错: {str(e)}", 'SERVER') + + # 创建下一个验证码清理计时器(每30分钟检查一次) + self.verification_cleanup_timer = threading.Timer(1800, self.start_verification_code_cleanup_timer) # 每30分钟清理一次 + self.verification_cleanup_timer.daemon = True + self.verification_cleanup_timer.start() + #获取服务器统计信息 def get_server_stats(self): """获取服务器统计信息""" @@ -203,6 +218,12 @@ class TCPGameServer(TCPServer): self.wisdom_tree_decay_timer = None self.log('INFO', "智慧树生命值衰减计时器已停止", 'SERVER') + # 停止验证码清理定时器 + if hasattr(self, 'verification_cleanup_timer') and self.verification_cleanup_timer: + self.verification_cleanup_timer.cancel() + self.verification_cleanup_timer = None + self.log('INFO', "验证码清理定时器已停止", 'SERVER') + # 强制保存所有缓存数据 self.log('INFO', "正在保存所有玩家数据...", 'SERVER') saved_count = self.force_save_all_data() @@ -678,6 +699,10 @@ class TCPGameServer(TCPServer): return self._handle_register(client_id, message) elif message_type == "request_verification_code":#验证码请求 return self._handle_verification_code_request(client_id, message) + elif message_type == "request_forget_password_verification_code":#忘记密码验证码请求 + return self._handle_forget_password_verification_code_request(client_id, message) + elif message_type == "reset_password":#重置密码 + return self._handle_reset_password_request(client_id, message) elif message_type == "verify_code":#验证码 return self._handle_verify_code(client_id, message) @@ -775,6 +800,16 @@ class TCPGameServer(TCPServer): return self._handle_wisdom_tree_message(client_id, message) elif message_type == "get_wisdom_tree_config":#获取智慧树配置 return self._handle_get_wisdom_tree_config(client_id, message) + elif message_type == "sell_crop":#出售作物 + return self._handle_sell_crop(client_id, message) + elif message_type == "add_product_to_store":#添加商品到小卖部 + return self._handle_add_product_to_store(client_id, message) + elif message_type == "remove_store_product":#下架小卖部商品 + return self._handle_remove_store_product(client_id, message) + elif message_type == "buy_store_product":#购买小卖部商品 + return self._handle_buy_store_product(client_id, message) + elif message_type == "buy_store_booth":#购买小卖部格子 + return self._handle_buy_store_booth(client_id, message) #--------------------------------------------------------------------------- elif message_type == "message":#处理聊天消息(暂未实现) @@ -982,9 +1017,12 @@ class TCPGameServer(TCPServer): # 验证验证码 if verification_code: from QQEmailSend import EmailVerification - success, verify_message = EmailVerification.verify_code(username, verification_code) + success, verify_message = EmailVerification.verify_code(username, verification_code, "register") if not success: + self.log('WARNING', f"QQ号 {username} 注册验证码验证失败: {verify_message}", 'SERVER') return self._send_register_error(client_id, f"验证码错误: {verify_message}") + else: + self.log('INFO', f"QQ号 {username} 注册验证码验证成功", 'SERVER') # 检查用户是否已存在 file_path = os.path.join("game_saves", f"{username}.json") @@ -1114,9 +1152,9 @@ class TCPGameServer(TCPServer): success, send_message = EmailVerification.send_verification_email(qq_number, verification_code) if success: - # 保存验证码 - EmailVerification.save_verification_code(qq_number, verification_code) - self.log('INFO', f"已向QQ号 {qq_number} 发送验证码", 'SERVER') + # 保存验证码(注册类型) + EmailVerification.save_verification_code(qq_number, verification_code, 300, "register") + self.log('INFO', f"已向QQ号 {qq_number} 发送注册验证码: {verification_code}", 'SERVER') return self.send_data(client_id, { "type": "verification_code_response", @@ -1166,6 +1204,127 @@ class TCPGameServer(TCPServer): #验证QQ号格式 + #处理忘记密码验证码请求 + def _handle_forget_password_verification_code_request(self, client_id, message): + """处理忘记密码验证码请求""" + from QQEmailSend import EmailVerification + + qq_number = message.get("qq_number", "") + + # 验证QQ号 + if not self._validate_qq_number(qq_number): + return self.send_data(client_id, { + "type": "forget_password_verification_code_response", + "success": False, + "message": "QQ号格式无效,请输入5-12位数字" + }) + + # 检查账号是否存在 + player_data = self.load_player_data(qq_number) + if not player_data: + return self.send_data(client_id, { + "type": "forget_password_verification_code_response", + "success": False, + "message": "该账号不存在,请检查QQ号是否正确" + }) + + # 生成验证码 + verification_code = EmailVerification.generate_verification_code() + + # 发送验证码邮件(专门用于密码重置) + success, send_message = EmailVerification.send_verification_email(qq_number, verification_code, "reset_password") + + if success: + # 保存验证码(密码重置类型) + EmailVerification.save_verification_code(qq_number, verification_code, 300, "reset_password") + self.log('INFO', f"已向QQ号 {qq_number} 发送密码重置验证码: {verification_code}", 'SERVER') + + return self.send_data(client_id, { + "type": "forget_password_verification_code_response", + "success": True, + "message": "密码重置验证码已发送到您的QQ邮箱,请查收" + }) + else: + self.log('ERROR', f"发送密码重置验证码失败: {send_message}", 'SERVER') + return self.send_data(client_id, { + "type": "forget_password_verification_code_response", + "success": False, + "message": f"发送验证码失败: {send_message}" + }) + + #处理重置密码请求 + def _handle_reset_password_request(self, client_id, message): + """处理重置密码请求""" + from QQEmailSend import EmailVerification + + username = message.get("username", "") + new_password = message.get("new_password", "") + verification_code = message.get("verification_code", "") + + # 验证必填字段 + if not username or not new_password or not verification_code: + return self.send_data(client_id, { + "type": "reset_password_response", + "success": False, + "message": "用户名、新密码或验证码不能为空" + }) + + # 验证QQ号格式 + if not self._validate_qq_number(username): + return self.send_data(client_id, { + "type": "reset_password_response", + "success": False, + "message": "用户名必须是5-12位的QQ号码" + }) + + # 检查账号是否存在 + player_data = self.load_player_data(username) + if not player_data: + return self.send_data(client_id, { + "type": "reset_password_response", + "success": False, + "message": "该账号不存在,请检查QQ号是否正确" + }) + + # 验证验证码(密码重置类型) + success, verify_message = EmailVerification.verify_code(username, verification_code, "reset_password") + if not success: + self.log('WARNING', f"QQ号 {username} 密码重置验证码验证失败: {verify_message}", 'SERVER') + return self.send_data(client_id, { + "type": "reset_password_response", + "success": False, + "message": f"验证码错误: {verify_message}" + }) + else: + self.log('INFO', f"QQ号 {username} 密码重置验证码验证成功", 'SERVER') + + # 更新密码 + try: + player_data["user_password"] = new_password + + # 保存到缓存和文件 + self.player_cache[username] = player_data + self.dirty_players.add(username) + + # 立即保存重要的账户信息 + self.save_player_data_immediate(username) + + self.log('INFO', f"用户 {username} 密码重置成功", 'ACCOUNT') + + return self.send_data(client_id, { + "type": "reset_password_response", + "success": True, + "message": "密码重置成功,请使用新密码登录" + }) + + except Exception as e: + self.log('ERROR', f"重置密码时出错: {str(e)}", 'ACCOUNT') + return self.send_data(client_id, { + "type": "reset_password_response", + "success": False, + "message": "密码重置失败,请稍后重试" + }) + #辅助函数-验证QQ号格式 def _validate_qq_number(self, qq_number): """验证QQ号格式""" @@ -5675,6 +5834,7 @@ class TCPGameServer(TCPServer): safe_player_data = { "user_name": target_player_data.get("user_name", target_username), + "username": target_username, # 添加username字段,用于购买商品时标识卖家 "player_name": target_player_data.get("player_name", target_username), "farm_name": target_player_data.get("farm_name", ""), "level": target_player_data.get("level", 1), @@ -5690,6 +5850,9 @@ class TCPGameServer(TCPServer): "出战宠物": self._convert_battle_pets_to_full_data(target_player_data), "稻草人配置": target_player_data.get("稻草人配置", {}), "智慧树配置": target_player_data.get("智慧树配置", {}), + "玩家小卖部": target_player_data.get("玩家小卖部", []), # 添加小卖部数据 + "小卖部格子数": target_player_data.get("小卖部格子数", 10), # 添加小卖部格子数 + "点赞数": target_player_data.get("点赞数", 0), # 添加点赞数 "last_login_time": target_player_data.get("last_login_time", "未知"), "total_login_time": target_player_data.get("total_login_time", "0时0分0秒"), "total_likes": target_player_data.get("total_likes", 0) @@ -8502,6 +8665,454 @@ class TCPGameServer(TCPServer): #==========================智慧树系统处理========================== +#==========================作物出售处理========================== + def _handle_sell_crop(self, client_id, message): + """处理作物出售请求""" + # 检查用户是否已登录 + logged_in, response = self._check_user_logged_in(client_id, "出售作物", "sell_crop") + if not logged_in: + return self.send_data(client_id, response) + + # 获取玩家数据 + player_data, username, response = self._load_player_data_with_check(client_id, "sell_crop") + if not player_data: + return self.send_data(client_id, response) + + crop_name = message.get("crop_name", "") + sell_count = message.get("sell_count", 1) + unit_price = message.get("unit_price", 0) + + # 验证参数 + if not crop_name: + return self._send_action_error(client_id, "sell_crop", "作物名称不能为空") + + if sell_count <= 0: + return self._send_action_error(client_id, "sell_crop", "出售数量必须大于0") + + if unit_price <= 0: + return self._send_action_error(client_id, "sell_crop", "单价必须大于0") + + # 检查作物仓库中是否有足够的作物 + crop_warehouse = player_data.get("作物仓库", []) + crop_found = False + crop_index = -1 + available_count = 0 + + for i, crop_item in enumerate(crop_warehouse): + if crop_item.get("name") == crop_name: + crop_found = True + crop_index = i + available_count = crop_item.get("count", 0) + break + + if not crop_found: + return self._send_action_error(client_id, "sell_crop", f"作物仓库中没有 {crop_name}") + + if available_count < sell_count: + return self._send_action_error(client_id, "sell_crop", f"作物数量不足,仓库中只有 {available_count} 个 {crop_name}") + + # 验证价格(防止客户端篡改价格) + crop_data = self._load_crop_data() + if crop_name in crop_data: + expected_price = crop_data[crop_name].get("收益", 0) + if unit_price != expected_price: + return self._send_action_error(client_id, "sell_crop", f"价格验证失败,{crop_name} 的正确价格应为 {expected_price} 元/个") + else: + return self._send_action_error(client_id, "sell_crop", f"未知的作物类型:{crop_name}") + + # 计算总收入 + total_income = sell_count * unit_price + + # 执行出售操作 + player_data["money"] += total_income + + # 从作物仓库中减少数量 + crop_warehouse[crop_index]["count"] -= sell_count + + # 如果数量为0,从仓库中移除该作物 + if crop_warehouse[crop_index]["count"] <= 0: + crop_warehouse.pop(crop_index) + + # 给予少量出售经验 + sell_experience = max(1, sell_count // 5) # 每5个作物给1点经验 + player_data["experience"] += sell_experience + + # 检查是否升级 + self._check_level_up(player_data) + + # 保存玩家数据 + self.save_player_data(username, player_data) + + # 获取显示名称 + display_name = crop_name + if crop_name in crop_data: + mature_name = crop_data[crop_name].get("成熟物名称") + if mature_name: + display_name = mature_name + else: + display_name = crop_data[crop_name].get("作物名称", crop_name) + + self.log('INFO', f"玩家 {username} 出售了 {sell_count} 个 {crop_name},获得 {total_income} 金币和 {sell_experience} 经验", 'SERVER') + + return self.send_data(client_id, { + "type": "action_response", + "action_type": "sell_crop", + "success": True, + "message": f"成功出售 {sell_count} 个 {display_name},获得 {total_income} 金币和 {sell_experience} 经验", + "updated_data": { + "money": player_data["money"], + "experience": player_data["experience"], + "level": player_data["level"], + "作物仓库": player_data["作物仓库"] + } + }) +#==========================作物出售处理========================== + + +#==========================小卖部管理处理========================== + def _handle_add_product_to_store(self, client_id, message): + """处理添加商品到小卖部请求""" + # 检查用户是否已登录 + logged_in, response = self._check_user_logged_in(client_id, "添加商品到小卖部", "add_product_to_store") + if not logged_in: + return self.send_data(client_id, response) + + # 获取玩家数据 + player_data, username, response = self._load_player_data_with_check(client_id, "add_product_to_store") + if not player_data: + return self.send_data(client_id, response) + + product_type = message.get("product_type", "") + product_name = message.get("product_name", "") + product_count = message.get("product_count", 1) + product_price = message.get("product_price", 0) + + # 验证参数 + if not product_type or not product_name: + return self._send_action_error(client_id, "add_product_to_store", "商品类型或名称不能为空") + + if product_count <= 0: + return self._send_action_error(client_id, "add_product_to_store", "商品数量必须大于0") + + if product_price <= 0: + return self._send_action_error(client_id, "add_product_to_store", "商品价格必须大于0") + + # 初始化小卖部数据 + if "玩家小卖部" not in player_data: + player_data["玩家小卖部"] = [] + if "小卖部格子数" not in player_data: + player_data["小卖部格子数"] = 10 + + player_store = player_data["玩家小卖部"] + max_slots = player_data["小卖部格子数"] + + # 检查小卖部格子是否已满 + if len(player_store) >= max_slots: + return self._send_action_error(client_id, "add_product_to_store", f"小卖部格子已满({len(player_store)}/{max_slots})") + + # 检查作物仓库中是否有足够的商品 + if product_type == "作物": + crop_warehouse = player_data.get("作物仓库", []) + crop_found = False + crop_index = -1 + available_count = 0 + + for i, crop_item in enumerate(crop_warehouse): + if crop_item.get("name") == product_name: + crop_found = True + crop_index = i + available_count = crop_item.get("count", 0) + break + + if not crop_found: + return self._send_action_error(client_id, "add_product_to_store", f"作物仓库中没有 {product_name}") + + if available_count < product_count: + return self._send_action_error(client_id, "add_product_to_store", f"作物数量不足,仓库中只有 {available_count} 个 {product_name}") + + # 从作物仓库中扣除商品 + crop_warehouse[crop_index]["count"] -= product_count + if crop_warehouse[crop_index]["count"] <= 0: + crop_warehouse.pop(crop_index) + + # 添加商品到小卖部 + new_product = { + "商品类型": product_type, + "商品名称": product_name, + "商品价格": product_price, + "商品数量": product_count + } + player_store.append(new_product) + + # 保存玩家数据 + self.save_player_data(username, player_data) + + self.log('INFO', f"玩家 {username} 添加商品到小卖部: {product_name} x{product_count}, 价格 {product_price}元/个", 'SERVER') + + return self.send_data(client_id, { + "type": "action_response", + "action_type": "add_product_to_store", + "success": True, + "message": f"成功添加 {product_count} 个 {product_name} 到小卖部", + "updated_data": { + "玩家小卖部": player_data["玩家小卖部"], + "作物仓库": player_data.get("作物仓库", []) + } + }) + + def _handle_remove_store_product(self, client_id, message): + """处理下架小卖部商品请求""" + # 检查用户是否已登录 + logged_in, response = self._check_user_logged_in(client_id, "下架小卖部商品", "remove_store_product") + if not logged_in: + return self.send_data(client_id, response) + + # 获取玩家数据 + player_data, username, response = self._load_player_data_with_check(client_id, "remove_store_product") + if not player_data: + return self.send_data(client_id, response) + + slot_index = message.get("slot_index", -1) + + # 验证参数 + if slot_index < 0: + return self._send_action_error(client_id, "remove_store_product", "无效的商品槽位") + + # 检查小卖部数据 + player_store = player_data.get("玩家小卖部", []) + if slot_index >= len(player_store): + return self._send_action_error(client_id, "remove_store_product", "商品槽位不存在") + + # 获取要下架的商品信息 + product_data = player_store[slot_index] + product_type = product_data.get("商品类型", "") + product_name = product_data.get("商品名称", "") + product_count = product_data.get("商品数量", 0) + + # 将商品返回到对应仓库 + if product_type == "作物": + # 返回到作物仓库 + if "作物仓库" not in player_data: + player_data["作物仓库"] = [] + + crop_warehouse = player_data["作物仓库"] + # 查找是否已有该作物 + crop_found = False + for crop_item in crop_warehouse: + if crop_item.get("name") == product_name: + crop_item["count"] += product_count + crop_found = True + break + + if not crop_found: + # 添加新的作物条目 + crop_data = self._load_crop_data() + quality = "普通" + if crop_data and product_name in crop_data: + quality = crop_data[product_name].get("品质", "普通") + + crop_warehouse.append({ + "name": product_name, + "quality": quality, + "count": product_count + }) + + # 从小卖部移除商品 + player_store.pop(slot_index) + + # 保存玩家数据 + self.save_player_data(username, player_data) + + self.log('INFO', f"玩家 {username} 下架小卖部商品: {product_name} x{product_count}", 'SERVER') + + return self.send_data(client_id, { + "type": "action_response", + "action_type": "remove_store_product", + "success": True, + "message": f"成功下架 {product_count} 个 {product_name},已返回仓库", + "updated_data": { + "玩家小卖部": player_data["玩家小卖部"], + "作物仓库": player_data.get("作物仓库", []) + } + }) + + def _handle_buy_store_product(self, client_id, message): + """处理购买小卖部商品请求""" + # 检查用户是否已登录 + logged_in, response = self._check_user_logged_in(client_id, "购买小卖部商品", "buy_store_product") + if not logged_in: + return self.send_data(client_id, response) + + # 获取买家数据 + buyer_data, buyer_username, response = self._load_player_data_with_check(client_id, "buy_store_product") + if not buyer_data: + return self.send_data(client_id, response) + + seller_username = message.get("seller_username", "") + slot_index = message.get("slot_index", -1) + product_name = message.get("product_name", "") + unit_price = message.get("unit_price", 0) + quantity = message.get("quantity", 1) + + # 验证参数 + if not seller_username: + return self._send_action_error(client_id, "buy_store_product", "卖家用户名不能为空") + + if slot_index < 0: + return self._send_action_error(client_id, "buy_store_product", "无效的商品槽位") + + if quantity <= 0: + return self._send_action_error(client_id, "buy_store_product", "购买数量必须大于0") + + # 检查是否是自己购买自己的商品 + if buyer_username == seller_username: + return self._send_action_error(client_id, "buy_store_product", "不能购买自己的商品") + + # 加载卖家数据 + seller_data = self.load_player_data(seller_username) + if not seller_data: + return self._send_action_error(client_id, "buy_store_product", f"卖家 {seller_username} 不存在") + + # 检查卖家小卖部 + seller_store = seller_data.get("玩家小卖部", []) + if slot_index >= len(seller_store): + return self._send_action_error(client_id, "buy_store_product", "商品不存在") + + product_data = seller_store[slot_index] + product_type = product_data.get("商品类型", "") + store_product_name = product_data.get("商品名称", "") + store_unit_price = product_data.get("商品价格", 0) + available_count = product_data.get("商品数量", 0) + + # 验证商品信息 + if store_product_name != product_name: + return self._send_action_error(client_id, "buy_store_product", "商品名称不匹配") + + if store_unit_price != unit_price: + return self._send_action_error(client_id, "buy_store_product", "商品价格已变更,请刷新重试") + + if available_count < quantity: + return self._send_action_error(client_id, "buy_store_product", f"商品库存不足,仅剩 {available_count} 个") + + # 计算总价 + total_cost = quantity * unit_price + + # 检查买家金钱是否足够 + if buyer_data["money"] < total_cost: + return self._send_action_error(client_id, "buy_store_product", f"金钱不足,需要 {total_cost} 元") + + # 执行交易 + buyer_data["money"] -= total_cost + seller_data["money"] += total_cost + + # 扣除卖家商品 + seller_store[slot_index]["商品数量"] -= quantity + if seller_store[slot_index]["商品数量"] <= 0: + seller_store.pop(slot_index) + + # 给买家添加商品 + if product_type == "作物": + if "作物仓库" not in buyer_data: + buyer_data["作物仓库"] = [] + + buyer_warehouse = buyer_data["作物仓库"] + # 查找是否已有该作物 + crop_found = False + for crop_item in buyer_warehouse: + if crop_item.get("name") == product_name: + crop_item["count"] += quantity + crop_found = True + break + + if not crop_found: + # 添加新的作物条目 + crop_data = self._load_crop_data() + quality = "普通" + if crop_data and product_name in crop_data: + quality = crop_data[product_name].get("品质", "普通") + + buyer_warehouse.append({ + "name": product_name, + "quality": quality, + "count": quantity + }) + + # 保存两个玩家的数据 + self.save_player_data(buyer_username, buyer_data) + self.save_player_data(seller_username, seller_data) + + self.log('INFO', f"玩家 {buyer_username} 从 {seller_username} 的小卖部购买了 {quantity} 个 {product_name},花费 {total_cost} 元", 'SERVER') + + return self.send_data(client_id, { + "type": "action_response", + "action_type": "buy_store_product", + "success": True, + "message": f"成功购买 {quantity} 个 {product_name},花费 {total_cost} 元", + "updated_data": { + "money": buyer_data["money"], + "作物仓库": buyer_data.get("作物仓库", []) + } + }) + + def _handle_buy_store_booth(self, client_id, message): + """处理购买小卖部格子请求""" + # 检查用户是否已登录 + logged_in, response = self._check_user_logged_in(client_id, "购买小卖部格子", "buy_store_booth") + if not logged_in: + return self.send_data(client_id, response) + + # 获取玩家数据 + player_data, username, response = self._load_player_data_with_check(client_id, "buy_store_booth") + if not player_data: + return self.send_data(client_id, response) + + cost = message.get("cost", 0) + + # 验证参数 + if cost <= 0: + return self._send_action_error(client_id, "buy_store_booth", "无效的购买费用") + + # 初始化小卖部数据 + if "小卖部格子数" not in player_data: + player_data["小卖部格子数"] = 10 + + current_slots = player_data["小卖部格子数"] + + # 检查是否已达上限 + if current_slots >= 40: + return self._send_action_error(client_id, "buy_store_booth", "小卖部格子数已达上限(40)") + + # 验证费用 + expected_cost = 1000 + (current_slots - 10) * 500 + if cost != expected_cost: + return self._send_action_error(client_id, "buy_store_booth", f"费用不正确,应为 {expected_cost} 元") + + # 检查玩家金钱是否足够 + if player_data["money"] < cost: + return self._send_action_error(client_id, "buy_store_booth", f"金钱不足,需要 {cost} 元") + + # 执行购买 + player_data["money"] -= cost + player_data["小卖部格子数"] += 1 + + # 保存玩家数据 + self.save_player_data(username, player_data) + + self.log('INFO', f"玩家 {username} 购买小卖部格子,花费 {cost} 元,格子数:{current_slots} -> {player_data['小卖部格子数']}", 'SERVER') + + return self.send_data(client_id, { + "type": "action_response", + "action_type": "buy_store_booth", + "success": True, + "message": f"成功购买格子,花费 {cost} 元,当前格子数:{player_data['小卖部格子数']}", + "updated_data": { + "money": player_data["money"], + "小卖部格子数": player_data["小卖部格子数"] + } + }) +#==========================小卖部管理处理========================== + + # 控制台命令系统 class ConsoleCommands: """控制台命令处理类""" @@ -8516,6 +9127,7 @@ class ConsoleCommands: "lsplayer": self.cmd_list_players, "playerinfo": self.cmd_player_info, "resetland": self.cmd_reset_land, + "weather": self.cmd_weather, "help": self.cmd_help, "stop": self.cmd_stop, "save": self.cmd_save_all, @@ -8790,6 +9402,51 @@ class ConsoleCommands: else: print("❌ 初始化模板中没有找到土地数据") + def cmd_weather(self, args): + """天气控制命令: /weather <天气类型>""" + if len(args) != 1: + print("❌ 用法: /weather <天气类型>") + print(" 可用天气: clear, rain, snow, cherry, gardenia, willow") + return + + weather_type = args[0].lower() + + # 定义可用的天气类型映射 + weather_map = { + "clear": "晴天", + "rain": "下雨", + "snow": "下雪", + "cherry": "樱花雨", + "gardenia": "栀子花雨", + "willow": "柳叶雨", + "stop": "停止天气" + } + + if weather_type not in weather_map: + print("❌ 无效的天气类型") + print(" 可用天气: clear, rain, snow, cherry, gardenia, willow, stop") + return + + # 广播天气变更消息给所有在线客户端 + weather_message = { + "type": "weather_change", + "weather_type": weather_type, + "weather_name": weather_map[weather_type] + } + + # 发送给所有连接的客户端 + for client_id in list(self.server.clients.keys()): + try: + self.server.send_data(client_id, weather_message) + except Exception as e: + print(f"⚠️ 向客户端 {client_id} 发送天气消息失败: {str(e)}") + + print(f"🌤️ 已将天气切换为: {weather_map[weather_type]}") + if len(self.server.clients) > 0: + print(f" 已通知 {len(self.server.clients)} 个在线客户端") + else: + print(" 当前无在线客户端") + def cmd_help(self, args): """显示帮助信息""" print("🌱 萌芽农场服务器控制台命令帮助") @@ -8803,6 +9460,10 @@ class ConsoleCommands: print(" /playerinfoTUqyM@IJO;wHYSyJpzcE*tqv%E^G66f8t!LYiFuwk&_06o6rs{|5caB_ke`S2>V|&ZBnWCtA%bUB)+10UNF02a#km5*y zMgXXKTbWQsZYsF)m8 ;o0+x&iAINYX3?(oH@ Wf#t zW5+3`)i0kfCKv8UG7D&-MhHO4*UQbyE1 E$wMJ(*aUo5m$O^AI9HY6v zQy*QtA_1HRwL?p|H1L6^uyyVT5c${^765Fq@tvEu`T>BrkdITXs=}MuOu~4ybT~db zzuOsNWsO217%ps8`<* y{~omtL^aUj}0g1$6-892^oLrmEAwf-J2IjrA&iOTuRK2bedW _a9Hg>U6rrL0y>8+)9WPnx-t#I zJUFfVY~b?Oe}FGP?&Dhv)v|AZYG)t-C=#et1yEKQX7j8lA_>7ozG-zFlOR=0W{2aP zY>Uy=(aG_dj-3sdcehjbgunO0lcU-;Gu1c%bZJ?p!;UdVVWMa@Guda4x)-m+;+;UP zaVi1;;t-jHP_&nx{gk`2Rp_iBj?Mrlk`)%sZB+`D;o$YFVYkkzYPgPrFq>(;@$%mG zkE}3Bg96%%1&twnMvk(rO2M|G?zTz1#Vknxj)Y=L1py*j`hnhZRn$kHzw8#qGMiab zC3St}SgcS1jx$dHbW2}5?!51bjs%f*!~w*tNr=EeTaYwSii@I13+uBR?>ipljV{<`&*)@TvkZ#NGb!jlaNnXD|+I4FEBn3O*o+NJ>|mtOb?{T$+~Aer=7(GKC{% zd8L9#&yKdXdUc&yI_Cu9#1)Tz;@#tq9!Bj=qZ&id$pZkA-rCvL#R4rTneIH*HJeFR zWSOKnF&8THrLT3*|IlK6dvRrZ6bAyNKeAh!2e%@bIhzc(#w%k!Ao3@_d$>6?Q&B;t zFFaE$*IDoG+a2ABF#D@Dw9(*T;rON9;oTqH>wo>QiKeTas!l>U1 *Die}mj?pV zzA7E3QUc6lXS_{@pS(O?9lN=}FRu;8qwd%uORC9Ub$B5n02jUf)SVyO6v2!u>o<1d z6^Bz=0T7@=o2mqbPE~EaWuizp2f%Os|63~`JDXEsy)|Iu8ki4WTe|%joRaS5D6ADT zqX1h`Yb@v-(WbG+s>?|Qphi51xlqk;?cL<$$ =R;=a%08}V-Zh-aPn|eDZ2n7^u0*VVss($7m@d$;{83EzY zs;xfp7u))Me91&@Xm uy^jpBj;kTmVy?ODuv$vK4nD9+Pil8F~5 zJoR9QafI2BU=t7o)R9Jv&U&_f^;vV_D~FNNcFr7c$EiF3;E35)VbrJ%nsWBbI(H|N z>-R$uY4x4Ae=T1qd5S0iHc`m IJBs= zWJ5As?=7t6)22`uH*+@WoOyTf;gf(V>UUmER}YWNz&U{8NE)vq<(sREm2ILRpca6D zQ#qYVK@|l6lRfmAgXi9>(^r)wW?qYDAKxy&xGHR-n%oH?iX1TqQlheZJX~iE PqWhMd!*U~52iP*XerwbrV&QQ?TWk?G+1C*9ql zdu=?7I?Eo-odO_@ha;P*T7w`UplVgDDxkJdP%3N5>7fHLoffV&hVOW7dG~gufJy4a zkp4=9Ig!q#Prmu?dhKQBqX)gk%1%KvD#x`Y8m%>I3%FPSD2(-M3<7J4;(B;uzm5Z@ zS3yjt3Y0BLIl6MzO+27x|K;;zQ=J{i96)iwrRA0Vj#riO@k*5$onmTBqFGhkwF{9; zg#dg~bnag6jLTxZc=MY!KY#b4PLD#TMb)Z9%XBVHjnRh1?~$!^wHt9Rh~n;rla=M8 z0szGkisn`Ul7J~hC zM4OCRWt|y>-TJR$1Nu~zWDGr1~(qvD=enhIKi|ONhp=A z03v8^5_T*jN6h||^pS6@Bpb`M1pr#>*vy*DFpsFV=uz^>5irY7e{1R4{hJPes+qjZ z$Kp$6ymYC*$ki9KT&71I*qQ O7dWpAau0*3+TOGan z+S$}vU!Qy9-YW+=p|<9YTcfn2AYlCd)~!e0yAbK(?s{d>g>#%1qD^Z65fWo1G0k)* zmpWNh6h&dISDmFu5f}1uq(_NjYC3IDTNK%6Mz`)s(K)(P=$#jyiX32>oxA(o_D1Re zwd?wUXO5S5t6r5ERVB#jPzAx3%w+3$oI5Z8( LZlNH%JK2S zGu?9&gH{yEhOIOHu_a7=^5vJq)deSm6jXTK_$0r$5DP&h2OnCT)P=DM03@c<8e_l^ z$f?w n>_uUFL(bqSGy~goPzbyeZkC=B6Jev#8zfiXPI>S%_qa&`SoGqt)fjMe(fYz zMtOiW))t;N2dPj1Vd25v#*Ly70Z5@LW~%zc4_-d}$ahKNBG-(v{r~h!nYl(n=yFE2 z_f~Z_k?55Ncekp{D5fz5mof{GB%sD*$tozpcvUbQd~3A(V|!Uj0L0X40g}_8wm$#v zRwh~R_Jbp36S#wi5l#^RWc(>Tvso3!Se-o>tr%+*(@Mfo70p$xEq2nvn}cD`0+ew+ z8ebl|0z|9UBpy+4B5f$F&yW?yyt`A@nA(jk$x{Yyy!7d-Pv2gylFS1-I}oA*#dH%2 zC;?T4sw{jfdEX69QV#$^=L#{yl*$&=R7c-)xHK5Y5!*1P+FG;`%Xsr3Z-H<)l_lhZ zn;+hpES}r#!K%&*%{gsQs}v z&>UU=Nvnf#6o^)}=oQg8l_``^&IkaCgy5ouc$QEo6eOn!%!kQpVodeW+UTRk)l`-N z{mnC mMtz8BtGDjU?Q(EyQ|0SG$K zadv=aLrCOAgwq4`BX7*|-8|-nD1@k7y&3=18wanhVPWT>6sKsH02D226ZpzhwF{1D zQ;Ur&zAHqdt$PX5cI#kH5NUg?f=Cys0n99sixvh{ GXUm;I>Hv_X8fP^A{$<(DMZwnKBKkdX!vi!p>D#2I zm~n-wRh=cE#+bV9+<1E}-+AWxU@UA68Z0$zUzAQ#)RUDc*dTSpG!I49@$DJk+3)9K ztIvP#bI)%BP(W0MlTma j~Wq6}3;K=Kbg7#-bBbY>LB8tW(3tO2OC#wXR* zox8hBKYSe8VBc6!B1fyLA%S|;@rQ2>`pM+QiD^qsbvs{iwAJPR>$yLjQ7AxCqLthU zlz>%Lwxkxiy?;&)(}wtd>Uy=oOschosjKy)pVG6l2LQF!`>g7nJG 5MY8wqvH|RH96;tE2?12=F40<+nXPN*AA7GO zz$iFa#Z0Qrs@~?EbCcucSq4y8Yl@=sFo@!D?wWvT)tYQ_w_m@p@a9QghkoKMpj1ww zYE5=K?WcsQ&5Sott&9Q!C7MeNr2?GFygIqK_td?KRe~Tf! G4H3+`>p@9R4Hr4iS?C!pHawSTIW3{SKg%uSxE3%|s-We0E+E^6;h^Yzn1`tOO zjpgO&53}(0et}a`HGAXLn+M%IP%$f;-s2l1)zrUt8vsPe*7o1OaQ$j}bNhS-rwYLO zTC-8)n%L~^mpZq$0|1atM4)Expvjb~){CuslW04qKnRA{p4{Kx>s5)&l)|ct^~*tm z5VN(-vdjuiU;&Ger;)Uv)avBqn@=yjusbnmR5imJy}`|qut{?5TDe^cKyfnY4+QGh zGgUSy0EN%?kL$(vpSk`4D>?`QMrBu??ps)$A+?z=ES+|Xh?u=qtpaL~K4O!ehD)!E zTdK>$FO`$ciOak;Sk02o%XhDTW80xov)V>c>Y#|?o%>I%IA~G9S_eR_T8LJAgL?7L zcl)IOeG$eNoq!^=X=V*MQ^n-WI>I33VT4&;0kif)YpyHh!8gVPND=@<`UiF5t)i*g zVRhr3SC+c{RM-I22YG5TuPSHX{KDbL8iY|QM5C_z5;6VlTgwk_U(Gw`&j;otK#m9A z5;LiQYVp?Ry)izzI&iZcpr#&H>O@mIg@h2C0L(2nfu{KH0l;v9D?f5|DKJ-Xq;R z=R1WjI@j+X2a?g*Akv^3f^(G3ZgsUA1!_niN}@mj6hLP{tyg1-Y@G^|<+ zR+RjH7p1-=V_YRzM0Hl2GwZ-b*XqZHAzx6~E1wczB0Vtq)OhI_v zBr NGBrM8gh`RB$0K+w7p_Lb}RPwKsMFLib( z0LyKs2-{b3e>0ph0xBUyqcN`QNTg~(ApuIXl`3XfLACY!KAEUWbausIj@6>qs}&IR zb3jC#uoYOj|24C_H_4LWz7s&{C_*}fQmhjIP!LTeLIe;31wa6jesQz1Gh?h;OPjqE zymc-uBbjroRcoPs(W`>vm2TP^+;JC~w+2CKt3YPPB#0miB|t^uA|(I{R8;_gXwEW+ z(HXI(x1WeOMK@JYh`Cq+X!h0*RSL Z2x06 ^c@>}zlIjc0OUYXAX&Xj#1})G|r~TL3fyICHAS-usUR+t~=K z5f$@BTb+BA1CBU)EvJ%XE7NI<+k*{b7nVvl6#!z!RTa?Ut+%nYoa^dXFpt!tP6|5F z$T$T<2#zzL91eTuZ=75&6sNM ZIdKUN!9A@_Z1Ez{eHkaSzMmx zg8bs2;u`}8p^(#YN~$U>eeup>T@a&wF7qXTLNdMgr}}|oVN{l>dmyM9YrR*CLNZa6 zGzjL?1fqj)Jyr_K=O64xJmsu4KC7zreZv}8`+=B`yRsXn Eh&4=3l( zZ6raoyK+zp=H(O-f$M(Z&vt(diK58yR8g30@BNqc=7sH;ba@cY&l(AiFaE*48PWBx zZ^sUtF;!!XDQtah_sa1&btEz0XEZooNf8b{@PD5~pa?mAYK ~}f$^Y+C90Y7ftxtygYk!H)ZY3~9;e+7xQ$&Q!m;PiA zZ;wk6VJoVp=p8-!=$D73v1--gw3UeZk34tv%4>Nf5U`0_lXW& 8z7UT5SMR9k~ zeeho2IVU8j@O5wf++Xg?!(#*G=?_GlO9zi0VQ?IRP&KR9zx+dwYpNtpmnmWY#?xn( z$J-@LJ=uNrlZi%DoKDjxy{&hzJ)QK2Pk;HHy9Xdnx7FC9?ycQ^{M_OY0mSLJTAvLk z9cuyNG+nK+>hT``zsLXYsUrYZP&goZH~;`Jk^r3nDgXfh0X`WDgh8PN66rbw0AVbx z-;a|jfvMQpTRdm=U)zsk8k_bH#edpAZ+<#`0CsiE-c7z;`6v57@PFOEU;b%-fAwqm zr`Rv_%l-HF|Lh;L{}A7|KJGtf{~5jtezShtd!_$({jc|r`pN7i{a5 u&0{}uYh^{?$;u)o~@^na!JGyFf} z@8##Qf02HD{ipI{;3M%* ne8|54)K^Z)O^?LX4~ z*Z;Bm1pY_-tNW++fAhcKzrcUu`%(SN{r~rG+t2d9`+fa>ZT)ln|MrjEAM(HdKLUS_ z|0Vv_{crr2`Cslo{lC=x)_V{AoAx;UMfv|PZs^5yV!AP17_N+0Mk}Kg(TeEBZ^3Wf zl97v^(hMu370{JihV?XzP)Wbo{T1D9T(+pTp5Tfj#6@;^V Hq|&3bKAC~F4 th8N}lVix uF$rW3N8pHtN=!d{E8HA2wtz1V!!C$%qg z%Ngy5t)?4BmoC$R!LY;5Qf*o-FJv-U`P9ATNx;*Oz1e%`g#aT$f|`Z$2{KgO_-0cZ zoHZoDI6V`|qYEF|dUg(WTk-p;&*g|!Do8wSDif~buY|gTk>M}=F2T}KfR5BVi_Cf8 z1e^5Eg*{|^tD?sE90@C1uF+rZDZVSxf1ff8N|9vwFNh3*MndeAo9MPuGp71fl9qGG zK!XL|>+XvEhDPQdEX7S)(fiMcGtR=?d?)QP_#&7z@+>%i_BM)9TH-GF$>uAu*Ut>p z@JOCCeb*wzx#COA?>S&Vc;SQ&eNg+1735$GPB2Zm=L42wFc98Us`Nhw)2~Zf200*Y z7Y1ztd+9Adlq&(@Ee9=fgw1#{Ze |mD3D=Y46&b0sd(MCA|gCd9w?6#M~WlG z5#osPM0lb)zySXL^{hYu);%;+euqHs+z7@nc0>PP0lY#2QT`eh2NIqpqk@CQ^o$4E zt+v$cBC8gg$Wh)rcm!bM_Br#FfDW4zqDa!c$yuWINFzx-C;XG0!L=wDTDQbBU0+ zLxw4sgugxxmv3k6_vp5{eZpjL7Ur#72BMMqIJ;f)wN%1u_lDy;zrj#E$GG)IZOlT! zPIO kS2>Ym%E*c;RMr8}oi)v9kFnqK6?(!7SXgr{D>bE~3y$2MU#jQx7nmHgJ&%FHZ; zeR=Lj(~nJg*Xdn{ u?&UqKu-hsX|sJhn*mmUX^POH8Dy5*N9B?lobp2QAX zIv+p4ni 2vXkB>7=Gh4*ER%YH{Z=k#Yv_}k!wTMKlGwFAdg1M9Ygza z7o#l8z#06URb| 8{_mc6&D>fXEN% K>29l>Wt zmb+ZAS>2}2xUMaz !fHZltt=;&Rx_<&*z9$y#e8~X+yhnsXCk}kdZayWSn z<}Q 1}MlD%n|-Axc-ZS^sn8j z`3`Ct31u_8w?BdF*3T0O5V_g!Qe4fIdHRDty+dw75!Bm6lcaUMd=!o%yQw@rY|6H( zlGHW1LYj3BMDMTQNl0Vwl85ahY*n@GX4V!o5`fwNaU6}CBdim;mYa> _zwf z=-XEwGX;#nu%5*40qZ=r1>EZ9C6lUXFPP{8uSA@Lufcen{C}hOIJ5CG7eKIAjo;9F zt}$~Tf=~HW&Q*xCDr~Q6I^M-Q%M!LC9M6$yMC^u;#<(vuC2R>+-osZUE+uG^`7dVz zhW(@MCc| TKj)t)8!&u;6TJR+JD&;V;v+lM_y}E$q`yloONRt3Y7=emVOsnsm@` z=AQNrfBJ9CDbL(Mn5lm$<3$!{j_oZHdoTvl8e$2+s{27~xVu_o3K*503s;*4(-qXt zJ|Ow>jup%xS{9MwWq1F?Xz7kQL;Y>c+$?$@bX7HZA^=-45M$Frk{qK~>{l-DM*WyI z+x1|CB50F@`9#H`mum?G#=LA@u&3N{xC?7A{(6D()hmIl8|?ROis+`8p+ICn&a|lg z#-~PMWncgOXaH(0o6#5f;I%hT@@D)_Ql`L&p8SX}O|iZtfzW|XZt|_@VS0cyy^<;; z|Ga{svYIFqd@G3m?ucm4Jqn7EpQJ6r2t4gcv~O>mReQa7hM#~u0wB J &Ep z8hKiM{vebg20J0xjY~N-9-a-a#u*@xBy5i(d2TlFo@8_VZoq`9$8)Hwk4EtA6i+=; zE6&|Te9mIe&s6BL+UW>>+D_sj*FRv86z9&;BPL3g5D}Cq3Koh8CxI1;z!y=+M0CSJL7NmURWnRyEN0MQ)$s7bsC!oPEDmKU0`ngA?Tlm7Hs!ESZ@SY{>VbE0& zBmwmCd8u#Fg1qDpN gOK QcZiY-wUfNkY0chq#02I-Em=!S*7dk6^}=?E%rrW zEZ@9F+e)r*NHQ2X&rVVFKBevE$yQA)Ot84!)V2Nnz#kVE)(m03;mtmPr6QZ}lxqPx zVEEfZ$Ei4hLT={U$MSHeZYJu+PFN<*5seskOtJGs5X^W+{vPBS3g<-UQfrqx@g0)H zkOEypbTcBQ3P|8`KAeaZL51V+GLzn?NcswcNO3>~aS@C;Jw*53L3G>54RB&^z)*R5 z%J$C8UUSc%q|3X7A;00hf7E(v0XrCgYMYgw;?0m|Ksi5$b5~YM3k~jhcOqLW<6l73 z=@z8WMGA0W=+&XFLH;jTNC!7oZ)kpvp(x_b*qx1=Sj~2|x-OY{#V9Fv5RAnhMOdg? zkfHkRy83$^M}WFs dKTI>&@VKb^6#9cWz9WbJCj)c%Y)qTIy+-ByPR8&i3#lIZ7 z(DyUS0hq7|1AnziNNmMfiS<~83r|gi!{ha>^7Gzd5MFdvl9qz&SxDtSuk@en!mpiN z4ntJjXfHr>S8^8hJ%Zit5-I8zFaX&46dG_DEpxxHHQeATX-ip;@$UK}(uHax`%kw% z+}?jBnzvkdhw(Jqe%wggpGB8o-qA2+5>4wd2va|lq(Tcu^x9AOcw(Ng%RZdQu^^9d2u}!6;>134_U|h4%^-G(2?s2lF{^vdo+K@EZeoz~7gsKPUSAH$Rd; zT6E4qszwScXE7E#j)q*$&7Baa4G#TDE=!puDgAp@%7b-J@-aFbh)F)*)QT0(Nq()f ztNjrvE*Jl2S8<9DlwOtQJ}x7><%wa|b3d|qN}(=mACp|zw`_R5??<4iaoibB)1wZS zYs{v_;XILWIwHS W3dZdR>e=YOt(1h6w_dTnK4CUF-PFb!@X3C1TVuxo026?4(fbQ30 zOC(pgLZ%`sXRBf>8ukz$P|V&~H2Ex;xj<*iceX5BTqXu!zuqVlpf2feFjnFHsd}ux z o(!%nE80_iZ>ZLWH}0-KHyweiJPc1LdaWAs z@BnFRgriJ#-jQ9rYG?Hnf0N$50u4PC1u?LQ)Yx4 gQ3mQ!1q_ls!e;^U=r2NGW~@;d=OjC8Gul#fN~6+r-3UFgvZz8wq@C zuL!ZI#B!ow&o;}yl;;V})wLcUndQLxeH1SNPf61%4AZ^buoG? zzZouG+4(EFi9eZbzEy0`Jt!F19P4NYs(X+yjF1-3D9r?&Pi$`&QA&Szic?0_e}xjb z1xyCc;YRO1u>YtR45ERe^BiTyLryi8g$0X!!S~8K8sxe4XAJp=&HLaQ=U1c17Z;GJ zi(zU%9&-W&UCBNDoRRkvq%14G9RZa(isOFKrliM_7dzd|pJ{?Pg>CxABrbQ+w7L^E zZ+X)bN1ddIH$w1Jbp>>19mRPUd_$4lhj}T^xT)AHH*)Yj4HWfBo?aIJMaZ13I Qj?d5FsG)Uz_j`NmWod)r2Oi+Hk>FjCED4Xz*Nua z@h8SvU6RQ{ljJPB+siC($uiJr4LO5;ri4q9Usmj;8*(Gqx<38U0s!je_{l*vdz!Z5 zn3@}C>%sZQf@;$33CpUuCJ@1#^Pp3d*V!WJ5*xi5q_7*LP%*A5N=-j2Y)BLTI5Ea+ zpX5=KddUcxy~>7cGFOWr2WCt8xTT74$;F^_e3jXZAx;IOA3T`TL-m0R3p!gOJasys z5L^}fDR+>U%V<;%rBWc$Xa7=4&2eTUfe^(DA8ZYM`3jL>hI&P5IK;G=KFTgH9wUNK zlB2MSU6Ev7_CBV58-_={q78!Ov}ho~AmPI^ZqpeblfxBTyV2T2R{kYs>?L7Cn%=#m zRVWxZ#|jU@e6I*VUv_ihl%C}iZ)*+$yHYwDsr$q3oCZ0ns?_An-O} Ge?ZD$Blwa$Q@>I*jd7IKbRnL>E C|GNLIm>T z#YFAjOK?T>szxhu!~@fvjl)Dj{b=&WIW!BAaDL;)us0O3FI|ZgmGFD5? jkwGcwo^fXLgr2a |ZWNi(Du;eykdZw#Z(8Jc%qq{;r79D@627fo IfJOEhY4O;94ADNb2754YI_DIdcvZ&M`mQ=-VI1{PQL@ zsYIz~L$EJVXlbg_Lf0;Db4d5Au`dz)_*%X^a-q# A#qfulU_e z@3jqask8Mp__N6Yr$1rnpf@#%tJysCL-naq{K|fb+S7nXCq@CX>`i4W)Or2N(WuxQ ziw|v2F%q#*gAIsLbWb@Z;9q^UHI@QS=z+pz$yu>Br` &kN5mHBYHMnTAy4_j}(NeKQt7iF{0;CL3n>|e7KL;yB+83 z%Yow}EF{(ym8Qzpn0F(9g52lc6g5hB`h#V_Zrxk}Z6)LW2(o0ZM+sc{dpSB9Zl zG09&4xQy}?=tu>}LP_tc1 o~Z);goSCeohkw;H};ZV~!j(wCv2sz2sC5a1LxpFw%r zDw=7%xQHQH5k|J-KlM&q@doKp(#ZY-)Y)&c3wjiXx-{|jkzDax2F;~{A{ZX&;hmKl z)W)fNquhA9u6jlA8;(F0f}b3?pqD>iYGBl~!#-Rz=9;66XBx4Y+2CpE^11@tGiKkk zeQ*__Tdh3~ZSvR#l^xusIB)&?-uObFA5bL(XXDeQiD4QiBzt6mW3729l=1G%?*gitw?^JPm3+kX>`11KFl8=WSqQj zl_tp&J+ x~2gh`nv-!3GigfE?81lGG_RqFMMl3G-${ zfDVX)bzQBd)A>D0hXN!-U5;8xzGCH;Nkl5+0)Pjfq<2*WcEzj(n2>YSKo;PtwUF3& zhm=ERk(5Um@lroIOw-MzfW+q7p#f$^GcW{{(e^;{M&`(oFP4~|(42r2e4C>0v@6fJ zes<2KM&lT?2!)UHNdZ(ItKr|OhZlLvr(nnN8Vo +r z4tb^ |-v-2u6 zDSU` m!R@wr)hK ?LKcy(GD;R^WMIsS^`x9l65g>HOx)`F)DFYx`5{C=0GGs$*K@d1BPD%p$K 0_XQJkzA|~-=VeRUpD5U9ChE^A`O-R+Q|%& zGl)=5gj@a2na(bj)oQVoQ&(RXAo5`S&VaEynlcbPWn+EhUk#-`exTVrdS4Y3`ms zwWCG1Il%B#UY1 (b zy7UO1->gMtgRrn~F zC_H>?~RMXptU0Yop?CSUpU N~Wt5S$#_nR1$0VGC^3y_eZM_(?z0~aNB;48CN?ywKb^J*hB&JPUxTe zoAjr13Ei~!O196J*)3=nn2y~Q-Uh4j+|;6nP3;=)!P5tYj7no~Bj8eT+%O5hBKf#f z<`D6U2%sUEtEFAh85tEvdA_wc2Tg)6-O)IA<@SF&8gQ)56Q7Rq(Qjf!8?JtG+28KR z=cX(5A; ==$-+4*bKHvZTXT0n@)-MeVf zzSHy38B#k^h!^BZG;Lfpd(Wb0S(e }{PFxXq z5&vpUJbQj=He0t-e+gfgB)ks)yV|Qz;t01qDGam_ZOwG$HMuU^R44wcRhh44POrn_ zP3BA&ZVgQmVw1b(us-BIIJPU)g%Q9Cl^bOu2)UXP!3~$$WvQrgy=Db{nNYf*mp$Wh zp3jCz!WTzxtTn1>hMH5j6AH6^GtX)MJuXLF&7f(5y7x?kut~SxYuNu8i+4)o%_J^# zW6`4rsMrx_dNOho*U9f)n=%56r7YT~Ptl|JM^048*YNk7+^dg^Ts4%RqrxbHA%`PI z7g^)!c@lH6GIJbD`5~bolq3dm*OJYcL@xB~(&gGg1qUGX0}p{oSckDC=y3scF`sX_ zyrp|LQ<%(x`I(PlA tpeMO`6A4U8m2r0q2?9Z$4;(98 Xie1&H()kSpys1!kwmPgkm*!yy8F$CG{L3dSOL1wHAjU#G6c)Enmg*76=<-6HPMK)<(OXTy>Yb2M-k94W9*!~u+6Uk(0T0i z+6bNmMHD4vsna-)t;)VK+~4-vLsA0czdCU1i#)?d0IaJ_H#Rh-c%NAhC*m_*!8H7! z1ww*eZj?QY6~c9nb{=_#oWArOpi5l;L1wGFQUT!uy5b xb(E!Av59#fj9 zPtd8uHwkR-b#YBB&b1(D1l|B?*~Y*Xw&3ZlCy*hdC!lLfN7~+p!^UMYm+~&TIg)<% zRQ{v}EEkE%?by_%rlGA}SHO4|$zfc8UpP_hCtyTH (bMy(t7?rP81Pny!!dRXD`FfDd zHD=0A{7*jwiIor0%f5qwo)T+xs2Uzm)h71*^wXy9!dWi3K5)JnsCLD@Mg{d{h;S9R z&qByP8%S V5D)!}j*SJLVM&gfy#0vx&?)SS- znkOUeynNIi!b)4-NWo{T->i0~QD*X({I+U{g|eU+(Zsa5ay;%WtcJ6ngX6ap*Khbw z9_z{|D@o!TiQ8du{)N;7e7Jn#9GFFP#U(h=Kskh^QxG~2+fcRQE>=xzSn76iJb%3l zeZA1d7>sv?1uL7h2$T*%7ivYwzSGR$`6VdG4=WsaGkT`tys~*{WVWfdIUOK9aeVso z<0N^rW~%DycGF%&o>wX4M5FEoy{RqZA9zH!QpTGK$1bu2zXtD{24@txNy<~=;)2NP z(!v%aE$fyd>A<1fG0=;Y6)3m6(R2@b9ww5JZ}pTBvlQsp6?gc7VQ2O21sLzcr&bxK zo`9vv#fd#SIk-x#JoLV}t^!pkIwjqTR}N_%z;PEXV9Xxp%CHZ+8Y;!GNo55>&=_L( zq;P~VYD@{_aBOiQF%yZ2ZDi`i3Ci M(Uf4UD-W zPmcfw@1*>@oyi|>#|U(Dq HcLaXrknJ70Ye48dKlrn%ORczG=Z%RJNM!lt z&u _RA70xl}9jL{o+n-}+S>6(Bzj1$L*sC`bju0d36F1<2fpB35#W^;P zaI &T0%S$6 zIx+0bwU55SAmGLJElW_}=}gPRfZs}VIv+6H3N8#+;1paFEHZw $s6iHf{HtWdc5Y-=2JBSV5ooHP52onb_H6RqtXRX$%9A^WVdO`2O4s!K zM1OcYdM9=%Alu^az=@k4Er0g%GqMB<9!1|fJwDaD#{zT+0DB5@QF25T(!x^FdE`^V zh%}U#j2Udr(B9tQ`;JY7Wu_c^R05kOVFSE6p1dbMjR0_lrqLSkS`#b_si 0D`a# z2X1kw=VH(Xmd&jNT7y&MR%f09EMOAD;ad>e{Cb!UooPMSqjLip@qDH~qp=FLz?Da3BA+gTMHL=FE>X%p#) zV&q_geLrNyj0BFh+8Xnb@u&{!MVY@hA!vk@jqs)CU 8*-r535Yfq?n Dqh2rdX`#u zt{l;j$@?Z|&!y-QwiHti)BxtpgSZ^}XR`WeUMrFU{ZlFsMP HKC{pzy8Zh`tkjFck!h4VTt;F~y&@n*|#0`YtEJlyc_> zrf~~UPpzD7#+o;uCSS}X%eQt i#AR z9vZE3tvSg7xJBW}ODF(3k@y{Vit`R? eraI5~{6F)B3=5LPRb8Ht`D3ig(-hSvvA+z4 |3??KeEJ#8hnNZwQr!exd;auXW*86H>##X6Oh==+8HW4#G8|>aq~cgL zem#8`aa2rI8+DV>xI)x oAQcmilO057%g_tI7m^&V@!^(xZ)7qQgEP?|>^Y!Ei zgZw;20%eoeG6k|nGcq8o|L*mb_=c=jINUp48c%O_0Iq;`oXaF`ErNO_&Z5e{qdHc_ zsr?9ud^m)zdqloH@CHJ(qTE1)5-|o(CVnly3qNF7{57stlS7yiA$wRa#~V!jm-+j~ zzpK)zM-l?-vakbOI8ubH3smpK^sx_6q~0=NqQi+;QPj9Oy)cj(^#Er;P@m 0nD#SH%4mbYC|xJ+o_4j3LRtZ%SNpWB|f{(h!rG!4+SWF< OYvp3=7p&Y9kYcOV1@}ZB+k4sGK~v@)pS?0N5%^3U=OWq;qV5Z%u7@k*UqHxl6@5>E8)wK~AI!)l4*I zSaN*~zXE_BpU`U})R*TOvJ03r0JDLN4>tjPP=%>1wSoPD{$w~l-%M7W&=M$4A+2ca z6#E-Kpn5l~4TCUR{Y@?49U`o;m!pp76at*rgFyWtj;BDS$%{|=`m41SAck~)jk{`* z311XEsT5LWk~poS3P7& ;Fud`9?}S#4VgQi^L4$eBfaYRS$f;ek)3BEfdS>0*a~-&`j* zx0Krjz->>+w1E6q3jov-9N_L##es*_BdOR=uGd2xfZS9TnX^X&kF4ciKhI$jHU2G) zM6myZtrXf$y;&9OTKNkrq-*z`#h4L9;d?PuyxlW4!_ZR^#$ 4k&VzGRu>IPXjqRIOWiPSYN$(}poI0(wg+on7Qi%bV_vxMvNTsp zFn9PM{U@NUdyu>_R|Lj19k2WtD+sH;`bo!uZ^6>$V!tI#gleKilEZZJ0ugTzigye4 zL~?y4*a>K$_r0xKdodt1RRf6<$1Ot|!++w{vRTDdFG7_HRcNxM$O)v1{3CX4%H25P z=?MxLHQ>j_$IWWgJ~X+6xBo|X%0hQ^!;N~B76@b9{B@14BW~;Y9C-3ErP{d_Es6AT z`6NB74geJfK+Xt;uO7vZX=_AY&~St9nb=HI1pwsp qlMG)>LBFm`7*?~5!q6UQmt zCW9h#q5w9iQhV%*+z#o5+VC#`VFq!d{?j w}qQoTEAy#ZrveXxCHZsa#phRRly;hakWbdR)0)>_< zSq>_+x;PDF VdksRsO{!GAiu}R+mqrT_Dg9GFZ4={2LtWAYN`n1cDI9R zlclAT!=?{5q+QHuBr)Tr+O!(TC@>bam6q|Z|I#+?J37XMK(alita2wN^Z8Cv-z@mx zmhgYHu;|`R%y^ULhHJ^z-DR=(vA#md99tZ6YU5+K30{DH;m^kovXONYNf ZsBGN-PllLINqCOe`xE1rb7Ew1cpW8*Rw4 z#15s>C~wH{#AC!wWP_D^-kmE zz~I&pYWRDx`ic1wp!Yo(>3`EMK!iD`Ac;s8>UHs+&0VMf<~TaB>gGHbh!6J;S?584 zH8J!s^MyZ=dII2-*dsnH4DfdH<%(bC2aKB%-9Q3b-5>^Wq9zaCO`RyPdsj0DW=Bp7 z@y!bh4jRsX#aT4XLk_k{5(R@j3fG3}W9_=-jS18aLk|i8JEcz|i2fmODzps8j;#T; z=5bkSE*`_T%Sx#OYEg8Vbq{+Ix&awa!Rd5D+ &aZL~I>(fm`kdgCAcw9%#9w}7? z_rkmSv4z|={Wl36h>jtg9?H<-kxyuSYqxkG?4_;-cbt4D)A_$ClT(llC95ZeBQEI1 zm(xoK+YY?dphaxEo%C&O|9d6286mvGht@NhohM2W&r|oj+QpN(b(8V+_)_%jOA_$A z@zfcKnNu2VtaLu~B;qA@cKwo$R9bLBuX+(H!lKt?wNML=^|k=?_QQEI^UaBd8^7rM zz>+23Y#;gD_qr?pGH@p1#fIBlCLT&g8`c$C@fe`s%8XntJIbTppMK6gO69uXZ|a@2 z1wb;&kdq}oE522rtjvG+G1Ee+b*t7n4es^85?0W&?J# SW+SseNt&QlHDPX<>3QaG)D$5F^HdQ{$W=WAT}p*asH!d zCYwKcr8jxl#6$K2nPSJ~cML)cE$S`0G)k%1c!RNrkW{t}9I6& B6zQxmk`6X@18s>( zL&X52ll+4tgY-9mJCEnd)U#NZ+wdWnip=b$b~Z-QOBCtCu{U42Tc#U}NeRDw^h+|C z_#3#wq}+53p$)i=DyZ&aIwHCcSMi~(?yulKHy_TU$~s4jcREg?^;8^F-MB+Qor>H? z=+Iq@rkvCJUosS0ez7T#sp<~a{tyW(NDH8%HTu6enMCO|5M8xN2ruRBOmDJPP8!IX z_idIN2LaMh#`epLv}R4q&ag~m^dBDwbLQAm+8By?g0Gzd&G6HQsM+-!)^ZR%fB>br z0Shkf;a+ltM$V=F<^ySE$kb6!RT>c{C2g@lY?eZTg=yL1?~qk7a-~RR)yyup EiNJV32UE#?&UDNzXkFgPS zJsCy?TJyHmU@!kq3`N4^;`vp2U`uOzhv=pB+7A&CgYS>;akHJzbn4KXM8 @w2zeCWxuCg_=P)fUMho{8ivf%fT@Ccz^sA% zIZArKrCnzJCJ26_U?9F?61h4>-~7W(O>M9yY`tH%h%iOq)+0(=<>IejI^(B#>SCg2 zy-Kfe7qFm_M9|`ELDw4a;nWN5zV5^vO<3X-EvL77mGprq`p3M^0YVY$zuiCw#p+HQ ztnD!+X2;^rf=5i4;2d(1()5&R2t@g;2vJo#^#|Q~O#^;_4eXDe>=@kOnqu@lcl(@q zT;hhq^ATJUg~;*ZEF>kX`K650kLbnKFvh|Lfw|Z_67~c!i*@#=YQ^t`eCUT0V6BVr zF#L${@dYcT(JcTXHD$1_;86V3fKCzO?qNEa;u00iFKs`^+K;)c7%?1k_)}rdKn;1a za6Qt{Vom~2xWJ8+eoorYHO_%J6-|(i`_KO@2mllS`M5isPZFc*ET`$&KfiNdm#sDP zU}TyX0A+fuQ0mrETQspsK7)Au&+ehJqL?R%K*s}iqr;Um2MjmIIjF?QF8N~@uf)^} zwnpfre1_Y2%SSz@Q_FkeFexg?0DpVJd`Cvq6_FHFxfBVWqN=NzG@Tc2?v(+6F7u|c zohe&S*B`G3FR+lilga{tC;5Ft7sB7%L+Yx3a6w-*SNS^6I+Tp`#yMeHpwZYV7h)BO zYt~HF@MkY}dTJf$F~g@y@r_qvHdLgAtmu{szEHWq5^PmZIsIkjx=68#SIY Ex8(1l0hc6?-eDGo{w{E+$R;` zyx_M(5%AsoJ%lG2+%05!z>mwGFm7N2ft6bA*Xi}^sYtxei(v9GW%Gs-6!(yZ^~9e> zT)EF}XaRoC{X2|0Kbw?8QZIwe+`W-15YVR8&bDP(*g8ff@&IXHOGG{SwLfYeOJTJl zPcnw2WXJ(P00WL>7I>_C&-xzHv_Xdp ~&mTiv&+$IoX01UK*i1?5BsXL6#q(9DSnj&h;5(8`G$6qu{F zKyBaz1?8&Xy8(g=c!C0RRd6u5z$Fxv69ruS@*jE$4ducG3e1IC0-=0xf?_UI@Ld4W z8~6sroT%Vna&ZgIoT%VJIeQBY=DI KT5Ini4nH}C|_oT%X00J(*Na#e6JxwwS}bKRhzT 1V`J^7_M`d<`Ib6#B9OSXzs!~WA5rE-m;avDKj|;`OS_0noR&i*0P-K1rv3G& ze{_IMADeCcE0Q~YR^`8- nL0ad3ndys&dJrHQa>xR4|BSzo|0Vq*-uvrZQeYM#)rYm#TLOfXaz-C|2mu5{ zleVqmpVWd6x(NV~CK3=4arom~WdNXTV=WiahZ+!V!GZa^-2GE{?-#KB&*8XK0w|M` zfH?4hO>f&rPibvD6glOL9AfCGOfR1f{yu*X|EuZz{USp|nzS-82psHPsoQjVg?niC zV+c*LeW?YKLyPk6*6-|BM-MF$g=Si1Hpo_T4jZ1m&vt%UUd@gsx(EE_Us1hW{) VyFq94m@d(`0& zkpS&n_s=r%t9bZia_k>+?9UbdOkTWUWBJTOJ4Kw ^k&K%*4UOi(@+z%f}KC zEyFqjgFpfRt~ e-!_|w4C9Es z_e{h9#O00g&nE=vlXIc |D(4bzC2V4LUW9MuT` zr1j4HKW0`#(lAIZAd h CQ7Wp)_J73bkV%rG_y{4~yF8PaXL=Lc?wX~wBNJ%vt+H%D}6r|y6 zjlWZ_hF2o-)aTZNaYHUqf<8G2vtr?DJJG`7#m=SK3)2e$B^yDruv{?^Q&1FNtPQiE z@|oY*FKVN!(0^j)`kRZVqC}|q>f+|g>q{Mmx$$d{O{A791~BQ(jg}i$i3IK0PRA%2 z005gdZ=Rgb4F|4>@}Oiw(we+<=U6xx5VO0M%W#UM(O4k1s3hC$;#+@#!&-tM7ZHF2 z68+UiKa~kpPP?*wCc!XTOYDv~5vd3`$U?R2;9N@RNt})O-5Lf_RAc; ZVcnoTPpApzKCTTeGO zbE8pd(dFnP_W(3}l5+O6C=e>EwJHBWf19b47yxux_&EH|*5quWL6#~aD(U_W{%rd( zhRxc!{BLDg1QnLn=;-9px+U~t;&|)^e_?wuw_}~mRzO52D;Hi+L^N!2jg5A<1fgQ; z&v)XEt$;v1M0|SX=W*fKU+YoPuYIZTOal>TO#6#J5HtnDXS4gKcwp9OR9b+gU^gcb z5dg%&-sxk9=TA+Uc=q&q5f4D%%FX9@pK#1Hd=j(y$M_SCI#gJ+Li(LMj+}7XluE!c z*N 0~@_ld4!~s)(v@t8U+ g7snG_T zVDIU8^~~g>OHEI@b?Mj*>!Ak#=|yzw!uUO{u4fufCDZS=Yl*Iuc31yA#jj=mo8N@F zd$IlK+{OL(eg@Ys?Y1&c&?f&Iow{w^iU1@41giB%xiPFmV>m74i9f)phLQ@2nBB&7 zemcFp-rQMS`{d-8CPF{Eyj^cHAUbMoZ;u$GttbM3gr+x@Si*2yK@*6n651$4`R&Uz z_UoE?(f=9RO47iUAKAE^5&%)E@x3#mNfQYGVmUe&5Qg2Z(J<5uW1@kHYi%b-(Ou-+ zUORU~_{eBPq-$5_LW^EizA}jv0ww_BgVZyI-6AXZEtl_I%RE^gfOc;8rORVpE0w<# z{RXamYU2$uT1xRmy=iT<0t T~W8$8Dh-$Uh_BsJWR6aXtJ~wiM zzaEJefY7HvF#OlXx$>^Bxv`rva99+U00^{l-T$H3kBJD{xyF;t$9^^!yH0TEDbPA; zP3F6&0?6JkZ_E+_1eJ8}$>y@a697`J_K}g3b*m3W0Ejj^X^qbkmBCpL5HrcjD`Wdv zk)@sh(K XLl0;I8cFhYB)v|?46Fu z%O`D$!F!Ib$?w(Flsp(oy)viUI^e(52A;Z2jrhd`-)6i^#&K$8U #v{VQX zXn{b8 nFsSJSufB*`{ zP6xEXWs 2q%4Lf0XC>;5;cn?zTJW9uzIMB1Ky zm9|^TnWX*xMPe@oR~|0B^`5QQ-Z&-0D6(1ShS%EuaMuXptbTHPEw2O-asBw1Bcxua zl li zV8qr3HvQOo3W^k97rPD+th^N8P$&_t-P-rck;j@r5NzFa`F-Oi(Q<&W4+1LyDPxox ziipy=v)SQN3wq@=AeW2ukJ$Qj?<}Ix$O^OuT%rU3I#16f&qs-KE^2J6Xm@{SuPDOi zxyE?S93W)FrK6Hl9GkW6mWYN7H5|uxzO|8S^vUc6;&gwAt;nLD%uB0ze zuz;0PNngaidAa!b-2A4Bj6(0!3v3)`#ivh~n=d4dR-Qx%V?*E~3$S~j1f7$zi$Juq zTXl*T#~T_%qD6)Z8`Qsbp{vke_EVpJW7qU$y^kkutIy9yN(2KYvNkR1bn#jKdwrsj zsQRzqOjm&!Bv1zi2WMYiEVV!^kKCJ >>VdPaMvA6Z#lvgXKaqNIwE zWnvMf#l{OyZ6|{Mvt@6L^u`>1Y-)DJ2nLg^_u2l#U;boiRS6+x(q(~uQZG^wf=x^G z&K5S myxs>u;H)_{v9jyz=cYeX(^ZQ&2yeQm?Wq?M@^_4EIt);> zB7*IOIJ bvO+W`#7+@C}WJ43J6hO09!xu+1XOr z%NPGDHIizDI6OMjwT=BzCP^1dJs^{h(pWF# 3LgZ1^;Tx-ML)A *;_u7Bw0N3I)f zowW>jw&9!metvN!(SQ$vy@xM9b15-O`la3T&m8}B=$QwQersv`nY?O=8{KZs6#+9U zE66hj0I~sVHaKu?np#~mt>yjH0ydpEr#DTxJ2~D}!n|;PzZ0J0GrLBlKa$KYP99xv z76ag7HNbSMdZ~>+U4GNidTJ{oqJ&_nm$_?=#pD1Y_SOYa nlmPmJo9Hqrc(SKbUQGxdkbi&m;?xz@_!qu1W6b6)~bU#8Wj{Qy0K z^;bu`>Hq?G@9D#nD-Atn*^bTd?(FNA>}W9&gGeWN{oF*SIpN0JTKWMI(^$TKx!G)b zfO;W-K!q`oDg~`vYj*RskO+Witp&`MdT5ngf8})3_8?mjIXRGs>#sKNThYF>-jCe6 zy}0|ze7?UG6CiT^$>38 7-Tw(Pj6M^`@sDU;+saXwK4lV{~P7EKB3UmW!u4b)5!GL}cqj@&hpGC MZj=gzKm&e8d*T_rN0}xg2ZtHk8m?IU1zBQU(&Bju$N(69VJxTQZ?Tx1{dV=@Q zE_^+rWb>720qp)lO6Z-4MazK(Kx<)bS(M6hCib!S*_S2?tGdgPfU__p`|?%>I(B!< zY25=_DW{nbr%_DN)!Xy)V*#P+5-|xlXigO*vi8Z1-6b*NN6y_JtnEf?*S!Y0X3FTB zib&Bx00BryC$+;%G1+3_N XY0FtOEuIcOT!@Iih zgsRIn_MQQI?bAdsqwk;m#1qDlo%yxh{IO5IQ!7g)0?UN{5(EdWNb pz~{z BpW!9ncWCONQ0_i1~xY=D_`SKf=w^Ak>_^sPK6CdMv@l-=8eQ=Sjuivq} zl4$gs<1~{|qa)SY3-U9arp17hwnj5Mu=0F!Wa7bZZtR+ 6T|>a1Jdry z`_8=?9!MgM`*><@W^N@i$>sy+M~=RFrlm2cT#Tt!t^^?Xl#}lJ>`pTb2t0P{=%^8n zGe+~x+7pv4b5MnIcTTsB#sDR0EvBmh3B1v|%rI~ SIW7v5OQwH!3W;jQte5e!u8&z!ko zJ(frZT@4bdfKraNx6B8MR!VstSSE;0%FNf7mJ`7sWpi`OqfS89v(hcZ*>^s!2%yQf zL$Aa^Tue h|asCdmYat0ufD| zHXClxERi)mg1tLrd~1o>CMm0fdM&%V2?Evz8`dfAZrOu3vrbxAFf^<{ASg z8(%s5T+3-WIBfCMm5ov=k=8qbh_(~0 (I!-0nyp(HEI%%w#sb<~sV5z7v{rpmB zgLsQ`Co11IN3)cZ!6PAW&@*t!Cf75I%979Ow0Y170hQ`W1f75V%u+^R@0-7d1!7Gi zX7QOw8E1@YynpkyrNA={hSsX}+#@@6OV)w_sPZA%uX}QX2T))H00|svVy+M@K}exm zOXEzZX^CvHaK6<}*+vFtuNm{&VyRqrd9rn7EA#}anCQGUdTFHYEdvk$Ix_GX;{X&0 z5qcGZXb6a@5+IO34i=KZ(vB4o*`WTdVmT6mh}iWP1Jn4rGu>L=y>IeU3js4Rg9tPL zt(3UdD;r1aaTWw5B^;myLZVp{+riOFv`Ibn03c>J_G!Qs;lO#V4hABEhS1;$AVicl z&$`h>qbJ#-8QYof*Y2((vzsf|w-; TZpk9d0_zNGulA)p$-oL!tqJw|~OUfw>s*pA@!3J+VyHN^yXA2*M z=bQd%_h-7EcV9W9Y3V#m9PX{wJp=%WlzFLP-nY&5%b5?0EBAUa*SiYBCJCXs_tieN z?=d*}0~?o9Lhl?0?!@$+LQEB~+0Sd{+p{?E$=A=<=evU=M4|!#vi(M)&`$!#K8UlO z_IzWdHl397R~2cM{g8bY^km(5rNNY+9ZBk`4Z@>$TyT;kP5|+74xP6f`+H1-(^$?z zi{1sDPfx8}KSOa)2~N{`qjoUZsc)#wNCrV`0u`yzMznXJ{s07K%i(wCpKm3~xu{g) zl4x^3QUpM_z8qQ6bIsT?QBXX*JL^OMh*s7*D@w)MdFINAb_(RFH)1pKbfLfaN4Rq) z@Js_Dwu&_!1b!?~iALo`HA)*!JXp>*qUch+lUeFdPqNw3okv?5T5A!}l0R^U(Yv}l zk%{$80Ep{FlxXyns2s(Z1-aU6S`RH%E-4!rv* 5F$?jiOVp-@EE94*=`~c9BL;0d(G&72At8S-?c-ol{?G^s17( zUO$%+L~p-8_`;ayN@ aebQW9^Cg-igbhv+h2+z;g-P{N8VRY+@ YNoxbk0HBpPtsuP!33}DqS!W4=Qfl<>m-o`tG7U&;rSrSYswec# zq-rlM%y~pdo>P#mFYH+j_H0?Sa*c=D6EO~aQM=vMFy@K~biO;fGP4?JpM__;(-G-~ z&O>IsZmE9)Q2z*Vb81fr5KgvRrAo8_5EAN5MM*gppr^R}kq=&exswJK0QDt+E^Etq zG?Eeet-GUhDT`@r H-(s ;jaD5aH_+V|}OnwB1`lbGl4E*4V8K`5%V zyM4CfxgP=%0Fu|*I%zKyLF%@-zO@%n@X@6Ye|WlRu3GAk=(4taF75|WP(L@d=vV-N zE?!i(oM 8V_~xX zp?LoJ+h3c4Vat7qPFnZv&G~+C5YOB@-gW{2 YZGGbL-`?3wv T7r=2e>t~m5f?9)4McxhUR*3s3YO@@w++%u9=e*mSNQUdpx? ur9_Fq%Cjdq9y7h@xMQqW#pgbHjCkmZM zD;;Vtt`tjQMnZt7^3wC?3PzvA#oWiQcAfMC;J6seHyw!dN|uS}^4My}Vt^t7NL1U1 zVt{;@rIrLD(i_?Oxe>kC(%zwhB7%dRe0^+&8cIU|8QV%35GF$3DmnG&vmZzUDCb4F z-S= W|q>=h{sk(kEv)lO0ZAO!96bxVD5 zJa^VlM;ZeZGKdLWd?na4;_^xCdw{?M(U5)sA_QXgos(k(Nyeum4J|_P*bVL3NF|ac z8d8k_A^{+%v}h9@`P#)#*E8j{b1qqYe-aBIQ0K-x`pNOlH%#Ul1Jqg-OsS_JD@!eE z^NlNY1~>>j8Dn(6h}e4X?aoI5kh$kBr;Rb1{2i}tCnE5H(^^Ya8g!AQeTgpCiwE =|jl7UE6E}y$|ox~h&D`?$PHXtG&?kwrUS8M&_I0) i<3vMSck=~0}$W*EW )hP%A0Q;#oKX`12g6L$s zwKXwUq|-(Of=W`*Ij6TP0Hi06?si$1Mu{Z(rI9*w+&MmS;rhmfnG}HZ#%X=(`|aYa zb4CfkaahDe>2j^q$`SywSw?N6hbD-*@W#%=%{a=O(Wop3S6-d3sew`O)E(O^MnnK; z{lybIMG(ZNKeo8>_P2MYnt;>NgM;|Nr`EcO7W7KY%v5}=^+v7$5PG#rFoY5q1VD>G zOqnP9_3&iAn5zm=Fs7A~fpHoJMrrh!`I0!CxjbLXEw#h_xy5$Ui_O+AUTP>o|2f+m zyVMc^2|=qeGOKn7Ar5RnfKx_k5vRsxgU(ZPuhbwFA+aD-l9 o0hd(kCG6_I36c7c?C0*W4b$$vM#%_AFIXILf9EQ zo+T6py7kUVBvQqQeSH37^D9n-<2ygD%meezX|QC?C0vLqBBTHzra@y8o!#2G>wiA| z>-p-5@s%`V>LI%Q n*1Nii;w4dCiF^koBx#q&Ff0L?8elrcFMLd9VPTlxn|;PILX8=}Et5(Ywm8k6)Y1 z0tTp;q>O^jL|YLD#YDDUqxGFFPMY)ZVnhY&vy(NYs~7v3Tj$#j0En3_TwUuZQkpG8 zKmvf+de7pN(E>o53%9&>b9Nv^pZUz$r?zgreB`cAo|?+251m9`e&M560=CvNLAm72 zL*M!B-0<9*_3_S^uJ112{oe0xE}-jeCm?X}__fa;j r0^}g7grHx|gn=CfeH%EkyFs zQzLs%i}E&{*_p4^md`BRIp%fZ1OPa#@9&)I=2ESgf-=*BzHRfd^NNps`-#^d-m(OM zIPifVd#nSP=PsDU8LgEwUIm_Mupk5--SvX=`r?AO3_wH#qD|{F?`o;vw!i{i`~1lV z_eiJ{oc_xAXhUgkY $D#RgU0Dpr#$gxm3BU0a$zEJ#uyQZPHM{$MP{xY#r4IxiBJlghF6xx zyS35U{x_3tgZ|nyh#RFy)p|ACH2q>G%Ol@d{K%*!5|H;JFWPK+)(Fy9&r9^Z!%hjA zN8bJZYdm<_<9E(Kq9lI#O8j<7sn;}pwREDiR$3`tKDh?g`t(-Q>M+LHo5p8r`Q+`# z{~$?C^@}+$sWMusRCr}F*Zl!VM^v9XE0fDkSh#4;%qmHo24`95CCrvbRkjr^mx4j( zw1~C3bE_z?&wZ#ai6X7P*HBRzDG`XycbBukxh&4S<%RRve782c`~1SvNa3Vk;P|Xr z%w4teCQ#BZtzGo!-UqIHX?Io$h_joT%SHqMu_ZtE;?}7r^zm0=CR=*Yh*>xt`JBv_ zRAi(zsQy*c*?g#Nv<9?tqCrIDZ5WN+HagmEO@80hwFSc#14yrZ;JNXnN&y7)M?~eV z-PzfTm&Y>W08qHIoGbK6++63QiT0zuES(G*03w?OqBMbs017|8UMnt)H*%!`fdBx3 z863w_+uV3(e=cpraTeQtQJkg0w5eMC*UFgMlhaQJ@9y8WZ#4jD0dZfjmeb=m`AaKC z3l30bLQet!o5ryXZgVfSBc;%HU|B>dmrVY^%v_A|_g(yEXf5@Ji8QIIB>?pyQf{st z=7|ng%AI{n0CaxW)I!F60UU&NpE!+!lZeR{3n!a5Zq&5&!NI~(xtt{V=-WF@&$@AD z?S}m@u>I&Mget9_cWkML)>`Ese0y~@WuidX0uV$uUV1DO>IX3s4r*fW>t6~#-!e+~ zM)3`IUaz&Hl^?h~=K)EQ`R{%F=8!E@Z=y9=Y_-bACpxhO1Uj#+sMc2K8AP6(074+z z+SG*pA7lX8qBtGCGh6mX6zHZZS2pfh-3u5HF^x;}=ZmpsOw?Oa)-YDJS`U>sg#@jY z bH5IL=3wmJ-3M|J0K}e?I7w>Xmi>SLK(vYOxtm_S zKd|JPp x z5Uz)ljmGZiR?O&CB7loQVydu2=mii8RDk%S z@7(eyS^sR^GglK3Y`#h)N(tGc4`1DF*B&}w5c(9QRO^E`LOuP_jG$ItIzQ%7wZ*g- zYZGp5UvxdS=u-fq@Y&hx5{|Bo?q*YC1yvOw9$C!@GCM6JGgsTA%k#-x&<{X#Il7$` z%j;gio3s~D71PF?c+TzAEi<8SrC9mq&F7z)O{7JWublo$P-Q5-`8%lv mkVv%x z2!O5E$1Wo{GhHeT85R%-`qC>md>}4jrG-)LE!*OzD@pgsmQha05JyC0iF^9Pun@wK z$VId8Q$eog+mRa5*jjHb`+7*(H{;2g24RR`vhmm##?;$iZ#gxjWn&`+r6XhcYOuBv zDZ$W5uwkdk=ik}Rl?-L!$F5m&7k=Qb7j5YTLn~Xd>C%U8A2mvbETV7 bE~{ju$LBd3R|AghO?PB=)R^pFIy+1%yHZmy-O z5u`!{zW=RL)3D4Ky4lA!pTDnVw60o2Ow1x$YZ(ABfP8duVJl?}<(zizFUPyNR4d{j zPGieSt7QgYz4vUs{1ENDJj9W;oli|aX;7VsZ4l0!jO$tBYp2ve>PJUfrh92qrxruI z2)XVSNmXJWWX+Rb-Aq=N8+Qr%PdxXnvAXEx`{oXtAx)HWyDweoSX5)S#p?anc(EPj zv-h2^Q~$vAdK@z5uFTi;(3W(!ukLg_RU_tX>xa j0|n= z!!Mkh^MtBY)W2Grjg+Wl`Nol$(0|b@ukWmfaZC^l?OK-8m{E<%hwr7EMgSL`8S5|x zs%oDYf3fa^!1B;W_Gn{8LzGk>wj)n(M;Z{FA6`#sU`b ZI+WU+n+2B zlB5(-c|u!h?x@!x3ta3q8`}!im;KxwhYbMH(c*qceUa=vUbLP|NVMpshxK>Y#QN0p zkfi}xd2(UTRv!w^j CMSq Rtk_tzB-$j4Di2KNeI2 Lsq#odr#If)8A5URusI z1l3&h^y_bp#Kg>V>z}C&29dQ>w}#*S Q+{X*4^?`Ta zJ0mk=w)%<74=-Mj*N>cVb1_whnS-?X`0?@CG6@%on)vkib9e7L;f0;hLI@zHYLMjZ zhOxEwR%*#-aWZ+`gaKDSXysaG9!YXV#Hb`$9~8yX2UeC_PJ}E22qxb8!~IWPMa5$s z0zlSVZ|NWt?acGf81URuXo-V`Z_@Z(Eln5_MU_z^f`JkNBCu)w-1(`oOYKCXPYUAB z(k;7*YAi#T$f9$!9v^HvX{}u6pzsqDvDQK}=d ;Nh!zp_li3n6Sd?~}>aX6} zo?OY5pdaLmudb~%%J!38Z!Kxy0}t{+SAOP`$EWtTPOs|4jh2?>QK6**Lo%^Xi_jwQ zMSDAUy0~FCkDM0tmpI*j_-n_?TIqK(TaZMvH3-qcB)Yt6*B7=v@LHml@2e@d_D0#+ zifJe!PP2&*tqWq7=)UbznOaE7X(7vffo*X)oGdk+`%>mwPOCGA6|-eJXhfW`wEor} zZ8#m(a^> nVL_l?z&4x-y>5joDxaO! zrqPQ0LOnQmkVGr_@teQOo4FCuPAe@KqB#zqeQUBEX=sJ)@w4gXcR#p2)_CL0>ZkUI z*;-40C`JC8Po>^IKUzl5ykm?`Tzc=(nAsi_qMf?*#+CC6VxnB5qZDDtW*;7ZdOKGF z0F-9+;*R6(?>(AruXkxxqUPm#0D?A+O|y4&rfKse(Yi3Zi#6D2tKO+wI`9ON !t@3<)xr6j%e zq)-0Ojimd^)#hVU-ZCFpT8UE8kr#F*QaSQy%L()4i2y^k%|^ceLPMdSWSM9r?G(6h zY&=h+d(Ib;P>d5ZF}Io^2yZP-71`;HcMflFxAvONX2GCA6A1MtD(`lcoLSgQt*f1h zEQznDJalpRW_#43|3tyX$90_g@4nq=HQs*103f?m5A^s)A_08;;irrGhh|>*Vk2pM z@A}fcAzNlUD58~8ng|3yZNAhdI^FREB+3cMGGXYZaIazX06;-l1Z?*or{`DhxxO(P z14)ruQr*dx22tFZ3W_uh4;$5*Z?xu8s(0$1s6qlQaQSYRtcM1`qj`Za^8^SqhI}Sr zkpVH2B^xxKzUIF6-P~ysVgj^tN&|55iY04l%xb5S>Cd+)_|{S!GgUw!+9)Hme4irH zN}Oi9G?NmDFiaGHga* ;c{0=xV*Yw9~N|plGcD zQH@Kbj+-A(+2?DQTAG{ppOoE}mf?bS0?$+lfS` W=^tm8D{<&%9lv^49NXPV#8+zLuu?ZKtDePh;31jpxtDsU@m_XjMM)`qLeY z0c)2;Mr;<=SKpceAZfhZjmAqtpIR%?kn)pj88=t;|MhIH;>i%sB<);-5SQ?R;i4)( z*nPYn1WW@F=%nnvdwDOQfkA5#*t9uv=j9CuxNOyFwq~R+$s29M7W3UkY?}|i*>0xO zD-i*XwWj>Z>&=Cjh7CGu+?_l)Qg~}Y0_h7z-$fk1jANj45&KiUik zl_g5+C-1mab6NnwKuIXQXC-Q=W^B=2iFQSjs7OrFv{Ny(asXi%sa4ckd#-$NAxOz3 zih4uQde>cUT0(_6PM`kJWJ96S+4raSthK<*>ck5XHCzznu`l&h#{vSu@KSVA)+V1A zC4wRr=|O-$*NH8m!sLskYm+S{m9laDOA`-l=8=NQTZtAS5+dzmOBjxdC{s4NQ7Ne> zq8@@gOf8|}WS8!}P*bQlaS_gZcH?`yO7|2%01*&|ts+V*Cjg^wCc+?+pG#{_DhFm? z-`RhD+hBk)d;uU3L4O5<%pzW6Fo~I!O=KX4klg(0@8M!bg8{7~Q5`;X$#(w7^?+#* zM773WKU&Zs3;E`p?qs%dTARk#M>-ydt{gNU{Mc$xDbcEI9V_NM9l{WIZv8(NDkR#u zvi6DDiI{LG%k|eD+w!0R3AA>t-RIX oveAeu}70I;k8odGHU0RRC$8483!Aq9I2LIeO| zDZBCVV*>yT&&`ex=6~FOC;uZpP%4krzp{RQ{ A(1YzW oEdJd3r}%%yzc~Kq`nCPD{nz;~ zgWuk}>vA{uzscWqe~bNM`j7YT@c-Yxw14OSQ}B=XzsH~JpSeE8``Z2I@&n)(@h{`Q z(toyoy8mteul|$WA7(D6{uBGh l$^KLJBl{Qax34$tKiofF{nz^E{y*}2 z``6ev@z3MG**~*>%Ku~k)BfA-pX?vzd{aLG{nPyy`0v`U^q*jV$bXZ6W&YFtgZyXq z-~3-{f3yFv{_p iTy*nmLUsW;V&^1| zsRXWWYEor%m07@Hk>|FIPCZ5=hVQ!o=e@451ToR QbO((er z7g2GS$ZNCz5*LoT;f*l(E9lorGqsICHl^pLmWQB(O7yc9RpdkOhwYF1Rf5*kfFKzS ze#1T9J9N~_MXHx$e5=wq+=KmmrK_j=f0Wi~l-yivlsrY?DiI3qX{Y{a-};e|*-|8^ z8i_3t=R5}QRIXSMT{RXi!o=;nFVVXs;d3m#`s-*}@)Gqj1ATdEv_@&5i=V%CJVwn< z%bwF$XM&6^TF1l&XXiXOHPAC)UK4F)oI%C=uDgnlCb-51>ah&89YDFgc{k;rj{K}r zxT(Vd0H=jbo&txWtGtXJ_IHn@DY$C$86cy8b9rS6^;Q123XhWh?~vu5GgNq@kX#67 zIw@ja8o`H&2dWPf;#bPdM}tSn)0jy>0Q|WHz}`%fa>=Pegv|I>zU_aPE@{o$N*Zbm z^C^OstD?b-+BkOxBjJ5NWEs;W5VGwVfYA?E>HE`V1Yo|yZcr65#AztEKLJTtwHTm2 zl`7S=rjuU;*8?FO1~}~6Na+syUSx8$vS?m%(F+*8ZP&Y?nupZs3Ub8&a?*BY#qA!M z-a61?XRc?r$@ROQ -g|c7{ $1saVN;UVv+bc&biitdIq%Mjh`nnOKAiB_hr!aY~HM9 zEjw-C2%R??!0a^aOjdT{n}khfQ
YE4+6X1^W( zUMgTWYGwE>(s-{PeKUH{p{#l;@kXmM#e@oY1}j1B`GHd5@L%%x@d_X0)gO~4D(TrX zTUby7^eV!WbmNq{x8<$QUkhy(UQhRmJEdcl=bmk6tV!GV*4K;*&T2p+Wo?CzBF)KE z;FPl#XimU1wv9Q@g#*9<{{JR&00RqNR`nt=!leW`cm*MteI=xtlT#Cw<@h2c=Zlal zT^2gl;m3}1V+#H8EhHKN(VH1=?z+biv#@pxFbIzljBe#Nl3n0$?sm%A!C6PjKR!wp zBn)r9+HA5IG6k}s3DXmyumER8bcffS13OsLV#$yTV|0Yn>~}_ ;L~nO%qclP95;Def`_~c9tRaGPjO1|A}Pi zaeinnaOzCXbWM3 fh+r?xHYqN)g?~s*n@-pcJk?KCbmXPe^(*CiVO15+mgZ&@ZV&n{X%&{+?@AdD!!B z=Q7Oyf0 y146{Ay3L))=T11tk76U)!Q1{VTn3dYi+7U#z zgN~x>R~S6K{UTcGDF@l@t{1g-e;!C0e-EUz)92?L6;tEXr|Q=PXpox(Q<0x*4P08A zVw;hk^ji5B_iAs#0IDdp7Ha(f37#6=luLaw=7BVOS%AH!-pLdGI1sxOp{@JX8~Dqd zLILcVi6 kCyZEe;cdS=T30RO$FdguX$LFZ~wtJW3EnJF(kk)aVQo z{;$rvDmz>FEAj~PQ{Ph?;h!Gph%9Ui?RR7>zD%$B3De{g_09w5xsjppGfbvHND