From 94f09b8dd98bc80d4e840af384ef8aabed67699a Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E6=A0=91=E8=90=8C=E8=8A=BD?= <3205788256@qq.com>
Date: Tue, 27 May 2025 19:53:17 +0800
Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E4=B8=89=E4=B8=AA=E6=8C=89?=
=?UTF-8?q?=E9=92=AE=20=E4=BF=AE=E4=BA=86=E4=B8=80=E5=A0=86bug?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
CopyItems/crop_item.tscn | 15 +-
GUI/land_panel.gd | 190 +-
GUI/login_panel.gd | 30 +-
GUI/player_ranking_item.tscn | 1 +
GUI/player_ranking_panel.gd | 4 -
GlobalScript/Toast.gd | 2 +-
MainGame.gd | 205 +-
MainGame.tscn | 24 +-
Network/TCPNetworkManager.gd | 61 +-
Server/BugFix_UI_Update_Missing.md | 241 ++
Server/BugFix_VisitorMode_LastLoginTime.md | 184 ++
Server/Feature_RemoveCrop.md | 277 ++
Server/Feature_Summary.md | 236 ++
Server/JsonEdit/README.md | 100 -
Server/JsonEdit/example_formats.md | 65 -
Server/JsonEdit/json_editor.py | 267 --
Server/JsonEdit/requirements.txt | 2 -
Server/JsonEdit/templates/json_editor.html | 627 -----
Server/New_Features_Test_Guide.md | 1 +
Server/QQEmailSend.zip | Bin 58612 -> 0 bytes
Server/README_Refactored.md | 175 ++
Server/TCPGameServer.py | 2255 +++++++++--------
.../__pycache__/TCPGameServer.cpython-313.pyc | Bin 0 -> 49870 bytes
Server/__pycache__/TCPGameServer.py | 1569 ++++++++++++
Server/config/crop_data.json.backup | 32 -
.../config/initial_player_data_template.json | 122 +-
Server/game_saves/2143323382.json | 452 +---
Server/game_saves/2143323382.json.backup | 414 +++
Server/game_saves/2221023030.json | 67 +
Server/game_saves/2221023030.json.backup | 425 ++++
Server/game_saves/2427948832.json | 68 +
Server/game_saves/2427948832.json.backup | 430 ++++
Server/game_saves/2634726358.json | 67 +
Server/game_saves/2634726358.json.backup | 425 ++++
Server/game_saves/2671387804.json | 69 +
Server/game_saves/2671387804.json.backup | 435 ++++
Server/game_saves/2809548669.json | 64 +
Server/game_saves/2809548669.json.backup | 414 +++
Server/game_saves/2973419538.json | 68 +
Server/game_saves/2973419538.json.backup | 430 ++++
Server/game_saves/3205788256.json.backup | 986 +++++--
Server/game_saves/3346964708.json | 91 +
Server/game_saves/3346964708.json.backup | 545 ++++
components/ToastShow.gd | 2 +-
project.godot | 1 +
server/game_saves/3205788256.json | 1150 +++++++--
test_version_validation.md | 100 +
47 files changed, 10303 insertions(+), 3085 deletions(-)
create mode 100644 Server/BugFix_UI_Update_Missing.md
create mode 100644 Server/BugFix_VisitorMode_LastLoginTime.md
create mode 100644 Server/Feature_RemoveCrop.md
create mode 100644 Server/Feature_Summary.md
delete mode 100644 Server/JsonEdit/README.md
delete mode 100644 Server/JsonEdit/example_formats.md
delete mode 100644 Server/JsonEdit/json_editor.py
delete mode 100644 Server/JsonEdit/requirements.txt
delete mode 100644 Server/JsonEdit/templates/json_editor.html
create mode 100644 Server/New_Features_Test_Guide.md
delete mode 100644 Server/QQEmailSend.zip
create mode 100644 Server/README_Refactored.md
create mode 100644 Server/__pycache__/TCPGameServer.cpython-313.pyc
create mode 100644 Server/__pycache__/TCPGameServer.py
delete mode 100644 Server/config/crop_data.json.backup
create mode 100644 Server/game_saves/2143323382.json.backup
create mode 100644 Server/game_saves/2221023030.json
create mode 100644 Server/game_saves/2221023030.json.backup
create mode 100644 Server/game_saves/2427948832.json
create mode 100644 Server/game_saves/2427948832.json.backup
create mode 100644 Server/game_saves/2634726358.json
create mode 100644 Server/game_saves/2634726358.json.backup
create mode 100644 Server/game_saves/2671387804.json
create mode 100644 Server/game_saves/2671387804.json.backup
create mode 100644 Server/game_saves/2809548669.json
create mode 100644 Server/game_saves/2809548669.json.backup
create mode 100644 Server/game_saves/2973419538.json
create mode 100644 Server/game_saves/2973419538.json.backup
create mode 100644 Server/game_saves/3346964708.json
create mode 100644 Server/game_saves/3346964708.json.backup
create mode 100644 test_version_validation.md
diff --git a/CopyItems/crop_item.tscn b/CopyItems/crop_item.tscn
index 6736eb7..e606ee3 100644
--- a/CopyItems/crop_item.tscn
+++ b/CopyItems/crop_item.tscn
@@ -30,7 +30,7 @@ scale = Vector2(0.2, 0.2)
theme_override_font_sizes/font_size = 50
script = ExtResource("2_1n4xp")
-[node name="Label" type="Label" parent="."]
+[node name="crop_name" type="Label" parent="."]
layout_mode = 2
offset_right = 250.0
offset_bottom = 42.0
@@ -40,3 +40,16 @@ theme_override_font_sizes/font_size = 30
text = "[普通-胡萝卜]"
horizontal_alignment = 1
vertical_alignment = 1
+
+[node name="status_label" type="Label" parent="."]
+modulate = Color(0.721569, 1, 1, 1)
+layout_mode = 2
+offset_top = 17.0
+offset_right = 500.0
+offset_bottom = 86.0
+scale = Vector2(0.2, 0.2)
+size_flags_horizontal = 3
+theme_override_font_sizes/font_size = 50
+text = "已浇水 已施肥 等级:1"
+horizontal_alignment = 1
+vertical_alignment = 1
diff --git a/GUI/land_panel.gd b/GUI/land_panel.gd
index 7c5a61a..aacb5cf 100644
--- a/GUI/land_panel.gd
+++ b/GUI/land_panel.gd
@@ -31,7 +31,21 @@ func _ready():
plant_button.pressed.connect(self._on_plant_button_pressed)
remove_button.pressed.connect(self._on_remove_button_pressed)
harvest_button.pressed.connect(self._on_harvest_button_pressed)
- dig_button.text = "开垦"+"\n花费:"+str(main_game.dig_money)
+
+ # 显示浇水、施肥、升级按钮
+ water_button.visible = true
+ fertilize_button.visible = true
+ upgrade_button.visible = true
+
+ _update_button_texts()
+
+# 更新按钮文本
+func _update_button_texts():
+ dig_button.text = "开垦"+"\n¥"+str(main_game.dig_money)
+ remove_button.text = "铲除"+"\n¥500"
+ water_button.text = "浇水"+"\n¥50"
+ fertilize_button.text = "施肥"+"\n¥150"
+ upgrade_button.text = "升级"+"\n¥1000"
#开垦
func _on_dig_button_pressed():
@@ -41,12 +55,31 @@ func _on_dig_button_pressed():
self.hide()
return
- dig_button.text = "开垦"+"\n花费:"+str(main_game.dig_money)
+ # 检查玩家金钱是否足够
+ var dig_cost = main_game.dig_money
+ if main_game.money < dig_cost:
+ Toast.show("金钱不足,开垦土地需要 " + str(dig_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+ # 检查地块是否已经开垦
+ var lot = main_game.farm_lots[selected_lot_index]
+ if lot.get("is_diged", false):
+ Toast.show("此地块已经开垦过了", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 发送开垦土地请求到服务器
if network_manager and network_manager.is_connected_to_server():
- # 使用服务器API来开垦土地
if network_manager.sendDigGround(selected_lot_index):
+ Toast.show("正在开垦土地...", Color.YELLOW, 1.5, 1.0)
self.hide()
+ else:
+ Toast.show("发送开垦请求失败", Color.RED, 2.0, 1.0)
+ self.hide()
+ else:
+ Toast.show("网络未连接,无法开垦土地", Color.RED, 2.0, 1.0)
+ self.hide()
#浇水
func _on_water_button_pressed():
# 检查是否处于访问模式
@@ -55,8 +88,49 @@ func _on_water_button_pressed():
self.hide()
return
- self.hide()
- pass
+ # 检查玩家金钱是否足够
+ var water_cost = 50
+ if main_game.money < water_cost:
+ Toast.show("金钱不足,浇水需要 " + str(water_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查地块状态
+ var lot = main_game.farm_lots[selected_lot_index]
+ if not lot.get("is_planted", false) or lot.get("crop_type", "") == "":
+ Toast.show("此地块没有种植作物", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查作物是否已死亡
+ if lot.get("is_dead", false):
+ Toast.show("死亡的作物无法浇水", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查是否已经成熟
+ if lot.get("grow_time", 0) >= lot.get("max_grow_time", 1):
+ Toast.show("作物已经成熟,无需浇水", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查是否已经浇过水
+ if lot.get("已浇水", false):
+ Toast.show("今天已经浇过水了", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 发送浇水请求到服务器
+ if network_manager and network_manager.is_connected_to_server():
+ if network_manager.sendWaterCrop(selected_lot_index):
+ Toast.show("正在浇水...", Color.YELLOW, 1.5, 1.0)
+ self.hide()
+ else:
+ Toast.show("发送浇水请求失败", Color.RED, 2.0, 1.0)
+ self.hide()
+ else:
+ Toast.show("网络未连接,无法浇水", Color.RED, 2.0, 1.0)
+ self.hide()
#施肥
func _on_fertilize_button_pressed():
# 检查是否处于访问模式
@@ -65,8 +139,49 @@ func _on_fertilize_button_pressed():
self.hide()
return
- self.hide()
- pass
+ # 检查玩家金钱是否足够
+ var fertilize_cost = 150
+ if main_game.money < fertilize_cost:
+ Toast.show("金钱不足,施肥需要 " + str(fertilize_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查地块状态
+ var lot = main_game.farm_lots[selected_lot_index]
+ if not lot.get("is_planted", false) or lot.get("crop_type", "") == "":
+ Toast.show("此地块没有种植作物", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查作物是否已死亡
+ if lot.get("is_dead", false):
+ Toast.show("死亡的作物无法施肥", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查是否已经成熟
+ if lot.get("grow_time", 0) >= lot.get("max_grow_time", 1):
+ Toast.show("作物已经成熟,无需施肥", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查是否已经施过肥
+ if lot.get("已施肥", false):
+ Toast.show("此作物已经施过肥了", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 发送施肥请求到服务器
+ if network_manager and network_manager.is_connected_to_server():
+ if network_manager.sendFertilizeCrop(selected_lot_index):
+ Toast.show("正在施肥...", Color.YELLOW, 1.5, 1.0)
+ self.hide()
+ else:
+ Toast.show("发送施肥请求失败", Color.RED, 2.0, 1.0)
+ self.hide()
+ else:
+ Toast.show("网络未连接,无法施肥", Color.RED, 2.0, 1.0)
+ self.hide()
#升级
func _on_upgrade_button_pressed():
# 检查是否处于访问模式
@@ -75,8 +190,38 @@ func _on_upgrade_button_pressed():
self.hide()
return
- self.hide()
- pass
+ # 检查玩家金钱是否足够
+ var upgrade_cost = 1000
+ if main_game.money < upgrade_cost:
+ Toast.show("金钱不足,升级土地需要 " + str(upgrade_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查地块是否已开垦
+ var lot = main_game.farm_lots[selected_lot_index]
+ if not lot.get("is_diged", false):
+ Toast.show("此地块尚未开垦", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查土地是否已经升级
+ var current_level = lot.get("土地等级", 0)
+ if current_level >= 1:
+ Toast.show("此土地已经升级过了", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 发送升级请求到服务器
+ if network_manager and network_manager.is_connected_to_server():
+ if network_manager.sendUpgradeLand(selected_lot_index):
+ Toast.show("正在升级土地...", Color.YELLOW, 1.5, 1.0)
+ self.hide()
+ else:
+ Toast.show("发送升级请求失败", Color.RED, 2.0, 1.0)
+ self.hide()
+ else:
+ Toast.show("网络未连接,无法升级土地", Color.RED, 2.0, 1.0)
+ self.hide()
#种植
func _on_plant_button_pressed():
# 检查是否处于访问模式
@@ -96,8 +241,31 @@ func _on_remove_button_pressed():
self.hide()
return
- main_game.root_out_crop(selected_lot_index)
- self.hide()
+ # 检查玩家金钱是否足够
+ var removal_cost = 500
+ if main_game.money < removal_cost:
+ Toast.show("金钱不足,铲除作物需要 " + str(removal_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查地块是否有作物
+ var lot = main_game.farm_lots[selected_lot_index]
+ if not lot.get("is_planted", false) or lot.get("crop_type", "") == "":
+ Toast.show("此地块没有种植作物", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 发送铲除作物请求到服务器
+ if network_manager and network_manager.is_connected_to_server():
+ if network_manager.sendRemoveCrop(selected_lot_index):
+ Toast.show("正在铲除作物...", Color.YELLOW, 1.5, 1.0)
+ self.hide()
+ else:
+ Toast.show("发送铲除请求失败", Color.RED, 2.0, 1.0)
+ self.hide()
+ else:
+ Toast.show("网络未连接,无法铲除作物", Color.RED, 2.0, 1.0)
+ self.hide()
pass
#收获
func _on_harvest_button_pressed():
diff --git a/GUI/login_panel.gd b/GUI/login_panel.gd
index 16fe216..f9f5840 100644
--- a/GUI/login_panel.gd
+++ b/GUI/login_panel.gd
@@ -24,7 +24,7 @@ extends PanelContainer
#状态提示标签
@onready var status_label : Label = $VBox/status_label
-# 记住密码选项(如果UI中有CheckBox的话)
+# 记住密码选项
var remember_password : bool = true # 默认记住密码
# 引用主场景和全局函数
@@ -45,6 +45,9 @@ func _ready():
# 加载保存的登录信息
_load_login_info()
+
+ # 显示客户端版本号
+ _display_version_info()
# 处理登录按钮点击
func _on_login_button_pressed():
@@ -86,7 +89,6 @@ func _on_login_button_pressed():
# 更新主游戏数据
main_game.user_name = user_name
main_game.user_password = user_password
- main_game.farmname = farmname
# 5秒后重新启用按钮(如果没有收到响应)
await get_tree().create_timer(5.0).timeout
@@ -156,6 +158,12 @@ func _on_register_button_pressed():
var player_name = playername_input.text.strip_edges()
var verification_code = verificationcode_input.text.strip_edges()
+ # 检查密码格式(只允许数字和字母)
+ if not is_valid_password(user_password):
+ status_label.text = "密码只能包含数字和字母!"
+ status_label.modulate = Color.RED
+ return
+
if user_name == "" or user_password == "":
status_label.text = "用户名或密码不能为空!"
status_label.modulate = Color.RED
@@ -178,7 +186,7 @@ func _on_register_button_pressed():
status_label.text = "请输入验证码!"
status_label.modulate = Color.RED
return
-
+
# 检查网络连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "未连接到服务器,正在尝试连接..."
@@ -248,6 +256,12 @@ func is_valid_qq_number(qq_number: String) -> bool:
return qq_regex.search(qq_number) != null
+# 添加密码验证函数
+func is_valid_password(password: String) -> bool:
+ # 使用正则表达式检查是否只包含数字和字母
+ var pattern = r"^[a-zA-Z0-9]+$"
+ return password.match(pattern) != null
+
# 处理登录响应
func _on_login_response_received(success: bool, message: String, user_data: Dictionary):
# 启用按钮
@@ -262,7 +276,8 @@ func _on_login_response_received(success: bool, message: String, user_data: Dict
main_game.farm_lots = user_data.get("farm_lots", [])
main_game.level = user_data.get("level", 1)
main_game.money = user_data.get("money", 0)
- main_game.farmname = user_data.get("farm_name", "")
+ main_game.show_farm_name.text = "农场名称:"+user_data.get("farm_name", "")
+ main_game.show_player_name.text = "玩家昵称:"+user_data.get("player_name", "")
farmname_input.text = user_data.get("farm_name", "")
# 加载玩家背包数据
@@ -431,3 +446,10 @@ func get_saved_username() -> String:
return login_data.get("user_name", "")
return ""
+
+# 显示版本信息
+func _display_version_info():
+ # 在状态标签中显示客户端版本信息
+ if status_label.text == "欢迎使用萌芽农场" or status_label.text == "连接状态":
+ status_label.text = "萌芽农场 v" + main_game.client_version + " - 欢迎使用"
+ status_label.modulate = Color.CYAN
diff --git a/GUI/player_ranking_item.tscn b/GUI/player_ranking_item.tscn
index 28e5cb4..4731962 100644
--- a/GUI/player_ranking_item.tscn
+++ b/GUI/player_ranking_item.tscn
@@ -11,6 +11,7 @@ offset_bottom = 82.0
layout_mode = 2
[node name="SerialNumber" type="Label" parent="HBox"]
+visible = false
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "1."
diff --git a/GUI/player_ranking_panel.gd b/GUI/player_ranking_panel.gd
index 2f8822a..b933d26 100644
--- a/GUI/player_ranking_panel.gd
+++ b/GUI/player_ranking_panel.gd
@@ -104,7 +104,3 @@ func _on_refresh_button_pressed():
# 退出按钮点击
func _on_quit_button_pressed():
self.hide()
-
-# 添加到main.gd中调用
-func _handle_player_rankings_response(data):
- handle_player_rankings_response(data)
diff --git a/GlobalScript/Toast.gd b/GlobalScript/Toast.gd
index 91dfd04..53fc2b7 100644
--- a/GlobalScript/Toast.gd
+++ b/GlobalScript/Toast.gd
@@ -3,7 +3,7 @@ extends Node
const ToastScene = preload("res://components/ToastShow.tscn")
-static func show(text: String,
+func show(text: String,
color: Color = Color.WHITE,
duration: float = 3.0,
fade: float = 1.0) -> void:
diff --git a/MainGame.gd b/MainGame.gd
index 834bf88..428e331 100644
--- a/MainGame.gd
+++ b/MainGame.gd
@@ -12,14 +12,13 @@ extends Node
@onready var show_player_name : Label = $UI/GUI/HBox2/player_name # 显示玩家昵称
@onready var show_farm_name : Label = $UI/GUI/HBox2/farm_name # 显示农场名称
@onready var show_status_label : Label = $UI/GUI/HBox2/StatusLabel # 显示与服务器连接状态
-
@onready var network_status_label :Label = get_node("/root/main/UI/TCPNetworkManager/StatusLabel")
-@onready var network_manager = get_node("/root/main/UI/TCPNetworkManager")
+@onready var return_my_farm_button: Button = $UI/GUI/VBox/ReturnMyFarmButton
+
+
+@onready var crop_grid_container : GridContainer = $UI/CropStorePanel/ScrollContainer/Crop_Grid #种子商店格子
+@onready var player_bag_grid_container : GridContainer = $UI/PlayerBagPanel/ScrollContainer/Bag_Grid #玩家背包格子
-#种子商店格子
-@onready var crop_grid_container : GridContainer = $UI/CropStorePanel/ScrollContainer/Crop_Grid
-#玩家背包格子
-@onready var player_bag_grid_container : GridContainer = $UI/PlayerBagPanel/ScrollContainer/Bag_Grid
#作物品质按钮
@onready var green_bar : Button = $CopyNodes/GreenCrop #普通
@onready var white_blue_bar : Button = $CopyNodes/WhiteBlueCrop #稀有
@@ -33,10 +32,10 @@ extends Node
@onready var login_panel : PanelContainer = $UI/LoginPanel#登录注册面板
@onready var crop_store_panel : Panel = $UI/CropStorePanel#种子商店面板
@onready var player_bag_panel : Panel = $UI/PlayerBagPanel#玩家背包面板
-@onready var TCPNerworkManager : Panel = $UI/TCPNetworkManager#网络管理器
+@onready var network_manager : Panel = $UI/TCPNetworkManager#网络管理器
@onready var player_ranking_panel : Panel = $UI/PlayerRankingPanel#玩家排行榜面板
-@onready var return_my_farm_button: Button = $UI/GUI/VBox/ReturnMyFarmButton
+
var money: int = 500 # 默认每个人初始为100元
var experience: float = 0.0 # 初始每个玩家的经验为0
@@ -48,7 +47,6 @@ var dig_money : int = 1000 #开垦费用
#临时变量
var user_name : String = ""
var user_password : String = ""
-var farmname : String = ""
var login_data : Dictionary = {}
var data : Dictionary = {}
var buttons : Array = []
@@ -75,16 +73,13 @@ var visited_player_data : Dictionary = {} # 被访问玩家的数据
var crop_textures_cache : Dictionary = {} # 缓存已加载的作物图片
var crop_frame_counts : Dictionary = {} # 记录每种作物的帧数
+const client_version :String = "1.0.1" #记录客户端版本
-#-------------Godot自带方法-----------------
-func _on_quit_button_pressed():
- player_bag_panel.hide()
- pass
-
# 准备阶段
func _ready():
+ print("萌芽农场客户端 v" + client_version + " 启动")
_update_ui()
_create_farm_buttons() # 创建地块按钮
_update_farm_lots_state() # 初始更新地块状态
@@ -105,8 +100,7 @@ func _ready():
player_bag_panel.hide()
# 启动后稍等片刻尝试从服务器获取最新数据
- var timer = get_tree().create_timer(0.5)
- await timer.timeout
+ await get_tree().create_timer(0.5).timeout
_try_load_from_server()
#每时每刻都更新
@@ -115,8 +109,9 @@ func _physics_process(delta):
update_timer += delta
if update_timer >= update_interval:
update_timer = 0.0 # 重置计时器
+
#同步网络管理器的状态
- show_status_label.text = network_status_label.text
+ show_status_label.text = "服务器状态:"+network_status_label.text
show_status_label.modulate = network_status_label.modulate
if start_game == true:
@@ -131,21 +126,47 @@ func _physics_process(delta):
pass
pass
+func _on_open_store_button_pressed():
+ # 如果处于访问模式,不允许打开商店
+ if is_visiting_mode:
+ Toast.show("访问模式下无法使用商店", Color.ORANGE)
+ return
+
+ # 确保商店面板已初始化
+ crop_store_panel.init_store()
+ # 显示商店面板
+ crop_store_panel.show()
+ # 确保在最前面显示
+ crop_store_panel.move_to_front()
+ pass
+
+func _on_player_ranking_button_pressed() -> void:
+ player_ranking_panel.show()
+ pass
+
+func _on_return_my_farm_button_pressed() -> void:
+ # 如果当前处于访问模式,返回自己的农场
+ if is_visiting_mode:
+ return_to_my_farm()
+ else:
+ # 如果不在访问模式,这个按钮可能用于其他功能或者不做任何操作
+ print("当前已在自己的农场")
+
+
+
+
# 处理服务器作物更新消息
func _handle_crop_update(update_data):
# 检查是否是访问模式的更新
var is_visiting_update = update_data.get("is_visiting", false)
- var visited_player = update_data.get("visited_player", "")
if is_visiting_update and is_visiting_mode:
# 访问模式下的更新,更新被访问玩家的农场数据
farm_lots = update_data["farm_lots"]
- print("收到访问模式下的作物更新,被访问玩家:", visited_player)
elif not is_visiting_update and not is_visiting_mode:
# 正常模式下的更新,更新自己的农场数据
farm_lots = update_data["farm_lots"]
- print("收到自己农场的作物更新")
else:
# 状态不匹配,忽略更新
print("忽略不匹配的作物更新,当前访问模式:", is_visiting_mode, ",更新类型:", is_visiting_update)
@@ -220,18 +241,74 @@ func _handle_action_response(response_data):
Toast.show(message, Color.GREEN)
else:
Toast.show(message, Color.RED)
+
+ "remove_crop":
+ if success:
+ # 更新玩家数据
+ if updated_data.has("money"):
+ money = updated_data["money"]
+ if updated_data.has("farm_lots"):
+ farm_lots = updated_data["farm_lots"]
+
+ # 更新UI
+ _update_ui()
+ _update_farm_lots_state()
+ Toast.show(message, Color.GREEN)
+ else:
+ Toast.show(message, Color.RED)
+
+ "water_crop":
+ if success:
+ # 更新玩家数据
+ if updated_data.has("money"):
+ money = updated_data["money"]
+ if updated_data.has("farm_lots"):
+ farm_lots = updated_data["farm_lots"]
+
+ # 更新UI
+ _update_ui()
+ _update_farm_lots_state()
+ Toast.show(message, Color.CYAN)
+ else:
+ Toast.show(message, Color.RED)
+
+ "fertilize_crop":
+ if success:
+ # 更新玩家数据
+ if updated_data.has("money"):
+ money = updated_data["money"]
+ if updated_data.has("farm_lots"):
+ farm_lots = updated_data["farm_lots"]
+
+ # 更新UI
+ _update_ui()
+ _update_farm_lots_state()
+ Toast.show(message, Color.PURPLE)
+ else:
+ Toast.show(message, Color.RED)
+
+ "upgrade_land":
+ if success:
+ # 更新玩家数据
+ if updated_data.has("money"):
+ money = updated_data["money"]
+ if updated_data.has("farm_lots"):
+ farm_lots = updated_data["farm_lots"]
+
+ # 更新UI
+ _update_ui()
+ _update_farm_lots_state()
+ Toast.show(message, Color.GOLD)
+ else:
+ Toast.show(message, Color.RED)
# 处理玩家排行榜响应
func _handle_player_rankings_response(data):
- if player_ranking_panel and player_ranking_panel.has_method("_handle_player_rankings_response"):
- player_ranking_panel._handle_player_rankings_response(data)
+ player_ranking_panel.handle_player_rankings_response(data)
# 处理玩家游玩时间响应
func _handle_play_time_response(data):
- # 如果需要在主游戏中处理游玩时间,可以在这里添加代码
- # 目前只是将响应转发给排行榜面板
- if player_ranking_panel and player_ranking_panel.has_method("handle_play_time_response"):
- player_ranking_panel.handle_play_time_response(data)
+ player_ranking_panel.handle_play_time_response(data)
# 处理访问玩家响应
func _handle_visit_player_response(data):
@@ -334,12 +411,9 @@ func _handle_return_my_farm_response(data):
Toast.show("返回农场失败:" + message, Color.RED)
print("返回农场失败:", message)
-#-------------Godot自带方法-----------------
-#-------------自定义方法-----------------
-
#创建作物按钮
func _create_crop_button(crop_name: String, crop_quality: String) -> Button:
# 根据品质选择相应的进度条
@@ -372,21 +446,6 @@ func _create_crop_button(crop_name: String, crop_quality: String) -> Button:
return button
-# 打开商店按钮处理函数
-func _on_open_store_button_pressed():
- # 如果处于访问模式,不允许打开商店
- if is_visiting_mode:
- Toast.show("访问模式下无法使用商店", Color.ORANGE)
- return
-
- # 确保商店面板已初始化
- crop_store_panel.init_store()
- # 显示商店面板
- crop_store_panel.show()
- # 确保在最前面显示
- crop_store_panel.move_to_front()
- pass
-
# 初始化农场地块按钮 - 只在游戏开始时调用一次
@@ -421,7 +480,8 @@ func _update_farm_lots_state():
var lot = farm_lots[i]
var button = grid_container.get_child(i)
- var label = button.get_node("Label")
+ var label = button.get_node("crop_name")
+ var status_label = button.get_node("status_label")
var progressbar = button.get_node("ProgressBar")
# 更新作物图片
@@ -437,7 +497,21 @@ func _update_farm_lots_state():
else:
# 正常生长逻辑
var crop_name = lot["crop_type"]
- label.text = "[" + can_planted_crop[crop_name]["品质"] + "-" + lot["crop_type"] + "]"
+ label.text = "[" + can_planted_crop[crop_name]["品质"] + "-" + lot["crop_type"] +"]"
+ var status_text = ""
+ # 添加状态标识
+ var status_indicators = []
+ if lot.get("已浇水", false):
+ status_indicators.append("已浇水")#💧
+ if lot.get("已施肥", false):
+ status_indicators.append("已施肥")#🌱
+ if lot.get("土地等级", 0) >= 1:
+ status_indicators.append("等级:1")#⭐
+
+ if status_indicators.size() > 0:
+ status_text += " " + " ".join(status_indicators)
+ status_label.text = status_text
+
# 根据品质显示颜色
match can_planted_crop[crop_name]["品质"]:
"普通":
@@ -456,8 +530,13 @@ func _update_farm_lots_state():
progressbar.value = int(lot["grow_time"]) # 直接设置值,不使用动画
else:
# 已开垦但未种植的地块显示为空地
+ var land_text = "[空地"
+ if lot.get("土地等级", 0) >= 1:
+ status_label.text = "等级:1"
+ land_text += "]"
+
label.modulate = Color.GREEN#绿色
- label.text = "[" + "空地" + "]"
+ label.text = land_text
progressbar.hide()
else:
@@ -493,6 +572,9 @@ func _on_item_selected(index):
land_panel.show()
land_panel.selected_lot_index = index
selected_lot_index = index
+ # 更新按钮文本
+ if land_panel.has_method("_update_button_texts"):
+ land_panel._update_button_texts()
# 处理访问模式下的地块点击事件
func _on_visit_item_selected(index):
@@ -538,17 +620,7 @@ func _harvest_crop(index):
Toast.show("作物还未成熟", Color.RED)
-#铲除已死亡作物
-func root_out_crop(index):
- var lot = farm_lots[index]
- lot["is_planted"] = false
- lot["grow_time"] = 0
- Toast.show("从地块[" + str(index) + "]铲除了[" + lot["crop_type"] + "]作物", Color.YELLOW)
- lot["crop_type"] = ""
- _check_level_up()
- _update_ui()
- _update_farm_lots_state()
- pass
+
# 检查玩家是否可以升级
@@ -562,23 +634,6 @@ func _check_level_up():
crop_store_panel.init_store()
-
-
-#-------------自定义方法----------------
-
-
-func _on_player_ranking_button_pressed() -> void:
- player_ranking_panel.show()
- pass
-
-func _on_return_my_farm_button_pressed() -> void:
- # 如果当前处于访问模式,返回自己的农场
- if is_visiting_mode:
- return_to_my_farm()
- else:
- # 如果不在访问模式,这个按钮可能用于其他功能或者不做任何操作
- print("当前已在自己的农场")
-
# 返回自己的农场
func return_to_my_farm():
if not is_visiting_mode:
diff --git a/MainGame.tscn b/MainGame.tscn
index 692e6e9..0fa0c47 100644
--- a/MainGame.tscn
+++ b/MainGame.tscn
@@ -49,7 +49,7 @@ expand_mode = 2
modulate = Color(1, 0.670588, 0.490196, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
-text = "树萌芽"
+text = "玩家昵称:树萌芽"
[node name="farm_name_image" type="TextureRect" parent="UI/GUI/HBox2"]
layout_mode = 2
@@ -60,7 +60,7 @@ expand_mode = 3
modulate = Color(1, 0.858824, 0.623529, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
-text = "树萌芽的农场"
+text = "农场名称:树萌芽的农场"
[node name="status_label_image" type="TextureRect" parent="UI/GUI/HBox2"]
layout_mode = 2
@@ -304,7 +304,6 @@ text = "连接状态"
horizontal_alignment = 1
[node name="LandPanel" type="Panel" parent="UI"]
-visible = false
offset_left = 475.0
offset_top = 145.0
offset_right = 991.0
@@ -344,53 +343,49 @@ columns = 5
modulate = Color(1, 0.419608, 0.352941, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "开垦"
[node name="Plant_Button" type="Button" parent="UI/LandPanel/Grid"]
modulate = Color(1, 0.682353, 0, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "种植"
[node name="Remove_Button" type="Button" parent="UI/LandPanel/Grid"]
-visible = false
modulate = Color(1, 1, 0, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "铲除"
[node name="Harvest_Button" type="Button" parent="UI/LandPanel/Grid"]
modulate = Color(0.223529, 1, 0.290196, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "收获"
[node name="Water_Button" type="Button" parent="UI/LandPanel/Grid"]
-visible = false
modulate = Color(0, 1, 1, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "浇水"
[node name="Fertilize_Button" type="Button" parent="UI/LandPanel/Grid"]
-visible = false
modulate = Color(0.592157, 0.337255, 1, 1)
custom_minimum_size = Vector2(80, 80)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "施肥"
[node name="Upgrade_Button" type="Button" parent="UI/LandPanel/Grid"]
-visible = false
modulate = Color(0.0784314, 0.470588, 1, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
-theme_override_font_sizes/font_size = 30
+theme_override_font_sizes/font_size = 20
text = "升级"
[node name="PlayerRankingPanel" type="Panel" parent="UI"]
@@ -741,4 +736,3 @@ bounds_max = Vector2(500, 500)
[connection signal="pressed" from="UI/GUI/VBox/OpenStoreButton" to="." method="_on_open_store_button_pressed"]
[connection signal="pressed" from="UI/GUI/VBox/PlayerRankingButton" to="." method="_on_player_ranking_button_pressed"]
[connection signal="pressed" from="UI/CropStorePanel/QuitButton" to="." method="_on_quit_button_pressed"]
-[connection signal="pressed" from="UI/PlayerBagPanel/QuitButton" to="." method="_on_quit_button_pressed"]
diff --git a/Network/TCPNetworkManager.gd b/Network/TCPNetworkManager.gd
index e1ed281..5bc979a 100644
--- a/Network/TCPNetworkManager.gd
+++ b/Network/TCPNetworkManager.gd
@@ -17,9 +17,9 @@ var client: TCPClient = TCPClient.new()
# 服务器配置 - 支持多个服务器地址
var server_configs = [
- #{"host": "127.0.0.1", "port": 4040, "name": "本地服务器"},
+ {"host": "127.0.0.1", "port": 4040, "name": "本地服务器"},
#{"host": "192.168.1.110", "port": 4040, "name": "局域网服务器"},
- {"host": "47.108.90.0", "port": 4040, "name": "公网服务器"}#成都内网穿透
+ #{"host": "47.108.90.0", "port": 4040, "name": "公网服务器"}#成都内网穿透
]
var current_server_index = 0
@@ -94,9 +94,6 @@ func _on_data_received(data):
"ping":
return
"response":
- # 显示服务器响应
- if data.has("original"):
- var original = data.get("original", {})
return
"login_response":
# 处理登录响应
@@ -196,7 +193,8 @@ func sendLoginInfo(username, password):
client.send_data({
"type": "login",
"username": username,
- "password": password
+ "password": password,
+ "client_version": main_game.client_version
})
#发送注册信息
@@ -207,7 +205,8 @@ func sendRegisterInfo(username, password, farmname, player_name="", verification
"password": password,
"farm_name": farmname,
"player_name": player_name,
- "verification_code": verification_code
+ "verification_code": verification_code,
+ "client_version": main_game.client_version
})
#发送收获作物信息
@@ -247,6 +246,18 @@ func sendDigGround(lot_index):
})
return true
+#发送铲除作物信息
+func sendRemoveCrop(lot_index):
+ if not client.is_client_connected():
+ return false
+
+ client.send_data({
+ "type": "remove_crop",
+ "lot_index": lot_index,
+ "timestamp": Time.get_unix_time_from_system()
+ })
+ return true
+
#发送购买种子信息
func sendBuySeed(crop_name):
if not client.is_client_connected():
@@ -347,6 +358,42 @@ func sendReturnMyFarm():
})
return true
+#发送浇水作物信息
+func sendWaterCrop(lot_index):
+ if not client.is_client_connected():
+ return false
+
+ client.send_data({
+ "type": "water_crop",
+ "lot_index": lot_index,
+ "timestamp": Time.get_unix_time_from_system()
+ })
+ return true
+
+#发送施肥作物信息
+func sendFertilizeCrop(lot_index):
+ if not client.is_client_connected():
+ return false
+
+ client.send_data({
+ "type": "fertilize_crop",
+ "lot_index": lot_index,
+ "timestamp": Time.get_unix_time_from_system()
+ })
+ return true
+
+#发送升级土地信息
+func sendUpgradeLand(lot_index):
+ if not client.is_client_connected():
+ return false
+
+ client.send_data({
+ "type": "upgrade_land",
+ "lot_index": lot_index,
+ "timestamp": Time.get_unix_time_from_system()
+ })
+ return true
+
#检查是否连接到服务器
func is_connected_to_server():
return client.is_client_connected()
diff --git a/Server/BugFix_UI_Update_Missing.md b/Server/BugFix_UI_Update_Missing.md
new file mode 100644
index 0000000..f8a3e6f
--- /dev/null
+++ b/Server/BugFix_UI_Update_Missing.md
@@ -0,0 +1,241 @@
+# Bug修复:开垦和铲除操作UI更新缺失
+
+## 问题描述
+
+在开垦土地和铲除作物操作完成后,客户端的UI没有正确更新,具体表现为:
+
+1. **金钱数量不更新**:操作完成后,显示的金钱数量仍然是操作前的数值
+2. **等级和经验不更新**:如果操作导致等级或经验变化,UI没有反映
+3. **地块状态不同步**:虽然服务器数据已更新,但客户端显示可能不一致
+
+## 问题根源
+
+### 原始问题分析
+
+客户端的 `_handle_action_response()` 方法只处理了以下操作类型:
+- `harvest_crop` - 收获作物 ✅
+- `plant_crop` - 种植作物 ✅
+- `buy_seed` - 购买种子 ✅
+- `dig_ground` - 开垦土地 ✅
+
+但是缺少了:
+- `remove_crop` - 铲除作物 ❌
+
+### 代码问题位置
+
+```gdscript
+# MainGame.gd 中的 _handle_action_response 方法
+func _handle_action_response(response_data):
+ var action_type = response_data.get("action_type", "")
+ var success = response_data.get("success", false)
+ var message = response_data.get("message", "")
+ var updated_data = response_data.get("updated_data", {})
+
+ match action_type:
+ "harvest_crop":
+ # 处理收获响应 ✅
+ "plant_crop":
+ # 处理种植响应 ✅
+ "buy_seed":
+ # 处理购买响应 ✅
+ "dig_ground":
+ # 处理开垦响应 ✅
+ # 缺少 "remove_crop" 的处理 ❌
+```
+
+## 修复方案
+
+### 1. 添加铲除作物响应处理
+
+在 `MainGame.gd` 的 `_handle_action_response()` 方法中添加对 `remove_crop` 操作的处理:
+
+```gdscript
+"remove_crop":
+ if success:
+ # 更新玩家数据
+ if updated_data.has("money"):
+ money = updated_data["money"]
+ if updated_data.has("farm_lots"):
+ farm_lots = updated_data["farm_lots"]
+
+ # 更新UI
+ _update_ui()
+ _update_farm_lots_state()
+ Toast.show(message, Color.GREEN)
+ else:
+ Toast.show(message, Color.RED)
+```
+
+### 2. 优化客户端预验证
+
+为了提供更好的用户体验,在 `land_panel.gd` 中添加了更完善的预验证:
+
+#### 开垦操作预验证
+```gdscript
+# 检查玩家金钱是否足够
+var dig_cost = main_game.dig_money
+if main_game.money < dig_cost:
+ Toast.show("金钱不足,开垦土地需要 " + str(dig_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+# 检查地块是否已经开垦
+var lot = main_game.farm_lots[selected_lot_index]
+if lot.get("is_diged", false):
+ Toast.show("此地块已经开垦过了", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+```
+
+#### 铲除操作预验证
+```gdscript
+# 检查玩家金钱是否足够
+var removal_cost = 500
+if main_game.money < removal_cost:
+ Toast.show("金钱不足,铲除作物需要 " + str(removal_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+# 检查地块是否有作物
+var lot = main_game.farm_lots[selected_lot_index]
+if not lot.get("is_planted", false) or lot.get("crop_type", "") == "":
+ Toast.show("此地块没有种植作物", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+```
+
+### 3. 移除不必要的UI更新调用
+
+移除了 `land_panel.gd` 中不必要的 `main_game._update_ui()` 调用,因为服务器响应会统一处理UI更新。
+
+## 修复效果
+
+### 修复前的问题
+1. **开垦土地后**:
+ - 金钱显示:1000元 → 1000元 ❌ (实际应该减少)
+ - 地块状态:可能不同步
+ - 用户体验:困惑,不知道操作是否成功
+
+2. **铲除作物后**:
+ - 金钱显示:1000元 → 1000元 ❌ (实际应该减少500)
+ - 地块状态:可能显示仍有作物
+ - 用户体验:需要刷新页面才能看到变化
+
+### 修复后的正确行为
+1. **开垦土地后**:
+ - 金钱显示:1000元 → 0元 ✅ (正确扣除1000)
+ - 地块状态:立即显示为已开垦 ✅
+ - 用户体验:即时反馈,操作流畅
+
+2. **铲除作物后**:
+ - 金钱显示:1000元 → 500元 ✅ (正确扣除500)
+ - 地块状态:立即显示为空地 ✅
+ - 用户体验:即时反馈,操作流畅
+
+## 数据流程
+
+### 完整的操作流程
+1. **客户端预验证** → 检查金钱、地块状态等
+2. **发送请求** → 向服务器发送操作请求
+3. **服务器处理** → 验证并执行操作,更新数据
+4. **服务器响应** → 返回操作结果和更新后的数据
+5. **客户端处理响应** → 更新本地数据和UI显示
+6. **UI更新** → 刷新金钱、经验、地块状态等显示
+
+### 数据同步机制
+```gdscript
+# 服务器响应处理
+if success:
+ # 1. 更新本地数据
+ if updated_data.has("money"):
+ money = updated_data["money"]
+ if updated_data.has("farm_lots"):
+ farm_lots = updated_data["farm_lots"]
+
+ # 2. 刷新UI显示
+ _update_ui() # 更新金钱、经验、等级显示
+ _update_farm_lots_state() # 更新地块状态显示
+
+ # 3. 显示成功提示
+ Toast.show(message, Color.GREEN)
+```
+
+## 测试验证
+
+### 测试用例1:开垦土地UI更新
+1. 玩家有1000金钱
+2. 点击未开垦地块,选择开垦
+3. 操作成功后检查:
+ - 金钱显示是否减少1000 ✅
+ - 地块是否显示为已开垦 ✅
+ - 是否显示成功提示 ✅
+
+### 测试用例2:铲除作物UI更新
+1. 玩家有1000金钱,地块有作物
+2. 点击地块,选择铲除
+3. 操作成功后检查:
+ - 金钱显示是否减少500 ✅
+ - 地块是否显示为空地 ✅
+ - 是否显示成功提示 ✅
+
+### 测试用例3:操作失败时的UI状态
+1. 玩家金钱不足
+2. 尝试进行开垦或铲除操作
+3. 检查:
+ - UI数据不应该改变 ✅
+ - 显示错误提示 ✅
+ - 操作被正确阻止 ✅
+
+### 测试用例4:网络异常时的处理
+1. 断开网络连接
+2. 尝试进行操作
+3. 检查:
+ - 显示网络错误提示 ✅
+ - UI状态保持不变 ✅
+ - 不发送无效请求 ✅
+
+## 代码改进点
+
+### 1. 统一的响应处理
+所有游戏操作现在都有统一的响应处理机制:
+- 成功时更新数据和UI
+- 失败时显示错误信息
+- 保持数据一致性
+
+### 2. 改进的用户体验
+- 添加了操作进行中的提示("正在开垦土地...")
+- 提供了详细的错误信息
+- 即时的UI反馈
+
+### 3. 更好的错误处理
+- 客户端预验证减少无效请求
+- 网络错误的友好提示
+- 服务器错误的正确显示
+
+## 防止类似问题的建议
+
+### 1. 代码审查检查点
+- 新增操作类型时,确保在 `_handle_action_response` 中添加对应处理
+- 检查所有UI更新是否通过统一的响应处理机制
+- 验证客户端和服务器的数据同步
+
+### 2. 测试覆盖
+- 为每个新操作添加UI更新测试用例
+- 测试成功和失败场景的UI表现
+- 验证网络异常情况下的行为
+
+### 3. 开发规范
+- 所有游戏操作都应该通过服务器处理
+- 客户端只做预验证和UI更新
+- 保持数据流的一致性
+
+## 总结
+
+这个bug修复确保了:
+- ✅ **数据一致性**:客户端UI与服务器数据保持同步
+- ✅ **用户体验**:操作后立即看到结果反馈
+- ✅ **错误处理**:完善的错误提示和状态管理
+- ✅ **代码质量**:统一的响应处理机制
+- ✅ **可维护性**:清晰的数据流和处理逻辑
+
+通过这个修复,玩家在进行开垦和铲除操作时将获得与收获作物操作一致的流畅体验。
\ No newline at end of file
diff --git a/Server/BugFix_VisitorMode_LastLoginTime.md b/Server/BugFix_VisitorMode_LastLoginTime.md
new file mode 100644
index 0000000..298a9dd
--- /dev/null
+++ b/Server/BugFix_VisitorMode_LastLoginTime.md
@@ -0,0 +1,184 @@
+# Bug修复:访客模式下最后登录时间错误更新
+
+## 问题描述
+
+在原始代码中,当玩家处于访客模式访问其他玩家的农场时,被访问玩家的最后登录时间会被意外更新。这是一个严重的数据完整性问题,因为:
+
+1. **数据不准确**:被访问玩家实际上没有登录,但最后登录时间被更新了
+2. **逻辑错误**:只有玩家真正登录时,最后登录时间才应该被更新
+3. **影响统计**:这会影响玩家活跃度统计和排行榜数据的准确性
+
+## 问题根源
+
+### 原始问题代码
+
+```python
+def update_crops_growth(self):
+ """更新所有玩家的作物生长状态"""
+ # 获取所有玩家存档文件
+ save_files = glob.glob(os.path.join("game_saves", "*.json"))
+
+ for save_file in save_files:
+ try:
+ # 从文件名提取账号ID
+ account_id = os.path.basename(save_file).split('.')[0]
+
+ # 加载玩家数据
+ player_data = self.load_player_data(account_id)
+ # ... 更新作物生长状态 ...
+
+ # 如果有作物更新,保存玩家数据
+ if growth_updated:
+ self.save_player_data(account_id, player_data) # 问题在这里!
+```
+
+**问题分析:**
+- 系统遍历所有玩家存档文件,包括离线玩家
+- 当保存玩家数据时,可能会触发其他逻辑更新最后登录时间
+- 访客模式下,被访问玩家的数据被加载和保存,导致时间戳更新
+
+## 修复方案
+
+### 1. 只更新在线玩家的作物生长状态
+
+```python
+def update_crops_growth(self):
+ """更新所有玩家的作物生长状态"""
+ # 只更新在线玩家的作物生长状态,避免影响离线玩家的数据
+ for client_id, user_info in self.user_data.items():
+ if not user_info.get("logged_in", False):
+ continue
+
+ username = user_info.get("username")
+ if not username:
+ continue
+
+ try:
+ # 加载玩家数据
+ player_data = self.load_player_data(username)
+ # ... 更新作物生长状态 ...
+
+ # 如果有作物更新,保存玩家数据
+ if growth_updated:
+ self.save_player_data(username, player_data)
+```
+
+**修复要点:**
+- ✅ 只遍历在线玩家(`self.user_data`)
+- ✅ 检查玩家登录状态(`logged_in: True`)
+- ✅ 避免处理离线玩家的数据
+
+### 2. 优化访客模式的数据推送
+
+```python
+def _push_crop_update_to_player(self, account_id, player_data):
+ # ... 现有代码 ...
+
+ if visiting_mode and visiting_target:
+ # 如果处于访问模式,发送被访问玩家的更新数据
+ # 注意:这里只读取数据,不修改被访问玩家的数据
+ target_player_data = self.load_player_data(visiting_target)
+ if target_player_data:
+ # 检查被访问玩家是否也在线
+ target_client_id = None
+ for cid, user_info in self.user_data.items():
+ if user_info.get("username") == visiting_target and user_info.get("logged_in", False):
+ target_client_id = cid
+ break
+
+ update_message = {
+ "type": "crop_update",
+ "farm_lots": target_player_data.get("farm_lots", []),
+ "timestamp": time.time(),
+ "is_visiting": True,
+ "visited_player": visiting_target,
+ "target_online": target_client_id is not None # 新增:标记被访问玩家是否在线
+ }
+```
+
+**优化要点:**
+- ✅ 明确标注只读取数据,不修改
+- ✅ 检查被访问玩家是否在线
+- ✅ 提供在线状态信息给客户端
+
+## 修复效果
+
+### 修复前的问题
+1. **错误场景**:
+ - 玩家A访问玩家B的农场
+ - 系统更新所有玩家的作物生长状态
+ - 玩家B的数据被加载、修改、保存
+ - 玩家B的最后登录时间被意外更新
+
+2. **数据污染**:
+ ```
+ 玩家B实际最后登录:2024-01-01 10:00:00
+ 被访问后错误更新为:2024-01-02 15:30:00 ❌ 错误!
+ ```
+
+### 修复后的正确行为
+1. **正确场景**:
+ - 玩家A访问玩家B的农场
+ - 系统只更新在线玩家(玩家A)的作物生长状态
+ - 玩家B的数据只被读取,不被修改
+ - 玩家B的最后登录时间保持不变
+
+2. **数据准确**:
+ ```
+ 玩家B实际最后登录:2024-01-01 10:00:00
+ 访问后仍然保持:2024-01-01 10:00:00 ✅ 正确!
+ ```
+
+## 测试验证
+
+### 测试用例1:访客模式数据完整性
+1. 玩家A登录游戏
+2. 玩家A访问离线玩家B的农场
+3. 等待作物生长更新周期
+4. 检查玩家B的最后登录时间是否保持不变
+
+**预期结果**:玩家B的最后登录时间不应该改变
+
+### 测试用例2:在线玩家正常更新
+1. 玩家A和玩家B都在线
+2. 玩家A访问玩家B的农场
+3. 等待作物生长更新周期
+4. 检查两个玩家的作物是否正常生长
+
+**预期结果**:两个玩家的作物都应该正常生长
+
+### 测试用例3:离线玩家数据保护
+1. 确保有离线玩家的存档文件
+2. 在线玩家进行游戏操作
+3. 检查离线玩家的数据是否被意外修改
+
+**预期结果**:离线玩家的数据应该保持不变
+
+## 代码审查要点
+
+在未来的开发中,需要注意以下几点:
+
+1. **数据修改原则**:
+ - 只修改当前在线玩家的数据
+ - 访问其他玩家数据时,优先使用只读操作
+ - 避免在定时任务中修改离线玩家数据
+
+2. **时间戳更新**:
+ - 最后登录时间只在真正登录时更新
+ - 避免在数据保存时自动更新时间戳
+ - 区分数据修改和时间戳更新的逻辑
+
+3. **访客模式处理**:
+ - 明确区分访客模式和正常模式
+ - 访客模式下只读取数据,不修改
+ - 提供足够的状态信息给客户端
+
+## 总结
+
+这个bug修复确保了:
+- ✅ 数据完整性:只有真正登录的玩家才会更新最后登录时间
+- ✅ 性能优化:只处理在线玩家的数据,减少不必要的文件操作
+- ✅ 逻辑正确:访客模式下不会影响被访问玩家的数据
+- ✅ 可维护性:代码逻辑更清晰,易于理解和维护
+
+通过这个修复,游戏的数据统计将更加准确,玩家的隐私和数据完整性得到更好的保护。
\ No newline at end of file
diff --git a/Server/Feature_RemoveCrop.md b/Server/Feature_RemoveCrop.md
new file mode 100644
index 0000000..6bbd508
--- /dev/null
+++ b/Server/Feature_RemoveCrop.md
@@ -0,0 +1,277 @@
+# 铲除作物功能实现
+
+## 功能概述
+
+实现了玩家可以花费500金钱铲除地块上的作物,将地块变成空地的功能。这个功能完全基于服务器端处理,确保数据的一致性和安全性。
+
+## 功能特点
+
+- **费用固定**:铲除任何作物都需要花费500金钱
+- **服务器验证**:所有验证和处理都在服务器端完成
+- **状态重置**:铲除后地块变成空地,可以重新种植
+- **访客保护**:访客模式下无法进行铲除操作
+- **实时更新**:操作完成后立即更新客户端显示
+
+## 实现架构
+
+### 1. 服务器端实现
+
+#### 消息路由
+```python
+elif message_type == "remove_crop":
+ return self._handle_remove_crop(client_id, message)
+```
+
+#### 主处理方法
+```python
+def _handle_remove_crop(self, client_id, message):
+ """处理铲除作物请求"""
+ # 检查用户是否已登录
+ logged_in, response = self._check_user_logged_in(client_id, "铲除作物", "remove_crop")
+ if not logged_in:
+ return self.send_data(client_id, response)
+
+ # 获取玩家数据
+ player_data, username, response = self._load_player_data_with_check(client_id, "remove_crop")
+ if not player_data:
+ return self.send_data(client_id, response)
+
+ lot_index = message.get("lot_index", -1)
+
+ # 验证地块索引
+ if lot_index < 0 or lot_index >= len(player_data.get("farm_lots", [])):
+ return self._send_action_error(client_id, "remove_crop", "无效的地块索引")
+
+ lot = player_data["farm_lots"][lot_index]
+
+ # 检查地块状态
+ if not lot.get("is_planted", False) or not lot.get("crop_type", ""):
+ return self._send_action_error(client_id, "remove_crop", "此地块没有种植作物")
+
+ # 处理铲除
+ return self._process_crop_removal(client_id, player_data, username, lot, lot_index)
+```
+
+#### 铲除处理逻辑
+```python
+def _process_crop_removal(self, client_id, player_data, username, lot, lot_index):
+ """处理铲除作物逻辑"""
+ # 铲除费用
+ removal_cost = 500
+
+ # 检查玩家金钱是否足够
+ if player_data["money"] < removal_cost:
+ return self._send_action_error(client_id, "remove_crop", f"金钱不足,铲除作物需要 {removal_cost} 金钱")
+
+ # 获取作物名称用于日志
+ crop_type = lot.get("crop_type", "未知作物")
+
+ # 执行铲除操作
+ player_data["money"] -= removal_cost
+ lot["is_planted"] = False
+ lot["crop_type"] = ""
+ lot["grow_time"] = 0
+ lot["is_dead"] = False # 重置死亡状态
+
+ # 保存玩家数据
+ self.save_player_data(username, player_data)
+
+ # 发送作物更新
+ self._push_crop_update_to_player(username, player_data)
+
+ self.log('INFO', f"玩家 {username} 铲除了地块 {lot_index} 的作物 {crop_type},花费 {removal_cost} 金钱", 'SERVER')
+
+ return self.send_data(client_id, {
+ "type": "action_response",
+ "action_type": "remove_crop",
+ "success": True,
+ "message": f"成功铲除作物 {crop_type},花费 {removal_cost} 金钱",
+ "updated_data": {
+ "money": player_data["money"],
+ "farm_lots": player_data["farm_lots"]
+ }
+ })
+```
+
+### 2. 网络通信
+
+#### 客户端发送请求
+```gdscript
+#发送铲除作物信息
+func sendRemoveCrop(lot_index):
+ if not client.is_client_connected():
+ return false
+
+ client.send_data({
+ "type": "remove_crop",
+ "lot_index": lot_index,
+ "timestamp": Time.get_unix_time_from_system()
+ })
+ return true
+```
+
+### 3. 客户端UI实现
+
+#### 按钮文本更新
+```gdscript
+# 更新按钮文本
+func _update_button_texts():
+ dig_button.text = "开垦"+"\n花费:"+str(main_game.dig_money)
+ remove_button.text = "铲除"+"\n花费:500"
+```
+
+#### 铲除操作处理
+```gdscript
+#铲除
+func _on_remove_button_pressed():
+ # 检查是否处于访问模式
+ if main_game.is_visiting_mode:
+ Toast.show("访问模式下无法铲除作物", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查玩家金钱是否足够
+ var removal_cost = 500
+ if main_game.money < removal_cost:
+ Toast.show("金钱不足,铲除作物需要 " + str(removal_cost) + " 金钱", Color.RED, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 检查地块是否有作物
+ var lot = main_game.farm_lots[selected_lot_index]
+ if not lot.get("is_planted", false) or lot.get("crop_type", "") == "":
+ Toast.show("此地块没有种植作物", Color.ORANGE, 2.0, 1.0)
+ self.hide()
+ return
+
+ # 发送铲除作物请求到服务器
+ if network_manager and network_manager.is_connected_to_server():
+ if network_manager.sendRemoveCrop(selected_lot_index):
+ Toast.show("正在铲除作物...", Color.YELLOW, 1.5, 1.0)
+ self.hide()
+ else:
+ Toast.show("发送铲除请求失败", Color.RED, 2.0, 1.0)
+ self.hide()
+ else:
+ Toast.show("网络未连接,无法铲除作物", Color.RED, 2.0, 1.0)
+ self.hide()
+```
+
+## 验证机制
+
+### 服务器端验证
+1. **用户登录验证**:确保用户已登录
+2. **地块索引验证**:检查地块索引是否有效
+3. **地块状态验证**:确保地块有作物可以铲除
+4. **金钱验证**:检查玩家金钱是否足够支付铲除费用
+
+### 客户端预验证
+1. **访问模式检查**:访客模式下禁止操作
+2. **金钱预检查**:提前检查金钱是否足够
+3. **地块状态预检查**:确保地块有作物
+4. **网络连接检查**:确保能够发送请求
+
+## 操作流程
+
+### 正常流程
+1. 玩家点击地块,显示操作面板
+2. 面板显示铲除按钮和费用信息
+3. 玩家点击铲除按钮
+4. 客户端进行预验证
+5. 发送铲除请求到服务器
+6. 服务器验证并处理请求
+7. 服务器返回操作结果
+8. 客户端更新UI显示
+
+### 错误处理
+- **金钱不足**:显示错误提示,不发送请求
+- **无作物**:显示提示信息,不发送请求
+- **访客模式**:显示权限提示,不发送请求
+- **网络错误**:显示网络错误提示
+- **服务器错误**:显示服务器返回的错误信息
+
+## 数据更新
+
+### 地块状态重置
+```python
+lot["is_planted"] = False # 取消种植状态
+lot["crop_type"] = "" # 清空作物类型
+lot["grow_time"] = 0 # 重置生长时间
+lot["is_dead"] = False # 重置死亡状态
+```
+
+### 玩家数据更新
+```python
+player_data["money"] -= removal_cost # 扣除金钱
+```
+
+### 实时同步
+- 服务器保存玩家数据到文件
+- 推送作物更新到客户端
+- 客户端接收并更新UI显示
+
+## 使用场景
+
+1. **清理死亡作物**:当作物死亡时,玩家可以花费金钱清理
+2. **重新规划农场**:玩家想要种植不同作物时
+3. **紧急处理**:当玩家需要快速清理地块时
+4. **策略调整**:根据市场需求调整种植策略
+
+## 安全考虑
+
+1. **服务器权威**:所有验证和处理都在服务器端
+2. **数据一致性**:确保客户端和服务器数据同步
+3. **防作弊**:客户端无法直接修改游戏数据
+4. **访问控制**:访客模式下无法进行破坏性操作
+
+## 扩展性
+
+该功能设计具有良好的扩展性:
+
+1. **费用可配置**:可以根据作物类型设置不同的铲除费用
+2. **条件扩展**:可以添加更多的铲除条件(如等级要求)
+3. **奖励机制**:可以在铲除时给予部分资源回收
+4. **工具系统**:可以引入铲子等工具来影响铲除效果
+
+## 测试用例
+
+### 测试用例1:正常铲除
+1. 玩家有足够金钱(≥500)
+2. 地块有作物
+3. 点击铲除按钮
+4. 验证:金钱减少500,地块变成空地
+
+### 测试用例2:金钱不足
+1. 玩家金钱不足(<500)
+2. 地块有作物
+3. 点击铲除按钮
+4. 验证:显示金钱不足提示,操作失败
+
+### 测试用例3:无作物地块
+1. 玩家有足够金钱
+2. 地块为空地
+3. 点击铲除按钮
+4. 验证:显示无作物提示,操作失败
+
+### 测试用例4:访客模式
+1. 玩家处于访客模式
+2. 点击铲除按钮
+3. 验证:显示权限提示,操作失败
+
+### 测试用例5:网络断开
+1. 网络连接断开
+2. 点击铲除按钮
+3. 验证:显示网络错误提示,操作失败
+
+## 总结
+
+铲除作物功能的实现遵循了以下设计原则:
+
+- ✅ **服务器权威**:所有关键逻辑在服务器端处理
+- ✅ **用户体验**:提供清晰的费用信息和操作反馈
+- ✅ **数据安全**:多层验证确保数据完整性
+- ✅ **错误处理**:完善的错误提示和处理机制
+- ✅ **代码复用**:利用现有的验证和处理框架
+- ✅ **可维护性**:清晰的代码结构和文档
+
+这个功能为玩家提供了更灵活的农场管理选项,同时保持了游戏的平衡性和数据安全性。
\ No newline at end of file
diff --git a/Server/Feature_Summary.md b/Server/Feature_Summary.md
new file mode 100644
index 0000000..c243f8b
--- /dev/null
+++ b/Server/Feature_Summary.md
@@ -0,0 +1,236 @@
+# 萌芽农场新功能实现总结
+
+## 功能概述
+
+本次更新成功实现了三个重要的农场管理功能:浇水、施肥、升级土地。这些功能为玩家提供了更丰富的农场管理体验和策略选择。
+
+## 实现的功能
+
+### 1. 浇水功能 💧
+- **费用**:50金钱
+- **效果**:作物直接生长1%,如果达到100%直接成熟
+- **限制**:每天每块地只能浇水一次,每日自动重置
+- **状态显示**:浇过水的地块显示💧图标
+- **验证**:检查金钱、作物状态、是否已浇水等
+
+### 2. 施肥功能 🌱
+- **费用**:150金钱
+- **效果**:作物在10分钟内以双倍速度生长
+- **限制**:每个作物只能施肥一次
+- **状态显示**:施过肥的地块显示🌱图标
+- **时间管理**:10分钟后自动清除施肥状态
+
+### 3. 土地升级功能 ⭐
+- **费用**:1000金钱
+- **效果**:永久让这块土地的作物以1.5倍速度生长
+- **限制**:每块土地只能升级一次
+- **状态显示**:升级过的土地显示⭐图标
+- **持久性**:升级效果永久有效
+
+## 技术实现
+
+### 服务器端实现
+
+#### 1. 消息路由扩展
+```python
+# 在_handle_message中添加新的消息类型
+elif message_type == "water_crop":
+ return self._handle_water_crop(client_id, message)
+elif message_type == "fertilize_crop":
+ return self._handle_fertilize_crop(client_id, message)
+elif message_type == "upgrade_land":
+ return self._handle_upgrade_land(client_id, message)
+```
+
+#### 2. 处理方法实现
+- `_handle_water_crop()` - 处理浇水请求
+- `_handle_fertilize_crop()` - 处理施肥请求
+- `_handle_upgrade_land()` - 处理升级请求
+- `_process_watering()` - 浇水逻辑处理
+- `_process_fertilizing()` - 施肥逻辑处理
+- `_process_land_upgrade()` - 升级逻辑处理
+
+#### 3. 作物生长系统增强
+```python
+# 计算生长速度倍数
+growth_multiplier = 1.0
+
+# 土地等级影响:1级土地提供1.5倍生长速度
+if land_level >= 1:
+ growth_multiplier *= 1.5
+
+# 施肥影响:10分钟内双倍生长速度
+if fertilized and within_10_minutes:
+ growth_multiplier *= 2.0
+```
+
+#### 4. 状态管理
+- 每日重置浇水状态
+- 施肥时间戳管理
+- 土地等级持久化
+
+### 客户端实现
+
+#### 1. 网络通信扩展
+```gdscript
+# 新增发送方法
+func sendWaterCrop(lot_index)
+func sendFertilizeCrop(lot_index)
+func sendUpgradeLand(lot_index)
+```
+
+#### 2. UI响应处理
+```gdscript
+# 在_handle_action_response中添加新的响应类型
+"water_crop", "fertilize_crop", "upgrade_land"
+```
+
+#### 3. 地块面板功能
+- 显示浇水、施肥、升级按钮
+- 添加费用显示
+- 实现预验证逻辑
+- 错误提示处理
+
+#### 4. 状态显示增强
+```gdscript
+# 添加状态标识
+if lot.get("已浇水", false):
+ status_indicators.append("💧")
+if lot.get("已施肥", false):
+ status_indicators.append("🌱")
+if lot.get("土地等级", 0) >= 1:
+ status_indicators.append("⭐")
+```
+
+## 数据结构扩展
+
+### 地块数据结构
+```json
+{
+ "crop_type": "作物名称",
+ "grow_time": 当前生长时间,
+ "max_grow_time": 最大生长时间,
+ "is_planted": true/false,
+ "is_diged": true/false,
+ "is_dead": true/false,
+ "已浇水": true/false,
+ "已施肥": true/false,
+ "土地等级": 0/1,
+ "施肥时间": 时间戳
+}
+```
+
+### 玩家数据扩展
+```json
+{
+ "last_water_reset_date": "2024-01-01",
+ // ... 其他现有字段
+}
+```
+
+## 功能特性
+
+### 1. 效果叠加
+- 土地升级 + 施肥 = 3倍生长速度(1.5 × 2.0)
+- 浇水 + 施肥 + 升级 = 最大化生长效率
+
+### 2. 时间管理
+- 浇水:每日重置(基于日期)
+- 施肥:10分钟时效
+- 升级:永久有效
+
+### 3. 经济平衡
+- 浇水:低成本,即时效果
+- 施肥:中等成本,短期加速
+- 升级:高成本,长期投资
+
+### 4. 用户体验
+- 清晰的状态图标显示
+- 详细的错误提示
+- 即时的UI反馈
+- 流畅的操作体验
+
+## 安全性和验证
+
+### 1. 服务器端验证
+- 用户登录状态检查
+- 金钱充足性验证
+- 地块状态验证
+- 重复操作防护
+
+### 2. 客户端预验证
+- 减少无效请求
+- 提供即时反馈
+- 改善用户体验
+
+### 3. 数据一致性
+- 服务器权威验证
+- 客户端状态同步
+- 错误状态恢复
+
+## 扩展性设计
+
+### 1. 可配置参数
+- 浇水费用:50金钱(可调整)
+- 施肥费用:150金钱(可调整)
+- 升级费用:1000金钱(可调整)
+- 施肥持续时间:10分钟(可调整)
+
+### 2. 未来扩展可能
+- 多级土地升级
+- 不同类型的肥料
+- 天气系统影响
+- 季节性效果
+
+## 测试覆盖
+
+### 1. 功能测试
+- 正常操作流程
+- 边界条件测试
+- 错误处理验证
+
+### 2. 集成测试
+- 客户端-服务器通信
+- 数据同步验证
+- 状态持久化测试
+
+### 3. 用户体验测试
+- 操作流畅性
+- 提示信息准确性
+- 视觉反馈效果
+
+## 性能影响
+
+### 1. 服务器性能
+- 作物生长计算复杂度略有增加
+- 状态管理内存占用轻微增长
+- 网络消息量适度增加
+
+### 2. 客户端性能
+- UI更新频率保持稳定
+- 状态显示计算开销很小
+- 用户操作响应及时
+
+## 维护和监控
+
+### 1. 日志记录
+- 所有操作都有详细日志
+- 错误情况记录完整
+- 便于问题排查
+
+### 2. 数据监控
+- 功能使用频率统计
+- 经济系统平衡监控
+- 用户行为分析
+
+## 总结
+
+本次功能实现成功为萌芽农场游戏添加了三个重要的农场管理功能,显著提升了游戏的策略性和可玩性。实现过程中注重了:
+
+- **完整性**:从服务器到客户端的完整实现
+- **安全性**:多层验证和错误处理
+- **用户体验**:清晰的反馈和流畅的操作
+- **可维护性**:清晰的代码结构和文档
+- **扩展性**:为未来功能扩展预留空间
+
+这些功能为玩家提供了更多的农场管理选择,增加了游戏的深度和策略性,同时保持了良好的游戏平衡性。
\ No newline at end of file
diff --git a/Server/JsonEdit/README.md b/Server/JsonEdit/README.md
deleted file mode 100644
index ebd3a7a..0000000
--- a/Server/JsonEdit/README.md
+++ /dev/null
@@ -1,100 +0,0 @@
-# JSON 批量编辑器
-
-一个简洁强大的JSON编辑工具,专门用于批量添加键值对到JSON文件中的所有对象。
-
-## 🚀 功能特点
-
-- **批量添加属性**: 一键为JSON中所有对象添加新的键值对
-- **智能类型识别**: 自动识别并转换数据类型
-- **支持所有JSON数据类型**: 字符串、数字、布尔值、对象、数组、null
-- **文件上传**: 支持JSON文件拖拽上传
-- **即时下载**: 编辑完成后立即下载修改后的JSON文件
-- **无需服务器**: 纯前端实现,直接在浏览器中运行
-
-## 📋 支持的数据类型
-
-| 类型 | 输入示例 | 转换结果 |
-|------|----------|----------|
-| 字符串 | `hello world` | `"hello world"` |
-| 整数 | `123` | `123` |
-| 小数 | `3.14` | `3.14` |
-| 布尔值 | `true` 或 `false` | `true` 或 `false` |
-| 空值 | `null` | `null` |
-| 对象 | `{"key": "value"}` | `{"key": "value"}` |
-| 数组 | `[1, 2, 3]` | `[1, 2, 3]` |
-| 空字符串 | *(留空)* | `""` |
-
-## 🎯 使用方法
-
-### 1. 加载JSON数据
-- **方法一**: 点击"上传JSON文件"按钮选择文件
-- **方法二**: 直接在编辑器中粘贴JSON数据
-- **方法三**: 点击"加载示例数据"使用预设数据
-
-### 2. 批量添加属性
-1. 在"键名"字段输入要添加的属性名,如: `能否购买`
-2. 在"键值"字段输入属性值,如: `true`
-3. 点击"批量添加到所有对象"按钮
-4. 系统会自动为JSON中的每个对象添加该属性
-
-### 3. 下载结果
-点击"下载修改后的JSON"按钮保存编辑后的文件
-
-## 💡 使用示例
-
-### 原始JSON:
-```json
-{
- "小麦": {
- "花费": 120,
- "收益": 100,
- "品质": "普通"
- },
- "稻谷": {
- "花费": 100,
- "收益": 120,
- "品质": "普通"
- }
-}
-```
-
-### 添加属性: 键名=`能否购买`, 键值=`true`
-
-### 结果JSON:
-```json
-{
- "小麦": {
- "花费": 120,
- "收益": 100,
- "品质": "普通",
- "能否购买": true
- },
- "稻谷": {
- "花费": 100,
- "收益": 120,
- "品质": "普通",
- "能否购买": true
- }
-}
-```
-
-## 🔧 快速开始
-
-1. 直接在浏览器中打开 `templates/json_editor.html` 文件
-2. 无需安装任何依赖或服务器
-3. 开始使用批量编辑功能
-
-## ⚠️ 注意事项
-
-- 工具会递归处理嵌套对象,为所有找到的对象添加指定属性
-- 数组元素如果是对象,也会被添加属性
-- 确保JSON格式正确,否则无法处理
-- 修改前建议备份原始文件
-
-## 🎨 界面说明
-
-- **左侧边栏**: 文件操作、批量编辑功能、快速示例
-- **右侧编辑区**: JSON数据显示和编辑
-- **智能提示**: 实时显示操作结果和错误信息
-
-这个工具特别适合游戏开发、配置文件管理等需要批量修改JSON数据的场景。
\ No newline at end of file
diff --git a/Server/JsonEdit/example_formats.md b/Server/JsonEdit/example_formats.md
deleted file mode 100644
index cd836a7..0000000
--- a/Server/JsonEdit/example_formats.md
+++ /dev/null
@@ -1,65 +0,0 @@
-# JSON格式化示例
-
-以下展示三种不同的JSON格式化效果:
-
-## 原始数据
-```json
-{"小麦":{"花费":120,"收益":100,"品质":"普通"},"稻谷":{"花费":100,"收益":120,"品质":"普通"}}
-```
-
-## 1. 标准格式化(2空格缩进)
-```json
-{
- "小麦": {
- "花费": 120,
- "收益": 100,
- "品质": "普通"
- },
- "稻谷": {
- "花费": 100,
- "收益": 120,
- "品质": "普通"
- }
-}
-```
-
-## 2. 最小化(压缩)
-```json
-{"小麦":{"花费":120,"收益":100,"品质":"普通"},"稻谷":{"花费":100,"收益":120,"品质":"普通"}}
-```
-
-## 3. 一行化(一个对象一行)
-```json
-{
- "小麦": {"花费":120,"收益":100,"品质":"普通"},
- "稻谷": {"花费":100,"收益":120,"品质":"普通"}
-}
-```
-
-## 使用场景
-
-- **标准格式化**: 适合阅读和编辑,开发时使用
-- **最小化**: 适合网络传输,节省带宽
-- **一行化**: 适合比较不同对象,每行一个对象便于查看差异
-
-## 批量添加属性示例
-
-添加键名: `能否购买`, 键值: `true`
-
-### 结果:
-```json
-{
- "小麦": {
- "花费": 120,
- "收益": 100,
- "品质": "普通",
- "能否购买": true
- },
- "稻谷": {
- "花费": 100,
- "收益": 120,
- "品质": "普通",
- "能否购买": true
- }
-}
-```
\ No newline at end of file
diff --git a/Server/JsonEdit/json_editor.py b/Server/JsonEdit/json_editor.py
deleted file mode 100644
index eac0890..0000000
--- a/Server/JsonEdit/json_editor.py
+++ /dev/null
@@ -1,267 +0,0 @@
-#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-from flask import Flask, request, jsonify, render_template, send_file
-import json
-import os
-from datetime import datetime
-
-app = Flask(__name__)
-
-class JSONFormatter:
- """JSON格式化工具类"""
-
- @staticmethod
- def format_standard(data, indent=2):
- """标准格式化 - 带缩进的可读格式"""
- return json.dumps(data, ensure_ascii=False, indent=indent)
-
- @staticmethod
- def minify(data):
- """最小化 - 压缩去除空格"""
- return json.dumps(data, ensure_ascii=False, separators=(',', ':'))
-
- @staticmethod
- def one_line_per_object(data):
- """一行化 - 每个对象/元素占一行"""
- if isinstance(data, list):
- # 如果是数组,每个元素占一行
- lines = ['[']
- for i, item in enumerate(data):
- comma = ',' if i < len(data) - 1 else ''
- lines.append(f' {json.dumps(item, ensure_ascii=False)}{comma}')
- lines.append(']')
- return '\n'.join(lines)
-
- elif isinstance(data, dict):
- # 如果是对象,每个键值对占一行
- lines = ['{']
- keys = list(data.keys())
- for i, key in enumerate(keys):
- comma = ',' if i < len(keys) - 1 else ''
- value_str = json.dumps(data[key], ensure_ascii=False)
- lines.append(f' {json.dumps(key, ensure_ascii=False)}: {value_str}{comma}')
- lines.append('}')
- return '\n'.join(lines)
-
- else:
- # 基本类型直接返回
- return json.dumps(data, ensure_ascii=False)
-
-@app.route('/')
-def index():
- """主页"""
- return render_template('json_editor.html')
-
-@app.route('/api/format', methods=['POST'])
-def format_json():
- """JSON格式化API"""
- try:
- data = request.get_json()
- content = data.get('content', '')
- format_type = data.get('format_type', 'standard') # standard, minify, oneline
-
- if not content.strip():
- return jsonify({'success': False, 'message': '请提供JSON内容'})
-
- # 解析JSON
- try:
- json_data = json.loads(content)
- except json.JSONDecodeError as e:
- return jsonify({'success': False, 'message': f'JSON格式错误: {str(e)}'})
-
- # 根据类型格式化
- formatter = JSONFormatter()
-
- if format_type == 'standard':
- formatted = formatter.format_standard(json_data)
- message = 'JSON标准格式化完成'
- elif format_type == 'minify':
- formatted = formatter.minify(json_data)
- message = 'JSON最小化完成'
- elif format_type == 'oneline':
- formatted = formatter.one_line_per_object(json_data)
- message = 'JSON一行化格式完成'
- else:
- return jsonify({'success': False, 'message': '不支持的格式化类型'})
-
- return jsonify({
- 'success': True,
- 'message': message,
- 'formatted': formatted,
- 'original_length': len(content),
- 'formatted_length': len(formatted)
- })
-
- except Exception as e:
- return jsonify({'success': False, 'message': f'处理错误: {str(e)}'})
-
-@app.route('/api/batch_add', methods=['POST'])
-def batch_add_property():
- """批量添加属性API"""
- try:
- data = request.get_json()
- content = data.get('content', '')
- key_name = data.get('key_name', '')
- key_value = data.get('key_value', '')
-
- if not content.strip():
- return jsonify({'success': False, 'message': '请提供JSON内容'})
-
- if not key_name.strip():
- return jsonify({'success': False, 'message': '请提供键名'})
-
- # 解析JSON
- try:
- json_data = json.loads(content)
- except json.JSONDecodeError as e:
- return jsonify({'success': False, 'message': f'JSON格式错误: {str(e)}'})
-
- # 智能解析键值
- parsed_value = parse_value(key_value)
-
- # 批量添加属性
- count = add_property_to_all_objects(json_data, key_name, parsed_value)
-
- # 格式化输出
- formatted = JSONFormatter.format_standard(json_data)
-
- return jsonify({
- 'success': True,
- 'message': f'成功为 {count} 个对象添加了属性 "{key_name}": {json.dumps(parsed_value, ensure_ascii=False)}',
- 'formatted': formatted,
- 'count': count
- })
-
- except Exception as e:
- return jsonify({'success': False, 'message': f'处理错误: {str(e)}'})
-
-def parse_value(value_str):
- """智能解析值的类型"""
- if value_str == '':
- return ''
-
- # null
- if value_str.lower() == 'null':
- return None
-
- # boolean
- if value_str.lower() == 'true':
- return True
- if value_str.lower() == 'false':
- return False
-
- # number
- try:
- if '.' in value_str:
- return float(value_str)
- else:
- return int(value_str)
- except ValueError:
- pass
-
- # JSON object or array
- if (value_str.startswith('{') and value_str.endswith('}')) or \
- (value_str.startswith('[') and value_str.endswith(']')):
- try:
- return json.loads(value_str)
- except json.JSONDecodeError:
- pass
-
- # string
- return value_str
-
-def add_property_to_all_objects(obj, key, value):
- """递归为所有对象添加属性"""
- count = 0
-
- def traverse(current):
- nonlocal count
- if isinstance(current, dict):
- current[key] = value
- count += 1
- # 继续递归处理嵌套对象
- for val in current.values():
- if isinstance(val, (dict, list)) and val != current:
- traverse(val)
- elif isinstance(current, list):
- for item in current:
- traverse(item)
-
- traverse(obj)
- return count
-
-@app.route('/api/validate', methods=['POST'])
-def validate_json():
- """JSON验证API"""
- try:
- data = request.get_json()
- content = data.get('content', '')
-
- if not content.strip():
- return jsonify({'success': False, 'message': '请提供JSON内容'})
-
- try:
- json_data = json.loads(content)
- return jsonify({
- 'success': True,
- 'message': 'JSON格式正确 ✓',
- 'valid': True
- })
- except json.JSONDecodeError as e:
- return jsonify({
- 'success': False,
- 'message': f'JSON格式错误: {str(e)}',
- 'valid': False,
- 'error': str(e)
- })
-
- except Exception as e:
- return jsonify({'success': False, 'message': f'验证错误: {str(e)}'})
-
-@app.route('/api/download', methods=['POST'])
-def download_json():
- """下载JSON文件"""
- try:
- data = request.get_json()
- content = data.get('content', '')
- format_type = data.get('format_type', 'standard')
-
- if not content.strip():
- return jsonify({'success': False, 'message': '没有可下载的内容'})
-
- # 验证JSON格式
- try:
- json_data = json.loads(content)
- except json.JSONDecodeError as e:
- return jsonify({'success': False, 'message': f'JSON格式错误: {str(e)}'})
-
- # 格式化
- formatter = JSONFormatter()
- if format_type == 'minify':
- formatted_content = formatter.minify(json_data)
- elif format_type == 'oneline':
- formatted_content = formatter.one_line_per_object(json_data)
- else:
- formatted_content = formatter.format_standard(json_data)
-
- # 生成文件名
- timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
- filename = f"edited_json_{format_type}_{timestamp}.json"
-
- # 创建临时文件
- temp_file = f"temp_{filename}"
- with open(temp_file, 'w', encoding='utf-8') as f:
- f.write(formatted_content)
-
- return send_file(temp_file, as_attachment=True, download_name=filename)
-
- except Exception as e:
- return jsonify({'success': False, 'message': f'下载错误: {str(e)}'})
-
-if __name__ == '__main__':
- # 确保templates目录存在
- os.makedirs('templates', exist_ok=True)
-
- # 运行应用
- app.run(debug=True, host='0.0.0.0', port=5000)
diff --git a/Server/JsonEdit/requirements.txt b/Server/JsonEdit/requirements.txt
deleted file mode 100644
index c4988fb..0000000
--- a/Server/JsonEdit/requirements.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-Flask==2.3.3
-Werkzeug==2.3.7
\ No newline at end of file
diff --git a/Server/JsonEdit/templates/json_editor.html b/Server/JsonEdit/templates/json_editor.html
deleted file mode 100644
index fbe7316..0000000
--- a/Server/JsonEdit/templates/json_editor.html
+++ /dev/null
@@ -1,627 +0,0 @@
-
-
-
-
-
- 简易JSON编辑器 - 批量添加键值
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/Server/New_Features_Test_Guide.md b/Server/New_Features_Test_Guide.md
new file mode 100644
index 0000000..0519ecb
--- /dev/null
+++ b/Server/New_Features_Test_Guide.md
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Server/QQEmailSend.zip b/Server/QQEmailSend.zip
deleted file mode 100644
index e52011442104a8d38695421d60a51beaac3c3f25..0000000000000000000000000000000000000000
GIT binary patch
literal 0
HcmV?d00001
literal 58612
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