大更新,太多了,具体进游戏查看详细更新内容

反正很多
This commit is contained in:
2025-05-27 11:09:09 +08:00
parent a1e71a6a79
commit 8215cfa3ee
382 changed files with 13838 additions and 2974 deletions

View File

@@ -0,0 +1,106 @@
extends TextureRect
class_name HTTPTextureRect
signal loading_started
signal loading_finished(success: bool)
# HTTP请求节点
var http_request: HTTPRequest
func _ready():
# 创建HTTP请求节点
http_request = HTTPRequest.new()
add_child(http_request)
# 连接信号
http_request.request_completed.connect(_on_request_completed)
# 从URL加载图像
func load_from_url(url: String, custom_headers: Array = []) -> void:
if url.is_empty():
push_error("HTTPTextureRect: URL不能为空")
loading_finished.emit(false)
return
loading_started.emit()
# 发起HTTP请求
var error = http_request.request(url, custom_headers)
if error != OK:
push_error("HTTPTextureRect: 发起HTTP请求失败错误码: " + str(error))
loading_finished.emit(false)
# HTTP请求完成的回调函数
func _on_request_completed(result, response_code, headers, body):
if result != HTTPRequest.RESULT_SUCCESS:
push_error("HTTPTextureRect: HTTP请求失败错误码: " + str(result))
loading_finished.emit(false)
return
if response_code != 200:
push_error("HTTPTextureRect: HTTP请求返回非200状态码: " + str(response_code))
loading_finished.emit(false)
return
# 检查内容类型
var content_type = ""
for header in headers:
if header.to_lower().begins_with("content-type:"):
content_type = header.substr(13).strip_edges().to_lower()
print("HTTPTextureRect: 内容类型: ", content_type)
break
# 创建图像
var image = Image.new()
var error = ERR_INVALID_DATA
# 根据内容类型选择加载方法
if content_type.begins_with("image/jpeg") or content_type.begins_with("image/jpg"):
error = image.load_jpg_from_buffer(body)
elif content_type.begins_with("image/png"):
error = image.load_png_from_buffer(body)
elif content_type.begins_with("image/webp"):
error = image.load_webp_from_buffer(body)
elif content_type.begins_with("image/bmp"):
error = image.load_bmp_from_buffer(body)
else:
# 未知内容类型,尝试常见格式
error = image.load_jpg_from_buffer(body)
if error != OK:
error = image.load_png_from_buffer(body)
if error != OK:
error = image.load_webp_from_buffer(body)
if error != OK:
error = image.load_bmp_from_buffer(body)
# 检查加载结果
if error != OK:
push_error("HTTPTextureRect: 无法加载图像,错误码: " + str(error))
loading_finished.emit(false)
return
# 创建纹理并应用
var texture = ImageTexture.create_from_image(image)
self.texture = texture
print("HTTPTextureRect: 图像加载成功,尺寸: ", image.get_width(), "x", image.get_height())
loading_finished.emit(true)
# 加载QQ头像的便捷方法
func load_qq_avatar(qq_number: String) -> void:
if not qq_number.is_valid_int():
push_error("HTTPTextureRect: QQ号必须为纯数字")
loading_finished.emit(false)
return
# 使用QQ头像API
#var url = "https://q.qlogo.cn/headimg_dl?dst_uin=" + qq_number + "&spec=640&img_type=png"
var url = "http://q1.qlogo.cn/g?b=qq&nk="+qq_number+"&s=100"
# 添加浏览器模拟头
var headers = [
"User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36",
"Accept: image/png,image/jpeg,image/webp,image/*,*/*;q=0.8"
]
# 加载图像
load_from_url(url, headers)

View File

@@ -0,0 +1 @@
uid://0d2j5m6j2ema

View File

@@ -0,0 +1 @@
uid://caly13tf4ni1d

View File

@@ -0,0 +1,27 @@
[gd_scene load_steps=3 format=3 uid="uid://cm1e72lhd7j7v"]
[ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_sgirt"]
[ext_resource type="Texture2D" uid="uid://kdhowrc6av4g" path="res://assets/作物/默认/0.png" id="2_sgirt"]
[node name="BlackBlueCrop" type="Button"]
custom_minimum_size = Vector2(400, 400)
offset_right = 400.0
offset_bottom = 400.0
scale = Vector2(0.3, 0.3)
theme_override_font_sizes/font_size = 1
icon_alignment = 1
script = ExtResource("1_sgirt")
[node name="CropImage" type="Sprite2D" parent="."]
position = Vector2(199.569, 201.043)
scale = Vector2(0.260977, 0.259058)
texture = ExtResource("2_sgirt")
[node name="Title" type="Label" parent="."]
modulate = Color(0, 0.152941, 0.984314, 1)
layout_mode = 0
offset_right = 400.0
offset_bottom = 55.0
theme_override_font_sizes/font_size = 50
text = "普通"
horizontal_alignment = 1

42
CopyItems/crop_item.tscn Normal file
View File

@@ -0,0 +1,42 @@
[gd_scene load_steps=4 format=3 uid="uid://bkivlkirrx6u8"]
[ext_resource type="Texture2D" uid="uid://c4l0qn0p4yav8" path="res://assets/tu3.png" id="1_bns1c"]
[ext_resource type="Script" uid="uid://xh5tr5co5kfu" path="res://GUI/SMY_ProgressBar.gd" id="2_1n4xp"]
[ext_resource type="Texture2D" uid="uid://kdhowrc6av4g" path="res://assets/作物/默认/0.png" id="2_bns1c"]
[node name="CropItem" type="Button"]
self_modulate = Color(1, 1, 1, 0.435294)
custom_minimum_size = Vector2(100, 100)
offset_right = 40.0
offset_bottom = 40.0
[node name="ground_sprite" type="Sprite2D" parent="."]
position = Vector2(50, 63)
scale = Vector2(0.130329, 0.130329)
texture = ExtResource("1_bns1c")
[node name="crop_sprite" type="Sprite2D" parent="."]
visible = false
position = Vector2(50, 36)
scale = Vector2(0.0660772, 0.0660772)
texture = ExtResource("2_bns1c")
[node name="ProgressBar" type="ProgressBar" parent="."]
layout_mode = 2
offset_top = 86.0
offset_right = 495.0
offset_bottom = 159.0
scale = Vector2(0.2, 0.2)
theme_override_font_sizes/font_size = 50
script = ExtResource("2_1n4xp")
[node name="Label" type="Label" parent="."]
layout_mode = 2
offset_right = 250.0
offset_bottom = 42.0
scale = Vector2(0.4, 0.4)
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 30
text = "[普通-胡萝卜]"
horizontal_alignment = 1
vertical_alignment = 1

25
CopyItems/green_crop.tscn Normal file
View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://2m54c0f1ejir"]
[ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_ihcyw"]
[node name="GreenCrop" type="Button"]
custom_minimum_size = Vector2(400, 400)
offset_right = 400.0
offset_bottom = 400.0
scale = Vector2(0.3, 0.3)
theme_override_font_sizes/font_size = 1
icon_alignment = 1
script = ExtResource("1_ihcyw")
[node name="CropImage" type="Sprite2D" parent="."]
position = Vector2(199.569, 201.043)
scale = Vector2(0.260977, 0.259058)
[node name="Title" type="Label" parent="."]
modulate = Color(0.243137, 0.729412, 0, 1)
layout_mode = 0
offset_right = 400.0
offset_bottom = 55.0
theme_override_font_sizes/font_size = 50
text = "普通"
horizontal_alignment = 1

9
CopyItems/item_crop.gd Normal file
View File

@@ -0,0 +1,9 @@
extends Button
@onready var title :Label = $Title
@onready var crop_image: Sprite2D = $CropImage
func _ready() -> void:
title.text = self.text
pass

View File

@@ -0,0 +1 @@
uid://c6ylh1o2kgqth

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://forqk66f354p"]
[ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_00rx4"]
[node name="OrangeCrop" type="Button"]
custom_minimum_size = Vector2(400, 400)
offset_right = 400.0
offset_bottom = 400.0
scale = Vector2(0.3, 0.3)
theme_override_font_sizes/font_size = 1
icon_alignment = 1
script = ExtResource("1_00rx4")
[node name="CropImage" type="Sprite2D" parent="."]
position = Vector2(200, 200)
scale = Vector2(0.308178, 0.308356)
[node name="Title" type="Label" parent="."]
modulate = Color(0.822776, 0.578065, 0, 1)
layout_mode = 0
offset_right = 400.0
offset_bottom = 55.0
theme_override_font_sizes/font_size = 50
text = "普通"
horizontal_alignment = 1

25
CopyItems/pink_crop.tscn Normal file
View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://cmdoymcviv0ai"]
[ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_3xhou"]
[node name="PinkCrop" type="Button"]
custom_minimum_size = Vector2(400, 400)
offset_right = 400.0
offset_bottom = 400.0
scale = Vector2(0.3, 0.3)
theme_override_font_sizes/font_size = 1
icon_alignment = 1
script = ExtResource("1_3xhou")
[node name="CropImage" type="Sprite2D" parent="."]
position = Vector2(199.569, 201.043)
scale = Vector2(0.260977, 0.259058)
[node name="Title" type="Label" parent="."]
modulate = Color(0.980392, 0, 0.552941, 1)
layout_mode = 0
offset_right = 400.0
offset_bottom = 55.0
theme_override_font_sizes/font_size = 50
text = "普通"
horizontal_alignment = 1

25
CopyItems/red_crop.tscn Normal file
View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://d3ve4qeggsdqy"]
[ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_1yrv4"]
[node name="RedCrop" type="Button"]
custom_minimum_size = Vector2(400, 400)
offset_right = 400.0
offset_bottom = 400.0
scale = Vector2(0.3, 0.3)
theme_override_font_sizes/font_size = 1
icon_alignment = 1
script = ExtResource("1_1yrv4")
[node name="CropImage" type="Sprite2D" parent="."]
position = Vector2(199.569, 201.043)
scale = Vector2(0.260977, 0.259058)
[node name="Title" type="Label" parent="."]
modulate = Color(1, 0.0509804, 0.0352941, 1)
layout_mode = 0
offset_right = 400.0
offset_bottom = 55.0
theme_override_font_sizes/font_size = 50
text = "普通"
horizontal_alignment = 1

View File

@@ -0,0 +1,25 @@
[gd_scene load_steps=2 format=3 uid="uid://dagh3u5med30i"]
[ext_resource type="Script" uid="uid://c6ylh1o2kgqth" path="res://CopyItems/item_crop.gd" id="1_nfqts"]
[node name="WhiteBlueCrop" type="Button"]
custom_minimum_size = Vector2(400, 400)
offset_right = 400.0
offset_bottom = 400.0
scale = Vector2(0.3, 0.3)
theme_override_font_sizes/font_size = 1
icon_alignment = 1
script = ExtResource("1_nfqts")
[node name="CropImage" type="Sprite2D" parent="."]
position = Vector2(199.569, 201.043)
scale = Vector2(0.260977, 0.259058)
[node name="Title" type="Label" parent="."]
modulate = Color(0.111013, 0.795686, 0.959948, 1)
layout_mode = 0
offset_right = 400.0
offset_bottom = 55.0
theme_override_font_sizes/font_size = 50
text = "普通"
horizontal_alignment = 1

31
Data/crop_data.json Normal file
View File

@@ -0,0 +1,31 @@
{
"测试作物": {"花费": 1, "生长时间": 3, "收益": 9999, "品质": "普通", "描述": "测试作物", "耐候性": 10, "等级": 1, "经验": 999},
"小麦": {"花费": 120, "生长时间": 120, "收益": 100, "品质": "普通", "描述": "基础作物,品质较低,适合新手种植", "耐候性": 10, "等级": 1, "经验": 10},
"稻谷": {"花费": 100, "生长时间": 240, "收益": 120, "品质": "普通", "描述": "适合大规模种植的基础作物", "耐候性": 10, "等级": 1, "经验": 10},
"玉米": {"花费": 70, "生长时间": 600, "收益": 90, "品质": "普通", "描述": "营养丰富的优良作物,适合稍有经验的玩家", "耐候性": 15, "等级": 2, "经验": 15},
"土豆": {"花费": 75, "生长时间": 360, "收益": 90, "品质": "普通", "描述": "容易种植的耐寒作物", "耐候性": 12, "等级": 1, "经验": 10},
"胡萝卜": {"花费": 60, "生长时间": 480, "收益": 80, "品质": "普通", "描述": "适合新手的健康作物", "耐候性": 12, "等级": 1, "经验": 10},
"草莓": {"花费": 120, "生长时间": 960, "收益": 150, "品质": "优良", "描述": "营养丰富的果实,收益不错", "耐候性": 14, "等级": 2, "经验": 20},
"番茄": {"花费": 100, "生长时间": 720, "收益": 130, "品质": "优良", "描述": "常见作物,适合小规模种植", "耐候性": 12, "等级": 2, "经验": 15},
"大豆": {"花费": 90, "生长时间": 840, "收益": 110, "品质": "优良", "描述": "富含蛋白质的基础作物", "耐候性": 11, "等级": 2, "经验": 12},
"蓝莓": {"花费": 150, "生长时间": 1200, "收益": 200, "品质": "稀有", "描述": "较为稀有的作物,市场价值较高", "耐候性": 18, "等级": 3, "经验": 25},
"洋葱": {"花费": 85, "生长时间": 600, "收益": 105, "品质": "稀有", "描述": "烹饪常用的作物,适合中级种植", "耐候性": 10, "等级": 2, "经验": 10},
"南瓜": {"花费": 180, "生长时间": 1440, "收益": 250, "品质": "稀有", "描述": "秋季收获的高收益作物", "耐候性": 20, "等级": 4, "经验": 30},
"葡萄": {"花费": 200, "生长时间": 1200, "收益": 300, "品质": "稀有", "描述": "需要特殊管理的高收益作物", "耐候性": 15, "等级": 4, "经验": 35},
"柿子": {"花费": 160, "生长时间": 1080, "收益": 240, "品质": "稀有", "描述": "富含营养的秋季作物", "耐候性": 18, "等级": 3, "经验": 28},
"花椰菜": {"花费": 130, "生长时间": 960, "收益": 170, "品质": "稀有", "描述": "耐寒的高品质作物,适合经验丰富的玩家", "耐候性": 17, "等级": 3, "经验": 22},
"芦笋": {"花费": 200, "生长时间": 1560, "收益": 280, "品质": "稀有", "描述": "市场需求量高的稀有作物", "耐候性": 15, "等级": 4, "经验": 30},
"香草": {"花费": 250, "生长时间": 1800, "收益": 400, "品质": "史诗", "描述": "非常稀有且收益极高的作物", "耐候性": 22, "等级": 5, "经验": 40},
"西瓜": {"花费": 240, "生长时间": 2400, "收益": 420, "品质": "史诗", "描述": "夏季丰产的高价值作物", "耐候性": 21, "等级": 5, "经验": 45},
"甜菜": {"花费": 220, "生长时间": 2160, "收益": 350, "品质": "史诗", "描述": "营养丰富的根茎作物,收益较高", "耐候性": 20, "等级": 5, "经验": 38},
"甘蔗": {"花费": 260, "生长时间": 3000, "收益": 450, "品质": "史诗", "描述": "需要充足水源的高价值作物", "耐候性": 18, "等级": 5, "经验": 50},
"龙果": {"花费": 400, "生长时间": 4800, "收益": 600, "品质": "传奇", "描述": "极为稀有的热带作物,产量和价值都极高", "耐候性": 25, "等级": 6, "经验": 60},
"松露": {"花费": 500, "生长时间": 7200, "收益": 700, "品质": "传奇", "描述": "极其珍贵的地下作物,市场价格极高", "耐候性": 23, "等级": 7, "经验": 80},
"人参": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
"金橘": {"花费": 420, "生长时间": 4800, "收益": 620, "品质": "传奇", "描述": "少见的耐寒果树,市场需求量极大", "耐候性": 26, "等级": 7, "经验": 70}
}

View File

@@ -0,0 +1,48 @@
{
"experience": 0,
"farm_lots": [
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 5}
],
"level": 1,
"money": 1000,
"user_name": "shumengya",
"user_password": "0123456789"
}

View File

@@ -1,19 +0,0 @@
extends Panel
#用户登录账号用QQ号代替
@onready var username_input = $username_input
#用户登录密码
@onready var password_input = $password_input
#登录按钮
@onready var login_button = $login_button
func _ready():
pass
func _process(delta):
pass
func _on_login_button_pressed():
pass

View File

@@ -0,0 +1 @@
uid://xh5tr5co5kfu

494
GUI/crop_store_panel.gd Normal file
View File

@@ -0,0 +1,494 @@
extends Panel
#种子商店面板
#种子商店格子
@onready var crop_grid_container : GridContainer = $ScrollContainer/Crop_Grid
@onready var quit_button : Button = $QuitButton
#各种排序过滤按钮
@onready var sort_all_button : Button = $SortContainer/Sort_All#全部
@onready var sort_common_button : Button = $SortContainer/Sort_Common#普通
@onready var sort_superior_button : Button = $SortContainer/Sort_Superior#优良
@onready var sort_rare_button : Button = $SortContainer/Sort_Rare#稀有
@onready var sort_epic_button : Button = $SortContainer/Sort_Epic#史诗
@onready var sort_legendary_button : Button = $SortContainer/Sort_Legendary#传奇
@onready var sort_price_button : Button = $SortContainer/Sort_Price#价格
@onready var sort_growtime_button : Button = $SortContainer/Sort_GrowTime#生长时间
@onready var sort_profit_button : Button = $SortContainer/Sort_Profit#收益
@onready var sort_level_button : Button = $SortContainer/Sort_Level#等级
#预添加常用的面板
@onready var main_game = get_node("/root/main")
@onready var land_panel = get_node("/root/main/UI/LandPanel")
@onready var crop_store_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var player_ranking_panel = get_node("/root/main/UI/PlayerRankingPanel")
@onready var player_bag_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var network_manager = get_node("/root/main/UI/TCPNetworkManager")
# 作物图片缓存(复用主游戏的缓存系统)
var crop_textures_cache : Dictionary = {}
var crop_frame_counts : Dictionary = {}
# 当前过滤和排序设置
var current_filter_quality = ""
var current_sort_key = ""
var current_sort_ascending = true
# 准备函数
func _ready():
# 连接按钮信号
_connect_buttons()
# 隐藏面板(初始默认隐藏)
self.hide()
# 连接所有按钮信号
func _connect_buttons():
# 关闭按钮
quit_button.pressed.connect(self._on_quit_button_pressed)
# 过滤按钮
sort_all_button.pressed.connect(func(): _filter_by_quality(""))
sort_common_button.pressed.connect(func(): _filter_by_quality("普通"))
sort_superior_button.pressed.connect(func(): _filter_by_quality("优良"))
sort_rare_button.pressed.connect(func(): _filter_by_quality("稀有"))
sort_epic_button.pressed.connect(func(): _filter_by_quality("史诗"))
sort_legendary_button.pressed.connect(func(): _filter_by_quality("传奇"))
# 排序按钮
sort_price_button.pressed.connect(func(): _sort_by("花费"))
sort_growtime_button.pressed.connect(func(): _sort_by("生长时间"))
sort_profit_button.pressed.connect(func(): _sort_by("收益"))
sort_level_button.pressed.connect(func(): _sort_by("等级"))
# 初始化商店
func init_store():
print("初始化商店...")
# 清空已有的作物按钮
for child in crop_grid_container.get_children():
child.queue_free()
# 遍历可种植的作物数据并添加到商店
print("初始化商店,显示所有作物...")
for crop_name in main_game.can_planted_crop:
var crop = main_game.can_planted_crop[crop_name]
# 只显示当前等级可以种植的作物
if crop["等级"] <= main_game.level:
var store_btn = _create_store_button(crop_name, crop["品质"])
crop_grid_container.add_child(store_btn)
#print("添加商店按钮: " + crop_name)
print("商店初始化完成,共添加按钮: " + str(crop_grid_container.get_child_count()) + "")
# 更新金钱显示
_update_money_display()
# 创建商店按钮
func _create_store_button(crop_name: String, crop_quality: String) -> Button:
# 根据品质选择相应的按钮
var button = null
match crop_quality:
"普通":
button = main_game.green_bar.duplicate()
"优良":
button = main_game.orange_bar.duplicate()
"稀有":
button = main_game.white_blue_bar.duplicate()
"史诗":
button = main_game.pink_bar.duplicate()
"传奇":
button = main_game.black_blue_bar.duplicate()
_: # 默认情况
button = main_game.green_bar.duplicate()
var crop = main_game.can_planted_crop[crop_name]
# 确保按钮可见并可点击
button.visible = true
button.disabled = false
button.focus_mode = Control.FOCUS_ALL
# 设置按钮文本,显示价格
button.text = str(crop_quality + "-" + crop_name + "\n价格: ¥" + str(crop["花费"]))
# 将成熟时间从秒转换为天时分秒格式
var total_seconds = int(crop["生长时间"])
# 定义时间单位换算
var SECONDS_PER_MINUTE = 60
var SECONDS_PER_HOUR = 3600
var SECONDS_PER_DAY = 86400
# 计算各时间单位
var days = total_seconds / SECONDS_PER_DAY
total_seconds %= SECONDS_PER_DAY
var hours = total_seconds / SECONDS_PER_HOUR
total_seconds %= SECONDS_PER_HOUR
var minutes = total_seconds / SECONDS_PER_MINUTE
var seconds = total_seconds % SECONDS_PER_MINUTE
# 构建时间字符串(只显示有值的单位)
var time_str = ""
if days > 0:
time_str += str(days) + ""
if hours > 0:
time_str += str(hours) + "小时"
if minutes > 0:
time_str += str(minutes) + "分钟"
if seconds > 0:
time_str += str(seconds) + ""
button.tooltip_text = str(
"作物: " + crop_name + "\n" +
"品质: " + crop_quality + "\n" +
"价格: " + str(crop["花费"]) + "\n" +
"成熟时间: " + time_str + "\n" +
"收获收益: " + str(crop["收益"]) + "\n" +
"需求等级: " + str(crop["等级"]) + "\n" +
"耐候性: " + str(crop["耐候性"]) + "\n" +
"经验: " + str(crop["经验"]) + "\n" +
"描述: " + str(crop["描述"])
)
# 添加按钮事件
button.pressed.connect(func(): _on_store_buy_pressed(crop_name))
# 更新按钮的作物图片
_update_button_crop_image(button, crop_name)
# 如果按钮有标题标签,设置标题
if button.has_node("Title"):
match crop_quality:
"普通":
button.get_node("Title").modulate = Color.HONEYDEW#白色
"优良":
button.get_node("Title").modulate =Color.DODGER_BLUE#深蓝色
"稀有":
button.get_node("Title").modulate =Color.HOT_PINK#品红色
"史诗":
button.get_node("Title").modulate =Color.YELLOW#黄色
"传奇":
button.get_node("Title").modulate =Color.ORANGE_RED#红色
return button
# 购买种子事件处理
func _on_store_buy_pressed(crop_name: String):
print("购买种子: " + crop_name)
var crop = main_game.can_planted_crop[crop_name]
# 检查等级要求
if main_game.level < crop["等级"]:
Toast.show("等级不足,无法购买此种子", Color.RED)
return
# 检查金钱是否足够
if main_game.money < crop["花费"]:
Toast.show("金钱不足,无法购买种子", Color.RED)
return
# 发送购买请求到服务器
if network_manager and network_manager.sendBuySeed(crop_name):
# 购买请求已发送,等待服务器响应
Toast.show("正在购买种子...", Color.YELLOW, 2.0, 1.0)
# 将种子添加到背包
var found = false
for seed_item in main_game.player_bag:
if seed_item["name"] == crop_name:
seed_item["count"] += 1
found = true
break
if not found:
main_game.player_bag.append({
"name": crop_name,
"quality": crop["品质"],
"count": 1
})
# 显示购买成功消息
Toast.show("购买了" + crop["品质"] + "-" + crop_name + "种子", Color.GREEN)
# 更新背包UI
crop_store_panel.update_player_bag_ui()
# 更新金钱显示
_update_money_display()
# 关闭面板
func _on_quit_button_pressed():
print("关闭商店面板")
self.hide()
# 按品质过滤作物
func _filter_by_quality(quality: String):
current_filter_quality = quality
print("过滤作物,品质: " + (quality if quality != "" else "全部"))
_apply_filter_and_sort()
# 按指定键排序
func _sort_by(sort_key: String):
# 切换排序方向或设置新排序键
if current_sort_key == sort_key:
current_sort_ascending = !current_sort_ascending
else:
current_sort_key = sort_key
current_sort_ascending = true
print("排序作物,键: " + sort_key + ",升序: " + str(current_sort_ascending))
_apply_filter_and_sort()
# 应用过滤和排序
func _apply_filter_and_sort():
# 清空现有按钮
for child in crop_grid_container.get_children():
child.queue_free()
# 收集符合条件的作物
var filtered_crops = []
for crop_name in main_game.can_planted_crop:
var crop = main_game.can_planted_crop[crop_name]
# 检查等级和品质过滤
if crop["等级"] > main_game.level:
continue
if current_filter_quality != "" and crop["品质"] != current_filter_quality:
continue
# 添加到过滤后的列表
filtered_crops.append({
"name": crop_name,
"data": crop
})
# 如果有排序条件,进行排序
if current_sort_key != "":
filtered_crops.sort_custom(Callable(self, "_sort_crop_items"))
# 添加所有过滤和排序后的作物
for crop in filtered_crops:
var store_btn = _create_store_button(crop["name"], crop["data"]["品质"])
crop_grid_container.add_child(store_btn)
# 更新金钱显示
_update_money_display()
# 自定义排序函数
func _sort_crop_items(a, b):
if current_sort_ascending:
return a["data"][current_sort_key] < b["data"][current_sort_key]
else:
return a["data"][current_sort_key] > b["data"][current_sort_key]
# 更新金钱显示
func _update_money_display():
var money_label = get_node_or_null("MoneyLabel")
if money_label == null:
# 创建金钱显示标签
money_label = Label.new()
money_label.name = "MoneyLabel"
money_label.position = Vector2(10, 10)
money_label.size = Vector2(300, 45)
# 设置标签样式
money_label.add_theme_color_override("font_color", Color(1, 0.647, 0, 1)) # 橙色
money_label.add_theme_font_size_override("font_size", 24)
add_child(money_label)
# 更新金钱显示
money_label.text = "当前金钱:" + str(main_game.money) + ""
print("更新商店金钱显示:" + str(main_game.money))
# 刷新商店内容,可以在金钱变化或等级提升后调用
func refresh_store():
# 清空并重新创建商店按钮
init_store()
# 尝试创建过滤按钮(如果商店面板中没有这些按钮)
_create_filter_buttons_if_needed()
# 如果需要,动态创建过滤按钮
func _create_filter_buttons_if_needed():
# 检查是否已存在过滤器容器
var filter_container = get_node_or_null("FilterContainer")
if filter_container == null:
# 创建过滤器容器
filter_container = HBoxContainer.new()
filter_container.name = "FilterContainer"
# 设置容器位置和大小
filter_container.position = Vector2(320, 10)
filter_container.size = Vector2(770, 45)
add_child(filter_container)
# 添加过滤按钮
_add_filter_button(filter_container, "全部", func(): _filter_by_quality(""))
_add_filter_button(filter_container, "普通", func(): _filter_by_quality("普通"))
_add_filter_button(filter_container, "优良", func(): _filter_by_quality("优良"))
_add_filter_button(filter_container, "稀有", func(): _filter_by_quality("稀有"))
_add_filter_button(filter_container, "史诗", func(): _filter_by_quality("史诗"))
_add_filter_button(filter_container, "传奇", func(): _filter_by_quality("传奇"))
# 检查是否已存在排序容器
var sort_container = get_node_or_null("SortContainer")
if sort_container == null:
# 创建排序容器
sort_container = HBoxContainer.new()
sort_container.name = "SortContainer"
# 设置容器位置和大小
sort_container.position = Vector2(320, 55)
sort_container.size = Vector2(770, 30)
add_child(sort_container)
# 添加排序按钮
_add_filter_button(sort_container, "按价格", func(): _sort_by("花费"))
_add_filter_button(sort_container, "按生长时间", func(): _sort_by("生长时间"))
_add_filter_button(sort_container, "按收益", func(): _sort_by("收益"))
_add_filter_button(sort_container, "按等级", func(): _sort_by("等级"))
# 添加过滤按钮
func _add_filter_button(container, text, callback):
var button = Button.new()
button.text = text
button.custom_minimum_size = Vector2(100, 0)
button.size_flags_horizontal = Control.SIZE_EXPAND_FILL
container.add_child(button)
button.pressed.connect(callback)
# 获取作物的最后一帧图片(用于商店显示)
func _get_crop_final_texture(crop_name: String) -> Texture2D:
"""
获取作物的最后一帧图片,用于商店和背包显示
如果作物图片不存在,使用默认图片的最后一帧
"""
# 先尝试从主游戏的缓存中获取
if main_game and main_game.crop_textures_cache.has(crop_name):
var textures = main_game.crop_textures_cache[crop_name]
if textures.size() > 0:
return textures[textures.size() - 1] # 返回最后一帧
# 如果主游戏缓存中没有,自己加载
var textures = _load_crop_textures(crop_name)
if textures.size() > 0:
return textures[textures.size() - 1] # 返回最后一帧
return null
# 加载作物图片序列帧(复用主游戏的逻辑)
func _load_crop_textures(crop_name: String) -> Array:
"""
加载指定作物的所有序列帧图片
"""
if crop_textures_cache.has(crop_name):
return crop_textures_cache[crop_name]
var textures = []
var crop_path = "res://assets/作物/" + crop_name + "/"
var default_path = "res://assets/作物/默认/"
# 检查作物文件夹是否存在
if DirAccess.dir_exists_absolute(crop_path):
# 尝试加载作物的序列帧从0开始
var frame_index = 0
while true:
var texture_path = crop_path + str(frame_index) + ".png"
if ResourceLoader.exists(texture_path):
var texture = load(texture_path)
if texture:
textures.append(texture)
frame_index += 1
else:
break
else:
break
if textures.size() > 0:
print("商店加载作物 ", crop_name, "", textures.size(), " 帧图片")
else:
print("商店:作物 ", crop_name, " 文件夹存在但没有找到有效图片,使用默认图片")
textures = _load_default_textures()
else:
print("商店:作物 ", crop_name, " 的文件夹不存在,使用默认图片")
textures = _load_default_textures()
# 缓存结果
crop_textures_cache[crop_name] = textures
crop_frame_counts[crop_name] = textures.size()
return textures
# 加载默认图片
func _load_default_textures() -> Array:
"""
加载默认作物图片
"""
if crop_textures_cache.has("默认"):
return crop_textures_cache["默认"]
var textures = []
var default_path = "res://assets/作物/默认/"
# 尝试加载默认图片序列帧
var frame_index = 0
while true:
var texture_path = default_path + str(frame_index) + ".png"
if ResourceLoader.exists(texture_path):
var texture = load(texture_path)
if texture:
textures.append(texture)
frame_index += 1
else:
break
else:
break
# 如果没有找到序列帧,尝试加载单个默认图片
if textures.size() == 0:
var single_texture_path = default_path + "0.png"
if ResourceLoader.exists(single_texture_path):
var texture = load(single_texture_path)
if texture:
textures.append(texture)
# 缓存默认图片
crop_textures_cache["默认"] = textures
crop_frame_counts["默认"] = textures.size()
print("商店加载了 ", textures.size(), " 个默认作物图片")
return textures
# 更新按钮的作物图片
func _update_button_crop_image(button: Button, crop_name: String):
"""
更新按钮中的作物图片
"""
# 检查按钮是否有CropImage节点
var crop_image = button.get_node_or_null("CropImage")
if not crop_image:
print("商店按钮没有找到CropImage节点", button.name)
return
# 获取作物的最后一帧图片
var texture = _get_crop_final_texture(crop_name)
if texture:
# CropImage是Sprite2D直接设置texture属性
crop_image.texture = texture
crop_image.visible = true
print("商店更新作物图片:", crop_name)
else:
crop_image.visible = false
print("商店无法获取作物图片:", crop_name)
# 兼容MainGame.gd中的调用转发到_on_store_buy_pressed
func _on_crop_selected(crop_name: String):
_on_store_buy_pressed(crop_name)

View File

@@ -0,0 +1 @@
uid://mtfp0ct42nrx

126
GUI/land_panel.gd Normal file
View File

@@ -0,0 +1,126 @@
extends Panel
#获取玩家要操作的地块序号
var selected_lot_index = 0
#预添加常用的面板
@onready var main_game = get_node("/root/main")
@onready var land_panel = get_node("/root/main/UI/LandPanel")
@onready var crop_store_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var player_ranking_panel = get_node("/root/main/UI/PlayerRankingPanel")
@onready var player_bag_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var network_manager = get_node("/root/main/UI/TCPNetworkManager")
@onready var quit_button :Button = $Quit_Button
@onready var dig_button: Button = $Grid/Dig_Button
@onready var water_button: Button = $Grid/Water_Button
@onready var fertilize_button: Button = $Grid/Fertilize_Button
@onready var upgrade_button: Button = $Grid/Upgrade_Button
@onready var plant_button: Button = $Grid/Plant_Button
@onready var remove_button: Button = $Grid/Remove_Button
@onready var harvest_button: Button = $Grid/Harvest_Button
func _ready():
self.hide()
quit_button.pressed.connect(self._on_quit_button_pressed)
dig_button.pressed.connect(self._on_dig_button_pressed)
water_button.pressed.connect(self._on_water_button_pressed)
fertilize_button.pressed.connect(self._on_fertilize_button_pressed)
upgrade_button.pressed.connect(self._on_upgrade_button_pressed)
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)
#开垦
func _on_dig_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法开垦土地", Color.ORANGE, 2.0, 1.0)
self.hide()
return
dig_button.text = "开垦"+"\n花费:"+str(main_game.dig_money)
if network_manager and network_manager.is_connected_to_server():
# 使用服务器API来开垦土地
if network_manager.sendDigGround(selected_lot_index):
self.hide()
#浇水
func _on_water_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法浇水", Color.ORANGE, 2.0, 1.0)
self.hide()
return
self.hide()
pass
#施肥
func _on_fertilize_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法施肥", Color.ORANGE, 2.0, 1.0)
self.hide()
return
self.hide()
pass
#升级
func _on_upgrade_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法升级", Color.ORANGE, 2.0, 1.0)
self.hide()
return
self.hide()
pass
#种植
func _on_plant_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法种植", Color.ORANGE, 2.0, 1.0)
self.hide()
return
player_bag_panel.show()
self.hide()
pass
#铲除
func _on_remove_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法铲除作物", Color.ORANGE, 2.0, 1.0)
self.hide()
return
main_game.root_out_crop(selected_lot_index)
self.hide()
pass
#收获
func _on_harvest_button_pressed():
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法收获作物", Color.ORANGE, 2.0, 1.0)
self.hide()
return
main_game._harvest_crop(selected_lot_index)
self.hide()
pass
#退出
func _on_quit_button_pressed():
self.hide()
pass

1
GUI/land_panel.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://bljtkxil64h14

View File

@@ -1,87 +0,0 @@
extends Node
var http_request: HTTPRequest
var farm_lots = []
@onready var harvest = $harvest
@onready var label1 = $Label
@onready var username_input = $Panel/username_input
@onready var password_input = $Panel/password_input
@onready var login_button = $Panel/login_button
@onready var panel = $Panel
@onready var item_list = $item_list #ItemList
func _ready():
# 创建 HTTPRequest 节点
http_request = HTTPRequest.new()
add_child(http_request)
# 连接信号
http_request.connect("request_completed", Callable(self, "_on_request_completed"))
# 连接登录按钮点击事件
login_button.connect("pressed", Callable(self, "_on_login_button_pressed"))
# 登录按钮按下事件
func _on_login_button_pressed():
# 隐藏面板(只有在输入后才进行隐藏)
panel.hide()
# 获取用户名和密码输入
var username = username_input.text # 直接获取输入
var password = password_input.text # 直接获取输入
# 打印调试信息
print("Username entered: ", username)
print("Password entered: ", password)
# 检查用户名和密码是否为空
if username == "" or password == "":
print("用户名和密码不能为空")
panel.show() # 如果输入为空,显示面板
return
# 构建登录请求的 URL 和参数
var url = "https://api.shumengya.top/smyfarm/login.php"
var body = {
"username": username,
"password": password
}
# 发送 POST 请求进行登录
var err = http_request.request(url, [], HTTPClient.METHOD_POST, JSON.stringify(body))
if err != OK:
print("Error making HTTP POST request: ", err)
# 请求完成后的回调函数
func _on_request_completed(result, response_code, headers, body):
if response_code == 200:
var json = JSON.new()
var parse_result = json.parse(body.get_string_from_utf8())
if parse_result != OK:
print("Error parsing JSON: ", json.get_error_message())
return
var json_data = json.data
if json_data.has("error"):
print("Error: " + str(json_data["error"]))
panel.show() # 如果登录失败,显示面板,允许重新输入
elif json_data.has("message"):
print(json_data["message"])
# 处理登录成功
if json_data.has("data"):
var player_data = json_data["data"]
print("欢迎, " + player_data["username"])
# 加载玩家数据,进入游戏逻辑
_load_player_data(player_data)
else:
print("HTTP Request failed with response code: " + str(response_code))
panel.show() # 请求失败,重新显示面板
# 加载玩家数据
func _load_player_data(player_data):
# 根据返回的 player_data 初始化玩家的农场状态等
pass

433
GUI/login_panel.gd Normal file
View File

@@ -0,0 +1,433 @@
#玩家登录注册面板
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 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
# 记住密码选项如果UI中有CheckBox的话
var remember_password : bool = true # 默认记住密码
# 引用主场景和全局函数
@onready var main_game = get_node("/root/main")
@onready var land_panel = get_node("/root/main/UI/LandPanel")
@onready var crop_store_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var player_ranking_panel = get_node("/root/main/UI/PlayerRankingPanel")
@onready var player_bag_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var tcp_network_manager = get_node("/root/main/UI/TCPNetworkManager")
# 准备函数
func _ready():
# 连接按钮信号
login_button.pressed.connect(self._on_login_button_pressed)
register_button.pressed.connect(self._on_register_button_pressed)
send_button.pressed.connect(self._on_send_button_pressed)
# 加载保存的登录信息
_load_login_info()
# 处理登录按钮点击
func _on_login_button_pressed():
var user_name = username_input.text.strip_edges() # 修剪前后的空格
var user_password = password_input.text.strip_edges()
var farmname = farmname_input.text.strip_edges()
if user_name == "" or user_password == "":
status_label.text = "用户名或密码不能为空!"
status_label.modulate = Color.RED
return
# 检查网络连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "未连接到服务器,正在尝试连接..."
status_label.modulate = Color.YELLOW
# 尝试自动连接到服务器
tcp_network_manager._on_connection_button_pressed()
await get_tree().create_timer(1.0).timeout
# 再次检查连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "连接服务器失败,请检查网络设置!"
status_label.modulate = Color.RED
return
# 禁用按钮,防止重复点击
login_button.disabled = true
status_label.text = "正在登录,请稍候..."
status_label.modulate = Color.YELLOW
# 如果启用了记住密码,保存登录信息
if remember_password:
_save_login_info(user_name, user_password)
tcp_network_manager.sendLoginInfo(user_name, user_password)
# 更新主游戏数据
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
if login_button.disabled:
login_button.disabled = false
status_label.text = "登录超时,请重试!"
status_label.modulate = Color.RED
# 处理验证码发送按钮点击
func _on_send_button_pressed():
var user_name = username_input.text.strip_edges()
if user_name == "":
status_label.text = "请输入QQ号以接收验证码"
status_label.modulate = Color.RED
return
if !is_valid_qq_number(user_name):
status_label.text = "请输入正确的QQ号码5-12位数字"
status_label.modulate = Color.RED
return
# 检查网络连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "未连接到服务器,正在尝试连接..."
status_label.modulate = Color.YELLOW
# 尝试自动连接到服务器
tcp_network_manager._on_connection_button_pressed()
await get_tree().create_timer(1.0).timeout
# 再次检查连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "连接服务器失败,请检查网络设置!"
status_label.modulate = Color.RED
return
# 禁用按钮,防止重复点击
send_button.disabled = true
status_label.text = "正在发送验证码,请稍候..."
status_label.modulate = Color.YELLOW
# 发送验证码请求
tcp_network_manager.sendVerificationCodeRequest(user_name)
# 60秒后重新启用按钮或收到响应后提前启用
var timer = 60
while timer > 0 and send_button.disabled:
send_button.text = "重新发送(%d)" % timer
await get_tree().create_timer(1.0).timeout
timer -= 1
if send_button.disabled:
send_button.disabled = false
send_button.text = "发送验证码"
if status_label.text == "正在发送验证码,请稍候...":
status_label.text = "验证码发送超时,请重试!"
status_label.modulate = Color.RED
# 处理注册按钮点击
func _on_register_button_pressed():
var user_name = username_input.text.strip_edges()
var user_password = password_input.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()
var verification_code = verificationcode_input.text.strip_edges()
if user_name == "" or user_password == "":
status_label.text = "用户名或密码不能为空!"
status_label.modulate = Color.RED
return
if farmname == "":
status_label.text = "农场名称不能为空!"
status_label.modulate = Color.RED
return
if user_password != user_password_2:
status_label.text = "两次输入的密码不一致!"
status_label.modulate = Color.RED
return
if !is_valid_qq_number(user_name):
status_label.text = "请输入正确的QQ号码5-12位数字"
status_label.modulate = Color.RED
return
if verification_code == "":
status_label.text = "请输入验证码!"
status_label.modulate = Color.RED
return
# 检查网络连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "未连接到服务器,正在尝试连接..."
status_label.modulate = Color.YELLOW
# 尝试自动连接到服务器
tcp_network_manager._on_connection_button_pressed()
await get_tree().create_timer(1.0).timeout
# 再次检查连接状态
if !tcp_network_manager.client.is_client_connected():
status_label.text = "连接服务器失败,请检查网络设置!"
status_label.modulate = Color.RED
return
# 禁用按钮,防止重复点击
register_button.disabled = true
status_label.text = "正在注册,请稍候..."
status_label.modulate = Color.YELLOW
# 发送注册请求
tcp_network_manager.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
# 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
# 处理验证码发送响应
func _on_verification_code_response(success: bool, message: String):
if success:
status_label.text = message
status_label.modulate = Color.GREEN
else:
status_label.text = message
status_label.modulate = Color.RED
send_button.disabled = false
send_button.text = "发送验证码"
# 处理验证码验证响应
func _on_verify_code_response(success: bool, message: String):
if success:
status_label.text = message
status_label.modulate = Color.GREEN
else:
status_label.text = message
status_label.modulate = Color.RED
# 验证QQ号是否有效
func is_valid_qq_number(qq_number: String) -> bool:
# QQ号的标准格式是5到12位的数字
var qq_regex = RegEx.new()
var pattern = r"^\d{5,12}$"
var error = qq_regex.compile(pattern)
if error != OK:
status_label.text = "QQ号验证失败部错误"
status_label.modulate = Color.RED
return false
return qq_regex.search(qq_number) != null
# 处理登录响应
func _on_login_response_received(success: bool, message: String, user_data: Dictionary):
# 启用按钮
login_button.disabled = false
if success:
status_label.text = "登录成功!正在加载游戏..."
status_label.modulate = Color.GREEN
# 更新主游戏数据
main_game.experience = user_data.get("experience", 0)
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", "")
farmname_input.text = user_data.get("farm_name", "")
# 加载玩家背包数据
if user_data.has("player_bag"):
main_game.player_bag = user_data.get("player_bag", [])
else:
main_game.player_bag = []
main_game.start_game = true
self.hide()
# 确保在更新数据后调用主游戏的 UI 更新函数
main_game._update_ui()
main_game._refresh_farm_lots()
player_bag_panel.update_player_bag_ui()
else:
status_label.text = "登录失败:" + message
status_label.modulate = Color.RED
# 如果登录失败且是密码错误,可以选择清除保存的信息
if "密码" in message or "password" in message.to_lower():
print("登录失败可能是密码错误。如需清除保存的登录信息请调用_clear_login_info()")
# 处理注册响应
func _on_register_response_received(success: bool, message: String):
# 启用按钮
register_button.disabled = false
if success:
status_label.text = "注册成功!请登录游戏"
status_label.modulate = Color.GREEN
# 注册成功后,如果启用了记住密码,保存登录信息
if remember_password:
var user_name = username_input.text.strip_edges()
var user_password = password_input.text.strip_edges()
_save_login_info(user_name, user_password)
# 清除注册相关的输入框
password_input_2.text = ""
verificationcode_input.text = ""
else:
status_label.text = "注册失败:" + message
status_label.modulate = Color.RED
# 保存登录信息到JSON文件
func _save_login_info(user_name: String, password: String):
var login_data = {
"user_name": user_name,
"password": password
}
var file = FileAccess.open("user://login.json", FileAccess.WRITE)
if file:
var json_string = JSON.stringify(login_data, "\t")
file.store_string(json_string)
file.close()
print("登录信息已保存")
else:
print("无法保存登录信息")
# 从JSON文件加载登录信息
func _load_login_info():
var file = FileAccess.open("user://login.json", FileAccess.READ)
if file:
var json_text = file.get_as_text()
file.close()
var json = JSON.new()
var parse_result = json.parse(json_text)
if parse_result == OK:
var login_data = json.get_data()
if login_data.has("user_name") and login_data.has("password"):
var saved_username = login_data.get("user_name", "")
var saved_password = login_data.get("password", "")
if saved_username != "" and saved_password != "":
username_input.text = saved_username
password_input.text = saved_password
status_label.text = "已加载保存的登录信息"
status_label.modulate = Color.CYAN
print("登录信息已加载:用户名 =", saved_username)
else:
status_label.text = "欢迎使用萌芽农场"
status_label.modulate = Color.WHITE
print("没有有效的保存登录信息")
else:
print("登录信息格式错误")
else:
print("登录信息JSON解析错误", json.get_error_message())
else:
# 创建默认的登录信息文件
_save_login_info("", "")
status_label.text = "欢迎使用萌芽农场"
status_label.modulate = Color.WHITE
print("没有找到保存的登录信息,已创建默认文件")
# 清除保存的登录信息
func _clear_login_info():
var file = FileAccess.open("user://login.json", FileAccess.WRITE)
if file:
var empty_data = {
"user_name": "",
"password": ""
}
var json_string = JSON.stringify(empty_data, "\t")
file.store_string(json_string)
file.close()
print("登录信息已清除")
else:
print("无法清除登录信息")
# 切换记住密码选项
func toggle_remember_password():
remember_password = !remember_password
print("记住密码选项:", "开启" if remember_password else "关闭")
# 如果关闭了记住密码,清除已保存的信息
if not remember_password:
_clear_login_info()
# 检查是否有保存的登录信息
func has_saved_login_info() -> bool:
var file = FileAccess.open("user://login.json", FileAccess.READ)
if file:
var json_text = file.get_as_text()
file.close()
var json = JSON.new()
var parse_result = json.parse(json_text)
if parse_result == OK:
var login_data = json.get_data()
var user_name = login_data.get("user_name", "")
var password = login_data.get("password", "")
return user_name != "" and password != ""
return false
# 快捷登录(使用保存的登录信息)
func quick_login():
if has_saved_login_info():
var user_name = username_input.text.strip_edges()
var user_password = password_input.text.strip_edges()
if user_name != "" and user_password != "":
print("执行快捷登录...")
_on_login_button_pressed()
else:
status_label.text = "保存的登录信息不完整"
status_label.modulate = Color.ORANGE
else:
status_label.text = "没有保存的登录信息"
status_label.modulate = Color.ORANGE
# 获取保存的用户名(用于调试或显示)
func get_saved_username() -> String:
var file = FileAccess.open("user://login.json", FileAccess.READ)
if file:
var json_text = file.get_as_text()
file.close()
var json = JSON.new()
var parse_result = json.parse(json_text)
if parse_result == OK:
var login_data = json.get_data()
return login_data.get("user_name", "")
return ""

1
GUI/login_panel.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cka0r4g8tbf0

479
GUI/player_bag_panel.gd Normal file
View File

@@ -0,0 +1,479 @@
extends Panel
# 背包格子容器
@onready var player_bag_grid_container : GridContainer = $ScrollContainer/Bag_Grid
@onready var quit_button : Button = $QuitButton
#各种排序过滤按钮
@onready var sort_all_button : Button = $SortContainer/Sort_All#全部
@onready var sort_common_button : Button = $SortContainer/Sort_Common#普通
@onready var sort_superior_button : Button = $SortContainer/Sort_Superior#优良
@onready var sort_rare_button : Button = $SortContainer/Sort_Rare#稀有
@onready var sort_epic_button : Button = $SortContainer/Sort_Epic#史诗
@onready var sort_legendary_button : Button = $SortContainer/Sort_Legendary#传奇
@onready var sort_price_button : Button = $SortContainer/Sort_Price#价格
@onready var sort_growtime_button : Button = $SortContainer/Sort_GrowTime#生长时间
@onready var sort_profit_button : Button = $SortContainer/Sort_Profit#收益
@onready var sort_level_button : Button = $SortContainer/Sort_Level#等级
#预添加常用的面板
@onready var main_game = get_node("/root/main")
@onready var land_panel = get_node("/root/main/UI/LandPanel")
@onready var crop_store_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var player_ranking_panel = get_node("/root/main/UI/PlayerRankingPanel")
@onready var player_bag_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var network_manager = get_node("/root/main/UI/TCPNetworkManager")
# 作物图片缓存(复用主游戏的缓存系统)
var crop_textures_cache : Dictionary = {}
var crop_frame_counts : Dictionary = {}
# 当前选择的地块索引从MainGame获取
var selected_lot_index : int = -1
# 当前过滤和排序设置
var current_filter_quality = ""
var current_sort_key = ""
var current_sort_ascending = true
# 准备函数
func _ready():
# 连接按钮信号
_connect_buttons()
# 隐藏面板(初始默认隐藏)
self.hide()
# 连接所有按钮信号
func _connect_buttons():
# 关闭按钮
quit_button.pressed.connect(self._on_quit_button_pressed)
# 过滤按钮
sort_all_button.pressed.connect(func(): _filter_by_quality(""))
sort_common_button.pressed.connect(func(): _filter_by_quality("普通"))
sort_superior_button.pressed.connect(func(): _filter_by_quality("优良"))
sort_rare_button.pressed.connect(func(): _filter_by_quality("稀有"))
sort_epic_button.pressed.connect(func(): _filter_by_quality("史诗"))
sort_legendary_button.pressed.connect(func(): _filter_by_quality("传奇"))
# 排序按钮
sort_price_button.pressed.connect(func(): _sort_by("花费"))
sort_growtime_button.pressed.connect(func(): _sort_by("生长时间"))
sort_profit_button.pressed.connect(func(): _sort_by("收益"))
sort_level_button.pressed.connect(func(): _sort_by("等级"))
# 初始化玩家背包
func init_player_bag():
# 清空玩家背包格子
for child in player_bag_grid_container.get_children():
child.queue_free()
# 显示背包中的种子
update_player_bag_ui()
# 更新玩家背包UI
func update_player_bag_ui():
# 清空玩家背包格子
for child in player_bag_grid_container.get_children():
child.queue_free()
#print("更新背包UI背包中物品数量", main_game.player_bag.size())
# 应用过滤和排序
var filtered_seeds = _get_filtered_and_sorted_seeds()
# 为背包中的每个过滤后的种子创建按钮
for seed_item in filtered_seeds:
var crop_name = seed_item["name"]
var crop_quality = seed_item["quality"]
var crop_count = seed_item["count"]
#print("背包物品:", crop_name, " 数量:", crop_count)
# 创建种子按钮
var button = _create_crop_button(crop_name, crop_quality)
# 更新按钮文本显示数量
button.text = str(crop_quality + "-" + crop_name + "\n数量:" + str(crop_count))
# 根据是否处于访问模式连接不同的事件
if main_game.is_visiting_mode:
# 访问模式下,点击种子只显示信息,不能种植
button.pressed.connect(func(): _on_visit_seed_selected(crop_name, crop_count))
else:
# 正常模式下,连接种植事件
button.pressed.connect(func(): _on_bag_seed_selected(crop_name))
player_bag_grid_container.add_child(button)
# 获取过滤和排序后的种子列表
func _get_filtered_and_sorted_seeds():
var filtered_seeds = []
# 收集符合条件的种子
for seed_item in main_game.player_bag:
# 品质过滤
if current_filter_quality != "" and seed_item["quality"] != current_filter_quality:
continue
# 获取种子对应的作物数据
var crop_data = null
if main_game.can_planted_crop.has(seed_item["name"]):
crop_data = main_game.can_planted_crop[seed_item["name"]]
# 添加到过滤后的列表
filtered_seeds.append({
"name": seed_item["name"],
"quality": seed_item["quality"],
"count": seed_item["count"],
"data": crop_data
})
# 如果有排序条件且数据可用,进行排序
if current_sort_key != "":
filtered_seeds.sort_custom(Callable(self, "_sort_seed_items"))
return filtered_seeds
# 自定义排序函数
func _sort_seed_items(a, b):
# 检查是否有有效数据用于排序
if a["data"] == null or b["data"] == null:
# 如果某一项没有数据,将其排在后面
if a["data"] == null and b["data"] != null:
return false
if a["data"] != null and b["data"] == null:
return true
# 如果都没有数据,按名称排序
return a["name"] < b["name"]
# 确保排序键存在于数据中
if !a["data"].has(current_sort_key) or !b["data"].has(current_sort_key):
print("警告: 排序键 ", current_sort_key, " 在某些种子数据中不存在")
return false
# 执行排序
if current_sort_ascending:
return a["data"][current_sort_key] < b["data"][current_sort_key]
else:
return a["data"][current_sort_key] > b["data"][current_sort_key]
# 按品质过滤种子
func _filter_by_quality(quality: String):
current_filter_quality = quality
print("过滤种子,品质: " + (quality if quality != "" else "全部"))
update_player_bag_ui()
# 按指定键排序
func _sort_by(sort_key: String):
# 切换排序方向或设置新排序键
if current_sort_key == sort_key:
current_sort_ascending = !current_sort_ascending
else:
current_sort_key = sort_key
current_sort_ascending = true
print("排序种子,键: " + sort_key + ",升序: " + str(current_sort_ascending))
update_player_bag_ui()
# 创建作物按钮
func _create_crop_button(crop_name: String, crop_quality: String) -> Button:
# 根据品质选择相应的进度条
var button = null
#普通 Color.HONEYDEW#白色
#优良 Color.DODGER_BLUE#深蓝色
#稀有 Color.HOT_PINK#品红色
#史诗 Color.YELLOW#黄色
#传奇 Color.ORANGE_RED#红色
#空地 Color.GREEN#绿色
#未开垦 Color.WEB_GRAY#深褐色
match crop_quality:
"普通":
button = main_game.green_bar.duplicate()
"优良":
button = main_game.orange_bar.duplicate()
"稀有":
button = main_game.white_blue_bar.duplicate()
"史诗":
button = main_game.pink_bar.duplicate()
"传奇":
button = main_game.black_blue_bar.duplicate()
_: # 默认情况
button = main_game.green_bar.duplicate()
# 确保按钮可见并可点击
button.visible = true
button.disabled = false
button.focus_mode = Control.FOCUS_ALL
# 设置按钮文本
button.text = str(crop_quality + "-" + crop_name)
# 添加工具提示 (tooltip)
if main_game.can_planted_crop.has(crop_name):
var crop = main_game.can_planted_crop[crop_name]
# 将成熟时间从秒转换为天时分秒格式
var total_seconds = int(crop["生长时间"])
# 定义时间单位换算
var SECONDS_PER_MINUTE = 60
var SECONDS_PER_HOUR = 3600
var SECONDS_PER_DAY = 86400
# 计算各时间单位
var days = total_seconds / SECONDS_PER_DAY
total_seconds %= SECONDS_PER_DAY
var hours = total_seconds / SECONDS_PER_HOUR
total_seconds %= SECONDS_PER_HOUR
var minutes = total_seconds / SECONDS_PER_MINUTE
var seconds = total_seconds % SECONDS_PER_MINUTE
# 构建时间字符串(只显示有值的单位)
var time_str = ""
if days > 0:
time_str += str(days) + ""
if hours > 0:
time_str += str(hours) + "小时"
if minutes > 0:
time_str += str(minutes) + "分钟"
if seconds > 0:
time_str += str(seconds) + ""
button.tooltip_text = str(
"作物: " + crop_name + "\n" +
"品质: " + crop_quality + "\n" +
"价格: " + str(crop["花费"]) + "\n" +
"成熟时间: " + time_str + "\n" +
"收获收益: " + str(crop["收益"]) + "\n" +
"需求等级: " + str(crop["等级"]) + "\n" +
"耐候性: " + str(crop["耐候性"]) + "\n" +
"经验: " + str(crop["经验"]) + "\n" +
"描述: " + str(crop["描述"])
)
# 如果按钮有标题标签,设置标题
if button.has_node("Title"):
button.get_node("Title").text = crop_quality
match crop_quality:
"普通":
button.get_node("Title").modulate = Color.HONEYDEW#白色
"优良":
button.get_node("Title").modulate =Color.DODGER_BLUE#深蓝色
"稀有":
button.get_node("Title").modulate =Color.HOT_PINK#品红色
"史诗":
button.get_node("Title").modulate =Color.YELLOW#黄色
"传奇":
button.get_node("Title").modulate =Color.ORANGE_RED#红色
# 更新按钮的作物图片
_update_button_crop_image(button, crop_name)
return button
# 从背包中选择种子并种植
func _on_bag_seed_selected(crop_name):
# 检查是否处于访问模式
if main_game.is_visiting_mode:
Toast.show("访问模式下无法种植", Color.ORANGE, 2.0, 1.0)
return
# 从主场景获取当前选择的地块索引
selected_lot_index = main_game.selected_lot_index
if selected_lot_index != -1:
# 检查背包中是否有这个种子
var seed_index = -1
for i in range(len(main_game.player_bag)):
if main_game.player_bag[i]["name"] == crop_name:
seed_index = i
break
#print("选择种子:", crop_name, ",背包索引:", seed_index)
if seed_index != -1 and main_game.player_bag[seed_index]["count"] > 0:
# 种植种子并从背包中减少数量
_plant_crop_from_bag(selected_lot_index, crop_name, seed_index)
main_game.selected_lot_index = -1
self.hide()
# 访问模式下的种子点击处理
func _on_visit_seed_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 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 + "-" + crop_name + " (数量: " + str(crop_count) + ")\n"
info_text += "价格: " + str(price) + "元, 收益: " + str(profit) + "\n"
info_text += "成熟时间: " + time_str + ", 需求等级: " + str(level_req)
else:
info_text = crop_name + " (数量: " + str(crop_count) + ")"
Toast.show(info_text, Color.CYAN, 3.0, 1.0)
print("查看种子信息: ", info_text)
# 从背包种植作物
func _plant_crop_from_bag(index, crop_name, seed_index):
var crop = main_game.can_planted_crop[crop_name]
# 检查是否有效的种子索引,防止越界访问
if seed_index < 0 or seed_index >= main_game.player_bag.size():
#print("错误:无效的种子索引 ", seed_index)
return
# 发送种植请求到服务器
if network_manager and network_manager.sendPlantCrop(index, crop_name):
# 种植请求已发送,等待服务器响应
Toast.show("正在发送种植请求", Color.YELLOW, 2.0, 1.0)
# 关闭背包面板
hide()
# 关闭面板
func _on_quit_button_pressed():
self.hide()
# 获取作物的最后一帧图片(用于背包显示)
func _get_crop_final_texture(crop_name: String) -> Texture2D:
"""
获取作物的最后一帧图片,用于背包显示
如果作物图片不存在,使用默认图片的最后一帧
"""
# 先尝试从主游戏的缓存中获取
if main_game and main_game.crop_textures_cache.has(crop_name):
var textures = main_game.crop_textures_cache[crop_name]
if textures.size() > 0:
return textures[textures.size() - 1] # 返回最后一帧
# 如果主游戏缓存中没有,自己加载
var textures = _load_crop_textures(crop_name)
if textures.size() > 0:
return textures[textures.size() - 1] # 返回最后一帧
return null
# 加载作物图片序列帧(复用主游戏的逻辑)
func _load_crop_textures(crop_name: String) -> Array:
"""
加载指定作物的所有序列帧图片
"""
if crop_textures_cache.has(crop_name):
return crop_textures_cache[crop_name]
var textures = []
var crop_path = "res://assets/作物/" + crop_name + "/"
var default_path = "res://assets/作物/默认/"
# 检查作物文件夹是否存在
if DirAccess.dir_exists_absolute(crop_path):
# 尝试加载作物的序列帧从0开始
var frame_index = 0
while true:
var texture_path = crop_path + str(frame_index) + ".png"
if ResourceLoader.exists(texture_path):
var texture = load(texture_path)
if texture:
textures.append(texture)
frame_index += 1
else:
break
else:
break
if textures.size() > 0:
print("背包加载作物 ", crop_name, "", textures.size(), " 帧图片")
else:
print("背包:作物 ", crop_name, " 文件夹存在但没有找到有效图片,使用默认图片")
textures = _load_default_textures()
else:
print("背包:作物 ", crop_name, " 的文件夹不存在,使用默认图片")
textures = _load_default_textures()
# 缓存结果
crop_textures_cache[crop_name] = textures
crop_frame_counts[crop_name] = textures.size()
return textures
# 加载默认图片
func _load_default_textures() -> Array:
"""
加载默认作物图片
"""
if crop_textures_cache.has("默认"):
return crop_textures_cache["默认"]
var textures = []
var default_path = "res://assets/作物/默认/"
# 尝试加载默认图片序列帧
var frame_index = 0
while true:
var texture_path = default_path + str(frame_index) + ".png"
if ResourceLoader.exists(texture_path):
var texture = load(texture_path)
if texture:
textures.append(texture)
frame_index += 1
else:
break
else:
break
# 如果没有找到序列帧,尝试加载单个默认图片
if textures.size() == 0:
var single_texture_path = default_path + "0.png"
if ResourceLoader.exists(single_texture_path):
var texture = load(single_texture_path)
if texture:
textures.append(texture)
# 缓存默认图片
crop_textures_cache["默认"] = textures
crop_frame_counts["默认"] = textures.size()
print("背包加载了 ", textures.size(), " 个默认作物图片")
return textures
# 更新按钮的作物图片
func _update_button_crop_image(button: Button, crop_name: String):
"""
更新按钮中的作物图片
"""
# 检查按钮是否有CropImage节点
var crop_image = button.get_node_or_null("CropImage")
if not crop_image:
print("背包按钮没有找到CropImage节点", button.name)
return
# 获取作物的最后一帧图片
var texture = _get_crop_final_texture(crop_name)
if texture:
# CropImage是Sprite2D直接设置texture属性
crop_image.texture = texture
crop_image.visible = true
print("背包更新作物图片:", crop_name)
else:
crop_image.visible = false
print("背包无法获取作物图片:", crop_name)

View File

@@ -0,0 +1 @@
uid://cgr332wsx63a8

View File

@@ -0,0 +1,71 @@
[gd_scene load_steps=3 format=3 uid="uid://crd28qnymob7"]
[ext_resource type="Texture2D" uid="uid://c3vng0nal1wr8" path="res://assets/Test/g.png" id="1_sgoxp"]
[ext_resource type="Script" uid="uid://0d2j5m6j2ema" path="res://Components/HTTPTextureRect.gd" id="2_ky0k8"]
[node name="player_ranking_item" type="VBoxContainer"]
offset_right = 1152.0
offset_bottom = 82.0
[node name="HBox" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="SerialNumber" type="Label" parent="HBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "1."
[node name="PlayerAvatar" type="TextureRect" parent="HBox"]
layout_mode = 2
texture = ExtResource("1_sgoxp")
expand_mode = 3
script = ExtResource("2_ky0k8")
metadata/_custom_type_script = "uid://0d2j5m6j2ema"
[node name="PlayerName" type="Label" parent="HBox"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 30
text = "树萌芽"
[node name="PlayerMoney" type="Label" parent="HBox"]
modulate = Color(1, 1, 0, 1)
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 30
text = "钱币999"
[node name="SeedNum" type="Label" parent="HBox"]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 30
text = "种子数999"
[node name="PlayerLevel" type="Label" parent="HBox"]
modulate = Color(0, 1, 1, 1)
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 30
text = "等级999"
[node name="VisitButton" type="Button" parent="HBox"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 30
text = "访问"
[node name="HBox2" type="HBoxContainer" parent="."]
layout_mode = 2
[node name="LastLoginTime" type="Label" parent="HBox2"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
text = "最后在线2025年12时09分35秒"
[node name="OnlineTime" type="Label" parent="HBox2"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
text = "累计在线时长99时60分60秒"

110
GUI/player_ranking_panel.gd Normal file
View File

@@ -0,0 +1,110 @@
extends Panel
@onready var player_ranking_list : VBoxContainer = $Scroll/PlayerList
@onready var refresh_button : Button = $RefreshButton
@onready var quit_button : Button = $QuitButton
#预添加常用的面板
@onready var main_game = get_node("/root/main")
@onready var land_panel = get_node("/root/main/UI/LandPanel")
@onready var crop_store_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var player_ranking_panel = get_node("/root/main/UI/PlayerRankingPanel")
@onready var player_bag_panel = get_node("/root/main/UI/PlayerBagPanel")
@onready var network_manager = get_node("/root/main/UI/TCPNetworkManager")
#下面这是每个玩家要展示的信息直接获取服务器玩家数据json文件来实现
#模板用于复制创建新的玩家条目
@onready var player_info_template : VBoxContainer = $Scroll/PlayerList/player_ranking_item
func _ready() -> void:
# 隐藏模板
player_info_template.visible = false
# 连接按钮信号
refresh_button.pressed.connect(_on_refresh_button_pressed)
quit_button.pressed.connect(_on_quit_button_pressed)
# 初始加载排行榜
request_player_rankings()
# 请求玩家排行榜数据
func request_player_rankings():
if network_manager:
network_manager.sendGetPlayerRankings()
# 处理玩家排行榜响应
func handle_player_rankings_response(data):
# 检查响应是否成功
if not data.get("success", false):
print("获取玩家排行榜失败:", data.get("message", "未知错误"))
return
# 清除现有的玩家条目(除了模板)
for child in player_ranking_list.get_children():
if child != player_info_template:
child.queue_free()
# 添加玩家条目
var players = data.get("players", [])
for player_data in players:
add_player_entry(player_data)
# 添加单个玩家条目
func add_player_entry(player_data):
# 复制模板
var player_entry = player_info_template.duplicate()
player_entry.visible = true
player_ranking_list.add_child(player_entry)
# 设置玩家信息
var player_name = player_entry.get_node("HBox/PlayerName")
var player_level = player_entry.get_node("HBox/PlayerLevel")
var player_money = player_entry.get_node("HBox/PlayerMoney")
var player_seed_num = player_entry.get_node("HBox/SeedNum")
var player_online_time = player_entry.get_node("HBox2/OnlineTime")
var player_last_login_time = player_entry.get_node("HBox2/LastLoginTime")
var player_avatar = player_entry.get_node("HBox/PlayerAvatar")
var visit_button = player_entry.get_node("HBox/VisitButton")
# 填充数据
var username = player_data.get("user_name", "未知")
var display_name = player_data.get("player_name", username)
player_name.text = display_name
player_level.text = "等级: " + str(player_data.get("level", 0))
player_money.text = "金币: " + str(player_data.get("money", 0))
player_seed_num.text = "种子: " + str(player_data.get("seed_count", 0))
player_online_time.text = "游玩时间: " + player_data.get("total_login_time", "0时0分0秒")
player_last_login_time.text = "最后登录: " + player_data.get("last_login_time", "未知")
# 尝试加载玩家头像(使用用户名/QQ号加载头像而不是显示名
if username.is_valid_int():
player_avatar.load_from_url("http://q1.qlogo.cn/g?b=qq&nk=" + username + "&s=100")
# 设置访问按钮
visit_button.pressed.connect(func(): _on_visit_player_pressed(username))
# 访问玩家按钮点击
func _on_visit_player_pressed(username):
print("访问玩家:", username)
# 发送访问玩家请求
if network_manager and network_manager.has_method("sendVisitPlayer"):
var success = network_manager.sendVisitPlayer(username)
if success:
print("已发送访问玩家请求:", username)
else:
print("发送访问玩家请求失败,网络未连接")
else:
print("网络管理器不可用")
# 刷新按钮点击
func _on_refresh_button_pressed():
request_player_rankings()
# 退出按钮点击
func _on_quit_button_pressed():
self.hide()
# 添加到main.gd中调用
func _handle_player_rankings_response(data):
handle_player_rankings_response(data)

View File

@@ -0,0 +1 @@
uid://fk4q3x6uqydd

View File

@@ -0,0 +1,76 @@
extends Node
# 全局通用功能脚本
# 使用方法:首先在项目设置的自动加载中添加此脚本,然后在任何地方使用 GlobalFunctions.函数名() 调用
func _ready():
print("全局函数库已加载")
# 写入 TXT 文件
func write_txt_file(file_path: String, text: String, append: bool = false) -> void:
var file
if append == true:
file = FileAccess.open(file_path, FileAccess.READ_WRITE) # 追加模式
if file:
file.seek_end() # 移动光标到文件末尾
else:
file = FileAccess.open(file_path, FileAccess.WRITE) # 覆盖模式
if file:
file.store_string(text)
file.close()
if has_node("/root/ToastScript"):
get_node("/root/ToastScript").show("游戏已保存!", Color.GREEN, 5.0, 1.0)
else:
print("写入文件时打开失败: ", file_path)
if has_node("/root/ToastScript"):
get_node("/root/ToastScript").show("写入文件时打开失败!", Color.RED, 5.0, 1.0)
# 读取 TXT 文件
func read_txt_file(file_path: String) -> String:
var file = FileAccess.open(file_path, FileAccess.READ)
if file:
var text = file.get_as_text()
file.close()
return text
else:
print("打开文件失败: ", file_path)
return "false"
#生成随机数-用于作物随机死亡
func random_probability(probability: float) -> bool:
# 确保传入的概率值在 0 到 1 之间
if probability*0.001 < 0.0 or probability*0.001 > 1.0:
print("概率值必须在 0 和 1 之间")
return false
# 生成一个 0 到 1 之间的随机数
var random_value = randf()
# 如果随机数小于等于概率值,则返回 true
return random_value <= (probability*0.001)
# 格式化时间为可读字符串
func format_time(seconds: int) -> String:
var minutes = seconds / 60
seconds = seconds % 60
var hours = minutes / 60
minutes = minutes % 60
if hours > 0:
return "%02d:%02d:%02d" % [hours, minutes, seconds]
else:
return "%02d:%02d" % [minutes, seconds]
#双击切换UI事件-比如按一下打开再按一下关闭
func double_click_close(node):
if node.visible == false:
node.show()
pass
else :
node.hide()
pass
pass

View File

@@ -0,0 +1 @@
uid://bv4lf4v2c73pb

View File

@@ -0,0 +1 @@
uid://co5rk48low4kq

View File

@@ -0,0 +1 @@
uid://336lik63ehtt

View File

@@ -1,6 +1,6 @@
[gd_scene load_steps=2 format=3 uid="uid://cvip7owyfmqav"]
[ext_resource type="Script" path="res://components/toast.gd" id="1_rdgmi"]
[ext_resource type="Script" uid="uid://336lik63ehtt" path="res://Components/Toast.gd" id="1_rdgmi"]
[node name="Toast" type="Node"]
script = ExtResource("1_rdgmi")

File diff suppressed because it is too large Load Diff

1
MainGame.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://2pt11sfcaxf7

View File

@@ -1,343 +1,744 @@
[gd_scene load_steps=11 format=3 uid="uid://dgh61dttaas5a"]
[gd_scene load_steps=25 format=3 uid="uid://dgh61dttaas5a"]
[ext_resource type="Script" path="res://MainGame.gd" id="1_v3yaj"]
[ext_resource type="Script" uid="uid://2pt11sfcaxf7" path="res://MainGame.gd" id="1_v3yaj"]
[ext_resource type="Texture2D" uid="uid://b4wi8yusmbbu8" path="res://assets/GUI/玩家昵称.png" id="2_ma1re"]
[ext_resource type="Script" uid="uid://cka0r4g8tbf0" path="res://GUI/login_panel.gd" id="2_mi4js"]
[ext_resource type="Texture2D" uid="uid://cbjtfrej7iq3x" path="res://assets/background2.jpg" id="2_psm5w"]
[ext_resource type="PackedScene" uid="uid://bkivlkirrx6u8" path="res://crop_item.tscn" id="3_isiom"]
[ext_resource type="PackedScene" uid="uid://ffw2vjwnwvew" path="res://components/ToastShow.tscn" id="4_7kdbl"]
[ext_resource type="Texture2D" uid="uid://b7yavo67sf4v7" path="res://assets/GUI/green_bar.tres" id="5_vtsi2"]
[ext_resource type="Texture2D" uid="uid://d0h1s3wrx45a7" path="res://assets/GUI/white_blue_bar.tres" id="6_0sxhs"]
[ext_resource type="Texture2D" uid="uid://bc0rsd5x4pxhn" path="res://assets/GUI/orange_bar.tres" id="7_2f3e4"]
[ext_resource type="Texture2D" uid="uid://beckne13egl8u" path="res://assets/GUI/pink_bar.tres" id="8_qyoht"]
[ext_resource type="Texture2D" uid="uid://bh73krj8mnojv" path="res://assets/GUI/black_blue_bar.tres" id="9_tunh0"]
[ext_resource type="Texture2D" uid="uid://b73vvxnp31xs4" path="res://assets/GUI/red_bar.tres" id="10_duo33"]
[ext_resource type="Texture2D" uid="uid://clvhlo0mc3e7v" path="res://assets/GUI/农场名称.png" id="3_28psf"]
[ext_resource type="Script" uid="uid://bljtkxil64h14" path="res://GUI/land_panel.gd" id="3_401ut"]
[ext_resource type="PackedScene" uid="uid://bkivlkirrx6u8" path="res://CopyItems/crop_item.tscn" id="3_isiom"]
[ext_resource type="PackedScene" uid="uid://2m54c0f1ejir" path="res://CopyItems/green_crop.tscn" id="3_o8l48"]
[ext_resource type="Script" uid="uid://mtfp0ct42nrx" path="res://GUI/crop_store_panel.gd" id="3_qtrx8"]
[ext_resource type="Texture2D" uid="uid://cwloibftcmp76" path="res://assets/GUI/经验球.png" id="4_a6adi"]
[ext_resource type="Script" uid="uid://cgr332wsx63a8" path="res://GUI/player_bag_panel.gd" id="4_led80"]
[ext_resource type="Script" uid="uid://fk4q3x6uqydd" path="res://GUI/player_ranking_panel.gd" id="4_yphxy"]
[ext_resource type="PackedScene" uid="uid://cm1e72lhd7j7v" path="res://CopyItems/black_blue_crop.tscn" id="5_o8l48"]
[ext_resource type="Texture2D" uid="uid://c87kujyuxnx2s" path="res://assets/GUI/等级.png" id="5_va67g"]
[ext_resource type="PackedScene" uid="uid://crd28qnymob7" path="res://GUI/player_ranking_item.tscn" id="5_yphxy"]
[ext_resource type="PackedScene" uid="uid://forqk66f354p" path="res://CopyItems/orange_crop.tscn" id="6_0v7qb"]
[ext_resource type="Texture2D" uid="uid://c6i00d35fnl12" path="res://assets/GUI/钱币.png" id="6_t0yo1"]
[ext_resource type="Texture2D" uid="uid://ckqy5yq2ltax6" path="res://assets/GUI/小提示.png" id="7_6brkw"]
[ext_resource type="PackedScene" uid="uid://cpxiaqh0y6a5d" path="res://Network/TCPNetworkManager.tscn" id="7_401ut"]
[ext_resource type="PackedScene" uid="uid://cmdoymcviv0ai" path="res://CopyItems/pink_crop.tscn" id="7_qtrx8"]
[ext_resource type="Texture2D" uid="uid://by5qcip8tel1f" path="res://assets/GUI/服务器连接状态.png" id="8_cgwad"]
[ext_resource type="PackedScene" uid="uid://d3ve4qeggsdqy" path="res://CopyItems/red_crop.tscn" id="8_led80"]
[ext_resource type="PackedScene" uid="uid://dagh3u5med30i" path="res://CopyItems/white_blue_crop.tscn" id="9_mi4js"]
[ext_resource type="Script" uid="uid://c7bxje0wvvgo4" path="res://game_camera.gd" id="10_o8l48"]
[node name="main" type="Node"]
script = ExtResource("1_v3yaj")
[node name="background" type="Sprite2D" parent="."]
position = Vector2(590, 344)
scale = Vector2(0.658482, 0.666992)
texture = ExtResource("2_psm5w")
[node name="UI" type="CanvasLayer" parent="."]
[node name="ItemList" type="ItemList" parent="."]
visible = false
offset_left = 80.0
offset_top = 156.0
offset_right = 1050.0
offset_bottom = 606.0
scale = Vector2(1.02983, 1.02983)
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_font_sizes/font_size = 20
allow_reselect = true
allow_rmb_select = true
auto_height = true
max_columns = 100
same_column_width = true
fixed_column_width = 100
icon_mode = 0
[node name="GUI" type="Control" parent="UI"]
layout_mode = 3
anchors_preset = 0
[node name="GridContainer" type="GridContainer" parent="."]
custom_minimum_size = Vector2(100, 100)
offset_top = 143.0
offset_right = 100.0
offset_bottom = 243.0
columns = 10
[node name="HBox2" type="HBoxContainer" parent="UI/GUI"]
layout_mode = 0
offset_top = 55.0
offset_right = 1400.0
offset_bottom = 111.0
[node name="CropItem" parent="." instance=ExtResource("3_isiom")]
offset_left = -538.0
offset_top = 37.0
offset_right = -438.0
offset_bottom = 137.0
[node name="CropList" type="ItemList" parent="."]
visible = false
custom_minimum_size = Vector2(100, 100)
offset_left = 1.0
offset_top = 41.0
offset_right = 1152.0
offset_bottom = 141.0
size_flags_horizontal = 3
size_flags_vertical = 3
theme_override_font_sizes/font_size = 20
allow_reselect = true
allow_rmb_select = true
max_columns = 8
same_column_width = true
fixed_column_width = 222
icon_mode = 0
[node name="ToastShow" parent="." instance=ExtResource("4_7kdbl")]
visible = false
offset_top = 580.0
offset_bottom = 603.0
[node name="ToastShow2" parent="." instance=ExtResource("4_7kdbl")]
visible = false
offset_left = 1.0
offset_top = 41.0
offset_right = 65.0
offset_bottom = 64.0
[node name="ScrollContainer" type="ScrollContainer" parent="."]
offset_left = 1.0
offset_top = 42.0
offset_right = 2065.0
offset_bottom = 244.0
scale = Vector2(0.5, 0.5)
horizontal_scroll_mode = 0
[node name="Crop_GridContainer" type="GridContainer" parent="ScrollContainer"]
[node name="player_name_image" type="TextureRect" parent="UI/GUI/HBox2"]
layout_mode = 2
columns = 5
texture = ExtResource("2_ma1re")
expand_mode = 2
[node name="Copy_Nodes" type="Node2D" parent="."]
position = Vector2(-1000, 0)
[node name="player_name" type="Label" parent="UI/GUI/HBox2"]
modulate = Color(1, 0.670588, 0.490196, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "树萌芽"
[node name="Green" type="Button" parent="Copy_Nodes"]
offset_left = 1.0
offset_top = 42.0
offset_right = 409.0
offset_bottom = 138.0
theme_override_font_sizes/font_size = 40
text = "普通"
icon = ExtResource("5_vtsi2")
icon_alignment = 1
[node name="farm_name_image" type="TextureRect" parent="UI/GUI/HBox2"]
layout_mode = 2
texture = ExtResource("3_28psf")
expand_mode = 3
[node name="White_Blue" type="Button" parent="Copy_Nodes"]
offset_left = -5.0
offset_top = 148.0
offset_right = 403.0
offset_bottom = 244.0
theme_override_font_sizes/font_size = 40
text = "稀有"
icon = ExtResource("6_0sxhs")
icon_alignment = 1
[node name="farm_name" type="Label" parent="UI/GUI/HBox2"]
modulate = Color(1, 0.858824, 0.623529, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "树萌芽的农场"
[node name="Orange" type="Button" parent="Copy_Nodes"]
offset_left = -6.0
offset_top = 252.0
offset_right = 402.0
offset_bottom = 348.0
theme_override_font_sizes/font_size = 40
text = "优良"
icon = ExtResource("7_2f3e4")
icon_alignment = 1
[node name="status_label_image" type="TextureRect" parent="UI/GUI/HBox2"]
layout_mode = 2
texture = ExtResource("8_cgwad")
expand_mode = 2
[node name="Pink" type="Button" parent="Copy_Nodes"]
offset_left = -16.0
offset_top = 362.0
offset_right = 392.0
offset_bottom = 458.0
theme_override_font_sizes/font_size = 40
text = "史诗"
icon = ExtResource("8_qyoht")
icon_alignment = 1
[node name="StatusLabel" type="Label" parent="UI/GUI/HBox2"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "服务器状态:正在检测中"
[node name="Black_Blue" type="Button" parent="Copy_Nodes"]
offset_left = -5.0
offset_top = 481.0
offset_right = 403.0
offset_bottom = 577.0
theme_override_font_sizes/font_size = 40
text = "传奇"
icon = ExtResource("9_tunh0")
icon_alignment = 1
[node name="HBox" type="HBoxContainer" parent="UI/GUI"]
layout_mode = 0
offset_right = 1400.0
offset_bottom = 56.0
[node name="Red" type="Button" parent="Copy_Nodes"]
offset_left = 7.0
offset_top = 596.0
offset_right = 415.0
offset_bottom = 692.0
theme_override_font_sizes/font_size = 40
text = "神话"
icon = ExtResource("10_duo33")
icon_alignment = 1
[node name="experience_image" type="TextureRect" parent="UI/GUI/HBox"]
layout_mode = 2
texture = ExtResource("4_a6adi")
expand_mode = 2
[node name="HTTPRequest" type="HTTPRequest" parent="."]
[node name="GUI" type="Node2D" parent="."]
[node name="level" type="Label" parent="GUI"]
offset_left = 632.0
offset_right = 773.0
offset_bottom = 42.0
theme_override_font_sizes/font_size = 20
text = "等级100"
[node name="experience" type="Label" parent="GUI"]
offset_left = 334.0
offset_right = 475.0
offset_bottom = 42.0
theme_override_font_sizes/font_size = 20
[node name="experience" type="Label" parent="UI/GUI/HBox"]
modulate = Color(0, 1, 0, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "经验999"
[node name="LoginPanel" type="Panel" parent="GUI"]
offset_left = 379.0
offset_top = 156.0
offset_right = 765.0
offset_bottom = 444.0
[node name="VBox" type="VBoxContainer" parent="GUI/LoginPanel"]
layout_mode = 0
offset_top = 2.0
offset_right = 386.0
offset_bottom = 286.0
[node name="Title" type="Label" parent="GUI/LoginPanel/VBox"]
[node name="level_image" type="TextureRect" parent="UI/GUI/HBox"]
layout_mode = 2
texture = ExtResource("5_va67g")
expand_mode = 2
[node name="level" type="Label" parent="UI/GUI/HBox"]
modulate = Color(0, 1, 1, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "等级100"
[node name="money_image" type="TextureRect" parent="UI/GUI/HBox"]
layout_mode = 2
texture = ExtResource("6_t0yo1")
expand_mode = 2
[node name="money" type="Label" parent="UI/GUI/HBox"]
modulate = Color(1, 1, 0, 1)
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "钱币999"
[node name="tip_image" type="TextureRect" parent="UI/GUI/HBox"]
layout_mode = 2
texture = ExtResource("7_6brkw")
expand_mode = 2
[node name="tip" type="Label" parent="UI/GUI/HBox"]
layout_mode = 2
theme_override_colors/font_color = Color(1, 0, 1, 1)
theme_override_font_sizes/font_size = 30
text = "游戏小提示"
[node name="VBox" type="VBoxContainer" parent="UI/GUI"]
layout_mode = 0
offset_left = 5.0
offset_top = 522.0
offset_right = 253.0
offset_bottom = 719.0
alignment = 2
[node name="ReturnMyFarmButton" type="Button" parent="UI/GUI/VBox"]
visible = false
layout_mode = 2
theme_override_font_sizes/font_size = 40
text = "返回我的农场"
[node name="OpenStoreButton" type="Button" parent="UI/GUI/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 40
text = "种子商店"
[node name="PlayerRankingButton" type="Button" parent="UI/GUI/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 40
text = "玩家排行榜"
[node name="LoginPanel" type="PanelContainer" parent="UI"]
offset_left = 486.0
offset_top = 143.0
offset_right = 989.0
offset_bottom = 616.0
script = ExtResource("2_mi4js")
[node name="VBox" type="VBoxContainer" parent="UI/LoginPanel"]
layout_mode = 2
[node name="Title" type="Label" parent="UI/LoginPanel/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 25
text = "登录/注册面板"
horizontal_alignment = 1
vertical_alignment = 1
[node name="HBox" type="HBoxContainer" parent="GUI/LoginPanel/VBox"]
[node name="UserName" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="GUI/LoginPanel/VBox/HBox"]
[node name="Label" type="Label" parent="UI/LoginPanel/VBox/UserName"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "账号"
horizontal_alignment = 1
vertical_alignment = 1
[node name="username_input" type="LineEdit" parent="GUI/LoginPanel/VBox/HBox"]
[node name="username_input" type="LineEdit" parent="UI/LoginPanel/VBox/UserName"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
placeholder_text = "请输入QQ号..."
metadata/_edit_use_anchors_ = true
[node name="HBox2" type="HBoxContainer" parent="GUI/LoginPanel/VBox"]
[node name="Password1" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="Label2" type="Label" parent="GUI/LoginPanel/VBox/HBox2"]
[node name="Label2" type="Label" parent="UI/LoginPanel/VBox/Password1"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "密码"
horizontal_alignment = 1
vertical_alignment = 1
[node name="password_input" type="LineEdit" parent="GUI/LoginPanel/VBox/HBox2"]
[node name="password_input" type="LineEdit" parent="UI/LoginPanel/VBox/Password1"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
placeholder_text = "请输入密码..."
[node name="Title3" type="Label" parent="GUI/LoginPanel/VBox"]
[node name="Title3" type="Label" parent="UI/LoginPanel/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "以下为注册填写内容"
horizontal_alignment = 1
vertical_alignment = 1
[node name="HBox5" type="HBoxContainer" parent="GUI/LoginPanel/VBox"]
[node name="Password2" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="Label2" type="Label" parent="GUI/LoginPanel/VBox/HBox5"]
[node name="Label2" type="Label" parent="UI/LoginPanel/VBox/Password2"]
layout_mode = 2
text = "密码[选填]"
theme_override_font_sizes/font_size = 20
text = "确认密码"
horizontal_alignment = 1
vertical_alignment = 1
[node name="password_input2" type="LineEdit" parent="GUI/LoginPanel/VBox/HBox5"]
[node name="password_input2" type="LineEdit" parent="UI/LoginPanel/VBox/Password2"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
placeholder_text = "请再次输入密码(登录不需要)..."
[node name="HBox3" type="HBoxContainer" parent="GUI/LoginPanel/VBox"]
[node name="VerificationCode" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="GUI/LoginPanel/VBox/HBox3"]
[node name="Label" type="Label" parent="UI/LoginPanel/VBox/VerificationCode"]
layout_mode = 2
text = "名称[选填]"
theme_override_font_sizes/font_size = 20
text = "验证码"
horizontal_alignment = 1
vertical_alignment = 1
[node name="farmname_input" type="LineEdit" parent="GUI/LoginPanel/VBox/HBox3"]
[node name="verificationcode_input" type="LineEdit" parent="UI/LoginPanel/VBox/VerificationCode"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
placeholder_text = "请输入您的QQ邮箱收到的验证码..."
metadata/_edit_use_anchors_ = true
[node name="SendButton" type="Button" parent="UI/LoginPanel/VBox/VerificationCode"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "发送验证码"
[node name="PlayerName" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="Label2" type="Label" parent="UI/LoginPanel/VBox/PlayerName"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "玩家昵称"
horizontal_alignment = 1
vertical_alignment = 1
[node name="playername_input" type="LineEdit" parent="UI/LoginPanel/VBox/PlayerName"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
placeholder_text = "请输入您的玩家昵称..."
[node name="FarmName" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="Label" type="Label" parent="UI/LoginPanel/VBox/FarmName"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "农场名称"
horizontal_alignment = 1
vertical_alignment = 1
[node name="farmname_input" type="LineEdit" parent="UI/LoginPanel/VBox/FarmName"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
placeholder_text = "请输入您的农场名称(登录不需要)..."
metadata/_edit_use_anchors_ = true
[node name="HBox4" type="HBoxContainer" parent="GUI/LoginPanel/VBox"]
[node name="LoginRegister" type="HBoxContainer" parent="UI/LoginPanel/VBox"]
layout_mode = 2
[node name="login_button" type="Button" parent="GUI/LoginPanel/VBox/HBox4"]
[node name="login_button" type="Button" parent="UI/LoginPanel/VBox/LoginRegister"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
text = "登录"
[node name="register_button" type="Button" parent="GUI/LoginPanel/VBox/HBox4"]
[node name="register_button" type="Button" parent="UI/LoginPanel/VBox/LoginRegister"]
layout_mode = 2
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 20
text = "注册"
[node name="Title2" type="Label" parent="GUI/LoginPanel/VBox"]
[node name="Note" type="Label" parent="UI/LoginPanel/VBox"]
layout_mode = 2
text = "注意账号请输入您的QQ号方便匹配QQ好友
账号密码请不要和您的QQ密码相同防止信息泄露"
theme_override_font_sizes/font_size = 20
text = "注意账号请直接使用您的QQ号系统会直接向您的QQ
邮箱发送一串验证码进行注册验证,密码请设置的复杂一
点,以免被暴力破解"
horizontal_alignment = 1
vertical_alignment = 1
[node name="tip" type="Label" parent="GUI"]
offset_left = 878.0
offset_right = 1150.0
offset_bottom = 42.0
theme_override_colors/font_color = Color(1, 0, 1, 1)
[node name="status_label" type="Label" parent="UI/LoginPanel/VBox"]
layout_mode = 2
theme_override_font_sizes/font_size = 20
text = "游戏自动保存剩余【10】秒"
text = "连接状态"
horizontal_alignment = 1
[node name="money" type="Label" parent="GUI"]
offset_left = 1.0
offset_right = 142.0
offset_bottom = 42.0
theme_override_font_sizes/font_size = 20
text = "钱币999"
[node name="Land_Panel" type="PanelContainer" parent="GUI"]
[node name="LandPanel" type="Panel" parent="UI"]
visible = false
offset_right = 556.0
offset_bottom = 58.0
offset_left = 475.0
offset_top = 145.0
offset_right = 991.0
offset_bottom = 616.0
script = ExtResource("3_401ut")
[node name="VBox" type="VBoxContainer" parent="GUI/Land_Panel"]
layout_mode = 2
[node name="Quit_Button" type="Button" parent="UI/LandPanel"]
layout_mode = 0
offset_left = 465.0
offset_right = 515.0
offset_bottom = 50.0
theme_override_font_sizes/font_size = 30
text = "X"
[node name="Title" type="Label" parent="GUI/Land_Panel/VBox"]
[node name="HBox" type="HBoxContainer" parent="UI/LandPanel"]
layout_mode = 0
offset_right = 40.0
offset_bottom = 40.0
[node name="Title" type="Label" parent="UI/LandPanel"]
layout_mode = 2
offset_right = 516.0
offset_bottom = 42.0
theme_override_font_sizes/font_size = 30
text = "土地面板"
horizontal_alignment = 1
vertical_alignment = 1
[node name="HBox" type="HBoxContainer" parent="GUI/Land_Panel/VBox"]
[node name="Grid" type="GridContainer" parent="UI/LandPanel"]
layout_mode = 2
offset_top = 46.0
offset_right = 500.0
offset_bottom = 210.0
columns = 5
[node name="Dig_button" type="Button" parent="GUI/Land_Panel/VBox/HBox"]
[node name="Dig_Button" type="Button" parent="UI/LandPanel/Grid"]
modulate = Color(1, 0.419608, 0.352941, 1)
custom_minimum_size = Vector2(100, 100)
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "开垦"
[node name="Description" type="Label" parent="GUI/Land_Panel/VBox/HBox"]
[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
text = "土地需要开垦才能种植,开垦所需费用随玩家已开垦土地数量增多而增多"
theme_override_font_sizes/font_size = 30
text = "种植"
[node name="HBox2" type="HBoxContainer" parent="GUI/Land_Panel/VBox"]
[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
text = "铲除"
[node name="Button" type="Button" parent="GUI/Land_Panel/VBox/HBox2"]
[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
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
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
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
text = "升级"
[node name="Description" type="Label" parent="GUI/Land_Panel/VBox/HBox2"]
layout_mode = 2
text = "升级描述"
[node name="HBox3" type="HBoxContainer" parent="GUI/Land_Panel/VBox"]
[node name="PlayerRankingPanel" type="Panel" parent="UI"]
visible = false
offset_right = 1400.0
offset_bottom = 720.0
script = ExtResource("4_yphxy")
[node name="Scroll" type="ScrollContainer" parent="UI/PlayerRankingPanel"]
layout_mode = 2
offset_top = 68.0
offset_right = 1400.0
offset_bottom = 720.0
size_flags_vertical = 3
[node name="PlayerList" type="VBoxContainer" parent="UI/PlayerRankingPanel/Scroll"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
[node name="player_ranking_item" parent="UI/PlayerRankingPanel/Scroll/PlayerList" instance=ExtResource("5_yphxy")]
layout_mode = 2
[node name="Button" type="Button" parent="GUI/Land_Panel/VBox/HBox3"]
[node name="Title" type="RichTextLabel" parent="UI/PlayerRankingPanel"]
layout_mode = 2
text = "恢复"
offset_left = 2.0
offset_right = 1395.0
offset_bottom = 56.0
size_flags_vertical = 3
theme_override_font_sizes/normal_font_size = 40
bbcode_enabled = true
text = "玩家排行榜"
horizontal_alignment = 1
[node name="Description" type="Label" parent="GUI/Land_Panel/VBox/HBox3"]
[node name="QuitButton" type="Button" parent="UI/PlayerRankingPanel"]
custom_minimum_size = Vector2(55, 55)
layout_mode = 0
offset_left = 1338.0
offset_top = 3.0
offset_right = 1395.0
offset_bottom = 60.0
theme_override_font_sizes/font_size = 35
text = "X"
[node name="RefreshButton" type="Button" parent="UI/PlayerRankingPanel"]
custom_minimum_size = Vector2(55, 55)
layout_mode = 0
offset_left = 1.0
offset_top = 3.0
offset_right = 79.0
offset_bottom = 60.0
theme_override_font_sizes/font_size = 35
text = "刷新"
[node name="CropStorePanel" type="Panel" parent="UI"]
visible = false
offset_left = 1.0
offset_right = 1400.0
offset_bottom = 720.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("3_qtrx8")
[node name="SortContainer" type="HBoxContainer" parent="UI/CropStorePanel"]
layout_mode = 0
offset_top = 58.0
offset_right = 1399.0
offset_bottom = 121.0
[node name="SortLabel" type="Label" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
text = "恢复描述"
theme_override_font_sizes/font_size = 30
text = "排序:"
[connection signal="pressed" from="GUI/LoginPanel/VBox/HBox4/login_button" to="." method="_on_login_button_pressed"]
[connection signal="pressed" from="GUI/LoginPanel/VBox/HBox4/register_button" to="." method="_on_register_button_pressed"]
[connection signal="pressed" from="GUI/Land_Panel/VBox/HBox/Dig_button" to="." method="_on_dig_button_pressed"]
[node name="Sort_All" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "全部"
[node name="Sort_Common" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "普通"
[node name="Sort_Superior" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "优良"
[node name="Sort_Rare" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "稀有"
[node name="Sort_Epic" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "史诗"
[node name="Sort_Legendary" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "传奇"
[node name="Sort_Price" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按价格"
[node name="Sort_GrowTime" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按生长时间"
[node name="Sort_Profit" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按收益"
[node name="Sort_Level" type="Button" parent="UI/CropStorePanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按等级"
[node name="ScrollContainer" type="ScrollContainer" parent="UI/CropStorePanel"]
layout_mode = 2
offset_top = 134.0
offset_right = 4657.0
offset_bottom = 2087.0
scale = Vector2(0.3, 0.3)
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="Crop_Grid" type="GridContainer" parent="UI/CropStorePanel/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 11
[node name="Title" type="Label" parent="UI/CropStorePanel"]
layout_mode = 2
offset_right = 1397.0
offset_bottom = 55.0
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 40
text = "种子商店"
horizontal_alignment = 1
[node name="QuitButton" type="Button" parent="UI/CropStorePanel"]
custom_minimum_size = Vector2(60, 60)
layout_mode = 2
offset_left = 1337.0
offset_top = 3.0
offset_right = 1397.0
offset_bottom = 66.0
theme_override_font_sizes/font_size = 40
text = "X"
[node name="PlayerBagPanel" type="Panel" parent="UI"]
visible = false
offset_left = 1.0
offset_right = 1398.0
offset_bottom = 720.0
size_flags_horizontal = 3
size_flags_vertical = 3
script = ExtResource("4_led80")
[node name="SortContainer" type="HBoxContainer" parent="UI/PlayerBagPanel"]
layout_mode = 0
offset_top = 58.0
offset_right = 1397.0
offset_bottom = 121.0
[node name="SortLabel" type="Label" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "排序:"
[node name="Sort_All" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "全部"
[node name="Sort_Common" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "普通"
[node name="Sort_Superior" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "优良"
[node name="Sort_Rare" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "稀有"
[node name="Sort_Epic" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "史诗"
[node name="Sort_Legendary" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "传奇"
[node name="Sort_Price" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按价格"
[node name="Sort_GrowTime" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按生长时间"
[node name="Sort_Profit" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按收益"
[node name="Sort_Level" type="Button" parent="UI/PlayerBagPanel/SortContainer"]
layout_mode = 2
theme_override_font_sizes/font_size = 30
text = "按等级"
[node name="ScrollContainer" type="ScrollContainer" parent="UI/PlayerBagPanel"]
layout_mode = 2
offset_top = 128.0
offset_right = 4657.0
offset_bottom = 2101.0
scale = Vector2(0.3, 0.3)
size_flags_vertical = 3
horizontal_scroll_mode = 0
[node name="Bag_Grid" type="GridContainer" parent="UI/PlayerBagPanel/ScrollContainer"]
layout_mode = 2
size_flags_horizontal = 3
size_flags_vertical = 3
columns = 11
[node name="Title" type="Label" parent="UI/PlayerBagPanel"]
layout_mode = 2
offset_right = 1276.0
offset_bottom = 55.0
size_flags_horizontal = 3
theme_override_font_sizes/font_size = 40
text = "玩家背包"
horizontal_alignment = 1
[node name="QuitButton" type="Button" parent="UI/PlayerBagPanel"]
custom_minimum_size = Vector2(60, 60)
layout_mode = 2
offset_left = 1337.0
offset_right = 1397.0
offset_bottom = 63.0
theme_override_font_sizes/font_size = 40
text = "X"
[node name="TCPNetworkManager" parent="UI" instance=ExtResource("7_401ut")]
visible = false
offset_left = 2.00012
offset_top = 143.0
offset_right = 2.00012
offset_bottom = 143.0
scale = Vector2(0.7, 0.7)
[node name="BackgroundUI" type="CanvasLayer" parent="."]
layer = -1
[node name="background" type="Sprite2D" parent="BackgroundUI"]
modulate = Color(1, 1, 1, 0.372549)
show_behind_parent = true
z_index = -100
z_as_relative = false
position = Vector2(702.875, 360)
scale = Vector2(0.779157, 0.703125)
texture = ExtResource("2_psm5w")
[node name="GridContainer" type="GridContainer" parent="."]
z_as_relative = false
custom_minimum_size = Vector2(100, 100)
offset_top = 3.0
offset_right = 1400.0
offset_bottom = 720.0
columns = 10
[node name="CopyNodes" type="Node2D" parent="."]
position = Vector2(-1000, 0)
[node name="CropItem" parent="CopyNodes" instance=ExtResource("3_isiom")]
z_index = 2
z_as_relative = false
offset_left = -1433.0
offset_top = -161.0
offset_right = -1333.0
offset_bottom = -61.0
[node name="GreenCrop" parent="CopyNodes" instance=ExtResource("3_o8l48")]
offset_left = 16.0
offset_top = 143.0
offset_right = 416.0
offset_bottom = 543.0
[node name="BlackBlueCrop" parent="CopyNodes" instance=ExtResource("5_o8l48")]
offset_left = -24.0
offset_top = -27.0
offset_right = 376.0
offset_bottom = 373.0
[node name="OrangeCrop" parent="CopyNodes" instance=ExtResource("6_0v7qb")]
offset_left = 57.0
offset_top = -184.0
offset_right = 457.0
offset_bottom = 216.0
[node name="PinkCrop" parent="CopyNodes" instance=ExtResource("7_qtrx8")]
offset_left = -149.0
offset_top = -185.0
offset_right = 251.0
offset_bottom = 215.0
[node name="RedCrop" parent="CopyNodes" instance=ExtResource("8_led80")]
offset_left = -311.0
offset_top = -18.0
offset_right = 89.0
offset_bottom = 382.0
[node name="WhiteBlueCrop" parent="CopyNodes" instance=ExtResource("9_mi4js")]
offset_left = -212.0
offset_top = 134.0
offset_right = 188.0
offset_bottom = 534.0
[node name="GameCamera" type="Camera2D" parent="."]
anchor_mode = 0
position_smoothing_enabled = true
script = ExtResource("10_o8l48")
bounds_enabled = true
bounds_min = Vector2(-500, -500)
bounds_max = Vector2(500, 500)
[node name="GameManager" type="Node" parent="."]
[connection signal="pressed" from="UI/GUI/VBox/ReturnMyFarmButton" to="." method="_on_return_my_farm_button_pressed"]
[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"]

157
Network/TCPClient.gd Normal file
View File

@@ -0,0 +1,157 @@
extends Node
#一个基本的TCP客户端API
class_name TCPClient
signal connected_to_server#连接到服务器信号
signal connection_failed#连接失败信号
signal connection_closed#连接关闭信号
signal data_received(data)#收到数据信号
var tcp: StreamPeerTCP = StreamPeerTCP.new()
var host: String = "127.0.0.1"
var port: int = 4040
var is_connected: bool = false
var auto_reconnect: bool = true
var reconnect_delay: float = 2.0
# 缓冲区管理
var buffer = ""
func _ready():
pass
func _process(_delta):
# 更新连接状态
tcp.poll()
_update_connection_status()
_check_for_data()
func connect_to_server(custom_host = null, custom_port = null):
if custom_host != null:
host = custom_host
if custom_port != null:
port = custom_port
if tcp.get_status() != StreamPeerTCP.STATUS_CONNECTED:
tcp.disconnect_from_host()
print("连接到服务器: %s:%s" % [host, port])
var error = tcp.connect_to_host(host, port)
if error != OK:
print("连接错误: %s" % error)
emit_signal("connection_failed")
func disconnect_from_server():
tcp.disconnect_from_host()
is_connected = false
emit_signal("connection_closed")
func _update_connection_status():
var status = tcp.get_status()
match status:
StreamPeerTCP.STATUS_NONE:
if is_connected:
is_connected = false
print("连接已断开")
emit_signal("connection_closed")
if auto_reconnect:
var timer = get_tree().create_timer(reconnect_delay)
await timer.timeout
connect_to_server()
StreamPeerTCP.STATUS_CONNECTING:
pass
StreamPeerTCP.STATUS_CONNECTED:
if not is_connected:
is_connected = true
tcp.set_no_delay(true) # 禁用Nagle算法提高响应速度
print("已连接到服务器")
emit_signal("connected_to_server")
StreamPeerTCP.STATUS_ERROR:
is_connected = false
print("连接错误")
emit_signal("connection_failed")
if auto_reconnect:
var timer = get_tree().create_timer(reconnect_delay)
await timer.timeout
connect_to_server()
func _check_for_data():
if tcp.get_status() == StreamPeerTCP.STATUS_CONNECTED and tcp.get_available_bytes() > 0:
var bytes = tcp.get_available_bytes()
var data = tcp.get_utf8_string(bytes)
# 将数据添加到缓冲区进行处理
buffer += data
_process_buffer()
func _process_buffer():
# 处理缓冲区中的JSON消息
# 假设每条消息以换行符结尾
while "\n" in buffer:
var message_end = buffer.find("\n")
var message_text = buffer.substr(0, message_end)
buffer = buffer.substr(message_end + 1)
# 处理JSON数据
if message_text.strip_edges() != "":
var json = JSON.new()
var error = json.parse(message_text)
if error == OK:
var data = json.get_data()
#print("收到JSON数据: ", data)
emit_signal("data_received", data)
else:
# 非JSON格式数据直接传递
#print("收到原始数据: ", message_text)
emit_signal("data_received", message_text)
func send_data(data):
if not is_connected:
print("未连接,无法发送数据")
return false
var message: String
# 如果是字典/数组转换为JSON
if typeof(data) == TYPE_DICTIONARY or typeof(data) == TYPE_ARRAY:
message = JSON.stringify(data) + "\n"
else:
# 否则简单转换为字符串
message = str(data) + "\n"
var result = tcp.put_data(message.to_utf8_buffer())
return result == OK
func is_client_connected() -> bool:
return is_connected
# 示例: 如何使用此客户端
#
# func _ready():
# var client = TCPClient.new()
# add_child(client)
#
# client.connected_to_server.connect(_on_connected)
# client.connection_failed.connect(_on_connection_failed)
# client.connection_closed.connect(_on_connection_closed)
# client.data_received.connect(_on_data_received)
#
# client.connect_to_server("127.0.0.1", 9000)
#
# func _on_connected():
# print("已连接")
# client.send_data({"type": "greeting", "content": "Hello Server!"})
#
# func _on_data_received(data):
# print("收到数据: ", data)

1
Network/TCPClient.gd.uid Normal file
View File

@@ -0,0 +1 @@
uid://cylhhkh8ooxcu

View File

@@ -0,0 +1,377 @@
extends Panel
# TCP客户端演示
# 这个脚本展示如何在UI中使用TCPClient类
# UI组件引用
@onready var status_label = $StatusLabel
@onready var message_input = $MessageInput
@onready var send_button = $SendButton
@onready var response_label = $Scroll/ResponseLabel
@onready var connection_button = $ConnectionButton
@onready var login_panel = $"/root/main/UI/LoginPanel"
@onready var main_game = get_node("/root/main")
# TCP客户端
var client: TCPClient = TCPClient.new()
# 服务器配置 - 支持多个服务器地址
var server_configs = [
#{"host": "127.0.0.1", "port": 4040, "name": "本地服务器"},
#{"host": "192.168.1.110", "port": 4040, "name": "局域网服务器"},
{"host": "47.108.90.0", "port": 4040, "name": "公网服务器"}#成都内网穿透
]
var current_server_index = 0
var auto_retry = true
var retry_delay = 3.0
func _ready():
# 创建TCP客户端实例
self.add_child(client)
# 连接信号
client.connected_to_server.connect(_on_connected)
client.connection_failed.connect(_on_connection_failed)
client.connection_closed.connect(_on_connection_closed)
client.data_received.connect(_on_data_received)
# 连接按钮事件
connection_button.pressed.connect(_on_connection_button_pressed)
send_button.pressed.connect(_on_send_button_pressed)
# 初始设置
status_label.text = "未连接"
response_label.text = "等待响应..."
connection_button.text = "连接"
func _on_connected():
status_label.text = "已连接"
status_label.modulate = Color.GREEN
connection_button.text = "断开"
# 发送连接成功消息
client.send_data({
"type": "greeting",
"content": "你好,服务器!"
})
# 连接成功后立即请求作物数据
print("连接成功,正在请求最新作物数据...")
sendGetCropData()
func _on_connection_failed():
status_label.text = "连接失败"
status_label.modulate = Color.RED
connection_button.text = "连接"
# 自动尝试下一个服务器
if auto_retry:
try_next_server()
func _on_connection_closed():
status_label.text = "连接断开"
status_label.modulate = Color.RED
connection_button.text = "连接"
# 自动重连当前服务器
if auto_retry:
var timer = get_tree().create_timer(retry_delay)
await timer.timeout
if not client.is_client_connected():
_on_connection_button_pressed()
func _on_data_received(data):
# 根据数据类型处理数据
response_label.text = "收到: %s" % JSON.stringify(data)
match typeof(data):
TYPE_DICTIONARY:
# 处理JSON对象
var message_type = data.get("type", "")
match message_type:
"ping":
return
"response":
# 显示服务器响应
if data.has("original"):
var original = data.get("original", {})
return
"login_response":
# 处理登录响应
var status = data.get("status", "")
var message = data.get("message", "")
var player_data = data.get("player_data", {})
if login_panel:
# 调用登录面板的响应处理方法
login_panel._on_login_response_received(status == "success", message, player_data)
"register_response":
# 处理注册响应
var status = data.get("status", "")
var message = data.get("message", "")
if login_panel:
# 调用登录面板的响应处理方法
login_panel._on_register_response_received(status == "success", message)
"verification_code_response":
# 处理验证码发送响应
var success = data.get("success", false)
var message = data.get("message", "")
if login_panel:
# 调用登录面板的验证码响应处理方法
login_panel._on_verification_code_response(success, message)
"verify_code_response":
# 处理验证码验证响应
var success = data.get("success", false)
var message = data.get("message", "")
if login_panel:
# 调用登录面板的验证码验证响应处理方法
login_panel._on_verify_code_response(success, message)
"crop_update":
# 处理作物生长更新
if main_game:
main_game._handle_crop_update(data)
"action_response":
# 处理玩家动作响应
if main_game:
main_game._handle_action_response(data)
"play_time_response":
# 处理玩家游玩时间响应
if main_game and main_game.has_method("_handle_play_time_response"):
main_game._handle_play_time_response(data)
"player_rankings_response":
# 处理玩家排行榜响应
if main_game and main_game.has_method("_handle_player_rankings_response"):
main_game._handle_player_rankings_response(data)
"crop_data_response":
# 处理作物数据响应
if main_game and main_game.has_method("_handle_crop_data_response"):
main_game._handle_crop_data_response(data)
"visit_player_response":
# 处理访问玩家响应
if main_game and main_game.has_method("_handle_visit_player_response"):
main_game._handle_visit_player_response(data)
"return_my_farm_response":
# 处理返回自己农场响应
if main_game and main_game.has_method("_handle_return_my_farm_response"):
main_game._handle_return_my_farm_response(data)
_:
# 显示其他类型的消息
return
_:
# 处理非JSON数据
return
func _on_connection_button_pressed():
if client.is_client_connected():
# 断开连接
client.disconnect_from_server()
else:
# 连接服务器
status_label.text = "正在连接..."
client.connect_to_server(server_configs[current_server_index]["host"], server_configs[current_server_index]["port"])
func _on_send_button_pressed():
if not client.is_client_connected():
status_label.text = "未连接,无法发送"
return
# 获取输入文本
var text = message_input.text.strip_edges()
if text.is_empty():
return
# 发送消息
client.send_data({
"type": "message",
"content": text,
"timestamp": Time.get_unix_time_from_system()
})
# 清空输入
message_input.text = ""
#发送登录信息
func sendLoginInfo(username, password):
client.send_data({
"type": "login",
"username": username,
"password": password
})
#发送注册信息
func sendRegisterInfo(username, password, farmname, player_name="", verification_code=""):
client.send_data({
"type": "register",
"username": username,
"password": password,
"farm_name": farmname,
"player_name": player_name,
"verification_code": verification_code
})
#发送收获作物信息
func sendHarvestCrop(lot_index):
if not client.is_client_connected():
return false
client.send_data({
"type": "harvest_crop",
"lot_index": lot_index,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送种植作物信息
func sendPlantCrop(lot_index, crop_name):
if not client.is_client_connected():
return false
client.send_data({
"type": "plant_crop",
"lot_index": lot_index,
"crop_name": crop_name,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送开垦土地信息
func sendDigGround(lot_index):
if not client.is_client_connected():
return false
client.send_data({
"type": "dig_ground",
"lot_index": lot_index,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送购买种子信息
func sendBuySeed(crop_name):
if not client.is_client_connected():
return false
client.send_data({
"type": "buy_seed",
"crop_name": crop_name,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送获取游玩时间请求
func sendGetPlayTime():
if not client.is_client_connected():
return false
client.send_data({
"type": "get_play_time"
})
return true
#发送更新游玩时间请求
func sendUpdatePlayTime():
if not client.is_client_connected():
return false
client.send_data({
"type": "update_play_time"
})
return true
#发送获取玩家排行榜请求
func sendGetPlayerRankings():
if not client.is_client_connected():
return false
client.send_data({
"type": "request_player_rankings"
})
return true
#发送验证码请求
func sendVerificationCodeRequest(qq_number):
if not client.is_client_connected():
return false
client.send_data({
"type": "request_verification_code",
"qq_number": qq_number,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送验证码验证
func sendVerifyCode(qq_number, code):
if not client.is_client_connected():
return false
client.send_data({
"type": "verify_code",
"qq_number": qq_number,
"code": code,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送获取作物数据请求
func sendGetCropData():
if not client.is_client_connected():
return false
client.send_data({
"type": "request_crop_data"
})
return true
#发送访问玩家请求
func sendVisitPlayer(target_username):
if not client.is_client_connected():
return false
client.send_data({
"type": "visit_player",
"target_username": target_username,
"timestamp": Time.get_unix_time_from_system()
})
return true
#发送返回自己农场请求
func sendReturnMyFarm():
if not client.is_client_connected():
return false
client.send_data({
"type": "return_my_farm",
"timestamp": Time.get_unix_time_from_system()
})
return true
#检查是否连接到服务器
func is_connected_to_server():
return client.is_client_connected()
# 尝试连接下一个服务器
func try_next_server():
current_server_index = (current_server_index + 1) % server_configs.size()
var config = server_configs[current_server_index]
status_label.text = "尝试连接 " + config["name"]
print("尝试连接服务器: ", config["name"], " (", config["host"], ":", config["port"], ")")
var timer = get_tree().create_timer(retry_delay)
await timer.timeout
if not client.is_client_connected():
client.connect_to_server(config["host"], config["port"])
# 检查网络连接状态
func check_network_status():
# 检查设备是否有网络连接
if OS.get_name() == "Android":
# 在Android上检查网络状态
status_label.text = "检查网络状态..."
# 尝试连接到当前配置的服务器
if not client.is_client_connected():
_on_connection_button_pressed()

View File

@@ -0,0 +1 @@
uid://q1f3tubmdsrk

View File

@@ -0,0 +1,69 @@
[gd_scene load_steps=2 format=3 uid="uid://cpxiaqh0y6a5d"]
[ext_resource type="Script" uid="uid://q1f3tubmdsrk" path="res://Network/TCPNetworkManager.gd" id="1_tfd57"]
[node name="TCPNetworkManager" type="Panel"]
script = ExtResource("1_tfd57")
[node name="Scroll" type="ScrollContainer" parent="."]
layout_mode = 0
offset_left = 1.0
offset_top = 142.0
offset_right = 401.0
offset_bottom = 647.0
horizontal_scroll_mode = 0
[node name="ResponseLabel" type="Label" parent="Scroll"]
custom_minimum_size = Vector2(400, 500)
layout_mode = 2
theme_override_colors/font_outline_color = Color(1, 1, 1, 1)
theme_override_font_sizes/font_size = 20
text = "回应"
autowrap_mode = 3
[node name="Title" type="Label" parent="."]
layout_mode = 0
offset_right = 400.0
offset_bottom = 42.0
theme_override_font_sizes/font_size = 30
text = "TCP网络调试面板"
horizontal_alignment = 1
[node name="ColorRect" type="ColorRect" parent="."]
layout_mode = 0
offset_right = 400.0
offset_bottom = 647.0
color = Color(0.104753, 0.146763, 0.23013, 0.427451)
[node name="StatusLabel" type="Label" parent="."]
layout_mode = 0
offset_top = 100.0
offset_right = 120.0
offset_bottom = 142.0
theme_override_font_sizes/font_size = 30
text = "连接状态"
[node name="MessageInput" type="LineEdit" parent="."]
layout_mode = 0
offset_left = 136.0
offset_top = 50.0
offset_right = 400.0
offset_bottom = 100.0
theme_override_font_sizes/font_size = 30
[node name="SendButton" type="Button" parent="."]
layout_mode = 0
offset_left = 68.0
offset_top = 50.0
offset_right = 136.0
offset_bottom = 100.0
theme_override_font_sizes/font_size = 30
text = "发送"
[node name="ConnectionButton" type="Button" parent="."]
layout_mode = 0
offset_top = 50.0
offset_right = 68.0
offset_bottom = 100.0
theme_override_font_sizes/font_size = 30
text = "连接"

100
Server/JsonEdit/README.md Normal file
View File

@@ -0,0 +1,100 @@
# 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数据的场景。

View File

@@ -0,0 +1,65 @@
# 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
}
}
```

View File

@@ -0,0 +1,267 @@
#!/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)

View File

@@ -0,0 +1,2 @@
Flask==2.3.3
Werkzeug==2.3.7

View File

@@ -0,0 +1,627 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>简易JSON编辑器 - 批量添加键值</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background: #f5f5f5;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
overflow: hidden;
}
.header {
background: #4a90e2;
color: white;
padding: 20px;
text-align: center;
}
.header h1 {
margin-bottom: 5px;
}
.main-content {
display: grid;
grid-template-columns: 300px 1fr;
gap: 0;
min-height: 600px;
}
.sidebar {
background: #f8f9fa;
border-right: 1px solid #dee2e6;
padding: 20px;
}
.editor-area {
padding: 20px;
}
.section {
background: white;
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 15px;
margin-bottom: 15px;
}
.section h3 {
margin-bottom: 15px;
color: #333;
}
.btn {
background: #4a90e2;
color: white;
border: none;
padding: 10px 20px;
border-radius: 5px;
cursor: pointer;
font-size: 14px;
margin: 5px 5px 5px 0;
width: 100%;
}
.btn:hover {
background: #357abd;
}
.btn-success {
background: #28a745;
}
.btn-success:hover {
background: #218838;
}
.input-group {
margin-bottom: 15px;
}
.input-group label {
display: block;
margin-bottom: 5px;
font-weight: 500;
color: #333;
}
.input-group input {
width: 100%;
padding: 8px 12px;
border: 1px solid #dee2e6;
border-radius: 5px;
font-size: 14px;
}
.input-group input:focus {
outline: none;
border-color: #4a90e2;
}
#jsonEditor {
width: 100%;
height: 500px;
border: 1px solid #dee2e6;
border-radius: 5px;
padding: 15px;
font-family: 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
resize: vertical;
}
#jsonEditor:focus {
outline: none;
border-color: #4a90e2;
}
.file-upload {
position: relative;
display: inline-block;
cursor: pointer;
overflow: hidden;
width: 100%;
}
.file-upload input[type=file] {
position: absolute;
left: -9999px;
}
.alert {
padding: 10px 15px;
margin-bottom: 15px;
border-radius: 5px;
font-weight: 500;
}
.alert-success {
background: #d4edda;
color: #155724;
border: 1px solid #c3e6cb;
}
.alert-error {
background: #f8d7da;
color: #721c24;
border: 1px solid #f5c6cb;
}
.alert-info {
background: #d1ecf1;
color: #0c5460;
border: 1px solid #bee5eb;
}
@media (max-width: 768px) {
.main-content {
grid-template-columns: 1fr;
}
}
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>🔧 JSON批量编辑器</h1>
<p>批量添加键值对到JSON文件</p>
</div>
<div class="main-content">
<!-- 侧边栏 -->
<div class="sidebar">
<!-- 文件操作 -->
<div class="section">
<h3>📁 文件操作</h3>
<div class="file-upload btn">
<input type="file" id="fileInput" accept=".json" />
上传JSON文件
</div>
<button class="btn btn-success" onclick="downloadJSON()">
下载修改后的JSON
</button>
</div>
<!-- 批量添加键值 -->
<div class="section">
<h3>⚡ 批量添加键值</h3>
<div class="input-group">
<label for="keyName">键名:</label>
<input type="text" id="keyName" placeholder="例: 能否购买" />
</div>
<div class="input-group">
<label for="keyValue">键值:</label>
<input type="text" id="keyValue" placeholder="支持多种类型,见下方说明" />
</div>
<button class="btn btn-success" onclick="batchAddProperty()">
批量添加到所有对象
</button>
<div style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; font-size: 12px;">
<strong>支持的数据类型:</strong><br>
• 字符串: hello world<br>
• 数字: 123 或 3.14<br>
• 布尔值: true 或 false<br>
• 空值: null<br>
• 对象: {"key": "value"}<br>
• 数组: [1, 2, 3]<br>
<small style="color: #666;">系统会自动识别并转换数据类型</small>
</div>
</div>
<!-- 快速示例 -->
<div class="section">
<h3>📝 快速示例</h3>
<button class="btn" onclick="loadSampleJSON()">
加载示例数据
</button>
<div style="margin-top: 15px;">
<strong style="font-size: 12px;">快速填入示例键值:</strong><br>
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('能否购买', 'true')">
布尔值示例
</button>
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('价格', '150')">
数字示例
</button>
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('备注', '新增属性')">
字符串示例
</button>
<button class="btn" style="font-size: 11px; padding: 5px 10px; margin: 2px;" onclick="fillExample('tags', '[&quot;新&quot;, &quot;热门&quot;]')">
数组示例
</button>
</div>
</div>
<!-- JSON格式化操作 -->
<div class="section">
<h3>🔧 格式化操作</h3>
<button class="btn" onclick="formatJSONStandard()">
标准格式化
</button>
<button class="btn btn-success" onclick="minifyJSON()">
最小化(压缩)
</button>
<button class="btn" style="background: #17a2b8;" onclick="oneLinePerObject()">
一行化(一个对象一行)
</button>
<button class="btn" style="background: #6f42c1; color: white;" onclick="validateJSON()">
验证JSON格式
</button>
<div style="margin-top: 15px; padding: 10px; background: #f8f9fa; border-radius: 5px; font-size: 12px;">
<strong>格式化说明:</strong><br>
<strong>标准格式化</strong>: 2空格缩进易于阅读<br>
<strong>最小化</strong>: 去除空格,节省空间<br>
<strong>一行化</strong>: 每个对象占一行,便于比较<br>
<strong>验证格式</strong>: 检查JSON语法是否正确<br>
</div>
</div>
</div>
<!-- 编辑区域 -->
<div class="editor-area">
<!-- 消息区域 -->
<div id="messageArea"></div>
<!-- JSON编辑器 -->
<textarea id="jsonEditor" placeholder="在此输入或上传JSON数据..."></textarea>
</div>
</div>
</div>
<script>
// 初始化
document.addEventListener('DOMContentLoaded', function() {
document.getElementById('fileInput').addEventListener('change', handleFileUpload);
});
// 显示消息
function showMessage(message, type = 'success') {
const messageArea = document.getElementById('messageArea');
let alertClass = 'alert-success';
if (type === 'error') {
alertClass = 'alert-error';
} else if (type === 'info') {
alertClass = 'alert-info';
}
messageArea.innerHTML = `
<div class="alert ${alertClass}">
${message}
</div>
`;
setTimeout(() => {
messageArea.innerHTML = '';
}, 3000);
}
// 加载示例JSON
function loadSampleJSON() {
const sampleJSON = {
"测试作物": {
"花费": 1,
"生长时间": 3,
"收益": 9999,
"品质": "普通"
},
"小麦": {
"花费": 120,
"生长时间": 120,
"收益": 100,
"品质": "普通"
},
"稻谷": {
"花费": 100,
"生长时间": 240,
"收益": 120,
"品质": "普通"
}
};
document.getElementById('jsonEditor').value = JSON.stringify(sampleJSON, null, 2);
showMessage('示例数据已加载');
}
// 快速填入示例键值对
function fillExample(keyName, keyValue) {
document.getElementById('keyName').value = keyName;
document.getElementById('keyValue').value = keyValue;
showMessage(`已填入示例: ${keyName} = ${keyValue}`, 'info');
}
// 文件上传处理
function handleFileUpload(event) {
const file = event.target.files[0];
if (!file) return;
if (!file.name.toLowerCase().endsWith('.json')) {
showMessage('请选择JSON文件', 'error');
return;
}
const reader = new FileReader();
reader.onload = function(e) {
try {
const content = e.target.result;
JSON.parse(content); // 验证JSON格式
document.getElementById('jsonEditor').value = content;
showMessage('文件上传成功');
} catch (error) {
showMessage('JSON格式错误: ' + error.message, 'error');
}
};
reader.readAsText(file);
// 清空文件输入
event.target.value = '';
}
// 批量添加属性
function batchAddProperty() {
const keyName = document.getElementById('keyName').value.trim();
const keyValue = document.getElementById('keyValue').value.trim();
const jsonContent = document.getElementById('jsonEditor').value.trim();
if (!keyName) {
showMessage('请输入键名', 'error');
return;
}
if (!jsonContent) {
showMessage('请输入或上传JSON数据', 'error');
return;
}
try {
let jsonData = JSON.parse(jsonContent);
// 智能类型转换
let processedValue = parseValue(keyValue);
// 批量添加属性
const result = addPropertyToAllObjects(jsonData, keyName, processedValue);
if (result.count > 0) {
document.getElementById('jsonEditor').value = JSON.stringify(jsonData, null, 2);
showMessage(`成功为 ${result.count} 个对象添加了属性 "${keyName}": ${JSON.stringify(processedValue)}`);
} else {
showMessage('未找到可添加属性的对象', 'info');
}
} catch (error) {
showMessage('JSON格式错误: ' + error.message, 'error');
}
}
// 智能解析值的类型
function parseValue(value) {
// 如果输入为空,返回空字符串
if (value === '') {
return '';
}
// 尝试解析为null
if (value.toLowerCase() === 'null') {
return null;
}
// 尝试解析为undefined虽然JSON不支持但转为null
if (value.toLowerCase() === 'undefined') {
return null;
}
// 尝试解析为布尔值
if (value.toLowerCase() === 'true') {
return true;
}
if (value.toLowerCase() === 'false') {
return false;
}
// 尝试解析为数字
if (!isNaN(value) && !isNaN(parseFloat(value))) {
// 检查是否为整数
if (Number.isInteger(parseFloat(value))) {
return parseInt(value, 10);
} else {
return parseFloat(value);
}
}
// 尝试解析为JSON对象或数组
if ((value.startsWith('{') && value.endsWith('}')) ||
(value.startsWith('[') && value.endsWith(']'))) {
try {
return JSON.parse(value);
} catch (e) {
// 如果解析失败,当作字符串处理
return value;
}
}
// 默认当作字符串处理
return value;
}
// 递归为所有对象添加属性
function addPropertyToAllObjects(obj, key, value) {
let count = 0;
function traverse(current) {
if (typeof current === 'object' && current !== null) {
if (Array.isArray(current)) {
// 处理数组
current.forEach(item => traverse(item));
} else {
// 处理对象
current[key] = value;
count++;
// 继续递归处理嵌套对象
Object.values(current).forEach(val => {
if (typeof val === 'object' && val !== null && val !== current) {
traverse(val);
}
});
}
}
}
traverse(obj);
return { count };
}
// 下载JSON
function downloadJSON() {
const content = document.getElementById('jsonEditor').value.trim();
if (!content) {
showMessage('没有可下载的内容', 'error');
return;
}
try {
// 验证JSON格式
JSON.parse(content);
const blob = new Blob([content], { type: 'application/json' });
const url = URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = url;
link.download = `edited_json_${new Date().getTime()}.json`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
showMessage('JSON文件下载成功');
} catch (error) {
showMessage('JSON格式错误无法下载: ' + error.message, 'error');
}
}
// 标准格式化JSON
function formatJSONStandard() {
const content = document.getElementById('jsonEditor').value.trim();
if (!content) {
showMessage('请输入JSON数据', 'error');
return;
}
try {
const jsonData = JSON.parse(content);
const formatted = JSON.stringify(jsonData, null, 2);
document.getElementById('jsonEditor').value = formatted;
showMessage('JSON标准格式化完成');
} catch (error) {
showMessage('JSON格式错误: ' + error.message, 'error');
}
}
// 最小化JSON压缩
function minifyJSON() {
const content = document.getElementById('jsonEditor').value.trim();
if (!content) {
showMessage('请输入JSON数据', 'error');
return;
}
try {
const jsonData = JSON.parse(content);
const minified = JSON.stringify(jsonData);
document.getElementById('jsonEditor').value = minified;
showMessage('JSON最小化完成');
} catch (error) {
showMessage('JSON格式错误: ' + error.message, 'error');
}
}
// 一行化格式(一个对象一行)
function oneLinePerObject() {
const content = document.getElementById('jsonEditor').value.trim();
if (!content) {
showMessage('请输入JSON数据', 'error');
return;
}
try {
const jsonData = JSON.parse(content);
let formatted = '';
if (Array.isArray(jsonData)) {
// 如果是数组,每个元素占一行
formatted = '[\n';
jsonData.forEach((item, index) => {
formatted += ' ' + JSON.stringify(item);
if (index < jsonData.length - 1) {
formatted += ',';
}
formatted += '\n';
});
formatted += ']';
} else if (typeof jsonData === 'object' && jsonData !== null) {
// 如果是对象,每个键值对占一行
formatted = '{\n';
const keys = Object.keys(jsonData);
keys.forEach((key, index) => {
formatted += ' ' + JSON.stringify(key) + ': ' + JSON.stringify(jsonData[key]);
if (index < keys.length - 1) {
formatted += ',';
}
formatted += '\n';
});
formatted += '}';
} else {
// 基本类型直接输出
formatted = JSON.stringify(jsonData);
}
document.getElementById('jsonEditor').value = formatted;
showMessage('JSON一行化格式完成');
} catch (error) {
showMessage('JSON格式错误: ' + error.message, 'error');
}
}
// 验证JSON格式
function validateJSON() {
const content = document.getElementById('jsonEditor').value.trim();
if (!content) {
showMessage('请输入JSON数据', 'error');
return;
}
try {
JSON.parse(content);
showMessage('JSON格式验证通过');
} catch (error) {
showMessage('JSON格式错误: ' + error.message, 'error');
}
}
</script>
</body>
</html>

337
Server/QQEmailSend.py Normal file
View File

@@ -0,0 +1,337 @@
import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from email.mime.application import MIMEApplication
from email.header import Header
import random
import string
import json
import os
# 邮件发送配置
SENDER_EMAIL = '3205788256@qq.com' # 发件人邮箱
SENDER_AUTH_CODE = 'szcaxvbftusqddhi' # 授权码
SMTP_SERVER = 'smtp.qq.com' # QQ邮箱SMTP服务器
SMTP_PORT = 465 # QQ邮箱SSL端口
# 验证码缓存文件
VERIFICATION_CACHE_FILE = os.path.join("config", "verification_codes.json")
class QQMailAPI:
"""QQ邮箱发送邮件API类"""
def __init__(self, sender_email, authorization_code):
"""
初始化邮箱配置
:param sender_email: 发送方QQ邮箱地址
:param authorization_code: QQ邮箱授权码
"""
self.sender_email = sender_email
self.authorization_code = authorization_code
self.smtp_server = 'smtp.qq.com'
self.smtp_port = 465 # SSL端口
def send_text_email(self, receiver_email, subject, content, cc_emails=None):
"""
发送纯文本邮件
:param receiver_email: 接收方邮箱地址(单个)
:param subject: 邮件主题
:param content: 邮件正文内容
:param cc_emails: 抄送邮箱列表
:return: 发送成功返回True失败返回False
"""
try:
# 创建邮件对象
message = MIMEText(content, 'plain', 'utf-8')
message['From'] = Header(self.sender_email, 'utf-8')
message['To'] = Header(receiver_email, 'utf-8')
message['Subject'] = Header(subject, 'utf-8')
# 添加抄送
if cc_emails:
message['Cc'] = Header(",".join(cc_emails), 'utf-8')
all_receivers = [receiver_email] + cc_emails
else:
all_receivers = [receiver_email]
# 连接SMTP服务器并发送邮件
with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
server.login(self.sender_email, self.authorization_code)
server.sendmail(self.sender_email, all_receivers, message.as_string())
print(f"邮件发送成功:主题='{subject}', 收件人='{receiver_email}'")
return True
except Exception as e:
print(f"邮件发送失败:{str(e)}")
return False
def send_html_email(self, receiver_email, subject, html_content, cc_emails=None, attachments=None):
"""
发送HTML格式邮件可带附件
:param receiver_email: 接收方邮箱地址(单个)
:param subject: 邮件主题
:param html_content: HTML格式的邮件正文
:param cc_emails: 抄送邮箱列表
:param attachments: 附件文件路径列表
:return: 发送成功返回True失败返回False
"""
try:
# 创建带附件的邮件对象
message = MIMEMultipart()
message['From'] = Header(self.sender_email, 'utf-8')
message['To'] = Header(receiver_email, 'utf-8')
message['Subject'] = Header(subject, 'utf-8')
# 添加抄送
if cc_emails:
message['Cc'] = Header(",".join(cc_emails), 'utf-8')
all_receivers = [receiver_email] + cc_emails
else:
all_receivers = [receiver_email]
# 添加HTML正文
message.attach(MIMEText(html_content, 'html', 'utf-8'))
# 添加附件
if attachments:
for file_path in attachments:
try:
with open(file_path, 'rb') as file:
attachment = MIMEApplication(file.read(), _subtype="octet-stream")
attachment.add_header('Content-Disposition', 'attachment', filename=file_path.split("/")[-1])
message.attach(attachment)
except Exception as e:
print(f"添加附件失败 {file_path}: {str(e)}")
# 连接SMTP服务器并发送邮件
with smtplib.SMTP_SSL(self.smtp_server, self.smtp_port) as server:
server.login(self.sender_email, self.authorization_code)
server.sendmail(self.sender_email, all_receivers, message.as_string())
print(f"HTML邮件发送成功主题='{subject}', 收件人='{receiver_email}'")
return True
except Exception as e:
print(f"HTML邮件发送失败{str(e)}")
return False
class EmailVerification:
@staticmethod
def generate_verification_code(length=6):
"""
生成指定长度的随机验证码
参数:
length (int): 验证码长度默认6位
返回:
str: 生成的验证码
"""
# 生成包含大写字母和数字的验证码
chars = string.ascii_uppercase + string.digits
return ''.join(random.choice(chars) for _ in range(length))
@staticmethod
def send_verification_email(qq_number, verification_code):
"""
发送验证码邮件到QQ邮箱
参数:
qq_number (str): 接收者QQ号
verification_code (str): 验证码
返回:
bool: 发送成功返回True否则返回False
str: 成功或错误信息
"""
receiver_email = f"{qq_number}@qq.com"
# 创建邮件内容
message = MIMEText(f'''
<html>
<body>
<div style="font-family: Arial, sans-serif; color: #333;">
<h2 style="color: #4CAF50;">萌芽农场 - 邮箱验证码</h2>
<p>亲爱的玩家,您好!</p>
<p>您正在注册萌芽农场游戏账号,您的验证码是:</p>
<div style="background-color: #f2f2f2; padding: 10px; font-size: 24px; font-weight: bold; color: #4CAF50; text-align: center; margin: 20px 0;">
{verification_code}
</div>
<p>该验证码有效期为5分钟请勿泄露给他人。</p>
<p>如果这不是您本人的操作,请忽略此邮件。</p>
<p style="margin-top: 30px; font-size: 12px; color: #999;">
本邮件由系统自动发送,请勿直接回复。
</p>
</div>
</body>
</html>
''', 'html', 'utf-8')
# 修正From头格式符合QQ邮箱的要求
message['From'] = SENDER_EMAIL
message['To'] = receiver_email
message['Subject'] = Header('【萌芽农场】注册验证码', 'utf-8')
try:
# 使用SSL/TLS连接而不是STARTTLS
smtp_obj = smtplib.SMTP_SSL(SMTP_SERVER, 465)
smtp_obj.login(SENDER_EMAIL, SENDER_AUTH_CODE)
smtp_obj.sendmail(SENDER_EMAIL, [receiver_email], message.as_string())
smtp_obj.quit()
return True, "验证码发送成功"
except Exception as e:
return False, f"发送验证码失败: {str(e)}"
@staticmethod
def save_verification_code(qq_number, verification_code, expiry_time=300):
"""
保存验证码到缓存文件
参数:
qq_number (str): QQ号
verification_code (str): 验证码
expiry_time (int): 过期时间默认5分钟
返回:
bool: 保存成功返回True否则返回False
"""
import time
# 创建目录(如果不存在)
os.makedirs(os.path.dirname(VERIFICATION_CACHE_FILE), exist_ok=True)
# 读取现有的验证码数据
verification_data = {}
if os.path.exists(VERIFICATION_CACHE_FILE):
try:
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
verification_data = json.load(file)
except:
verification_data = {}
# 添加新的验证码
expire_at = time.time() + expiry_time
verification_data[qq_number] = {
"code": verification_code,
"expire_at": expire_at
}
# 保存到文件
try:
with open(VERIFICATION_CACHE_FILE, 'w', encoding='utf-8') as file:
json.dump(verification_data, file, indent=2, ensure_ascii=False)
return True
except Exception as e:
print(f"保存验证码失败: {str(e)}")
return False
@staticmethod
def verify_code(qq_number, input_code):
"""
验证用户输入的验证码
参数:
qq_number (str): QQ号
input_code (str): 用户输入的验证码
返回:
bool: 验证成功返回True否则返回False
str: 成功或错误信息
"""
import time
# 检查缓存文件是否存在
if not os.path.exists(VERIFICATION_CACHE_FILE):
return False, "验证码不存在或已过期"
# 读取验证码数据
try:
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
verification_data = json.load(file)
except:
return False, "验证码数据损坏"
# 检查该QQ号是否有验证码
if qq_number not in verification_data:
return False, "验证码不存在,请重新获取"
# 获取存储的验证码信息
code_info = verification_data[qq_number]
stored_code = code_info.get("code", "")
expire_at = code_info.get("expire_at", 0)
# 检查验证码是否过期
current_time = time.time()
if current_time > expire_at:
# 移除过期的验证码
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 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, "验证码正确"
else:
return False, "验证码错误"
@staticmethod
def clean_expired_codes():
"""
清理过期的验证码
"""
import time
if not os.path.exists(VERIFICATION_CACHE_FILE):
return
try:
with open(VERIFICATION_CACHE_FILE, 'r', encoding='utf-8') as file:
verification_data = json.load(file)
current_time = time.time()
removed_keys = []
# 找出过期的验证码
for qq_number, code_info in verification_data.items():
expire_at = code_info.get("expire_at", 0)
if current_time > expire_at:
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)
except Exception as e:
print(f"清理过期验证码失败: {str(e)}")
# 测试邮件发送
if __name__ == "__main__":
# 清理过期验证码
EmailVerification.clean_expired_codes()
# 生成验证码
test_qq = input("请输入测试QQ号: ")
verification_code = EmailVerification.generate_verification_code()
print(f"生成的验证码: {verification_code}")
# 发送测试邮件
success, message = EmailVerification.send_verification_email(test_qq, verification_code)
print(f"发送结果: {success}, 消息: {message}")
if success:
# 保存验证码
EmailVerification.save_verification_code(test_qq, verification_code)
# 测试验证
test_input = input("请输入收到的验证码: ")
verify_success, verify_message = EmailVerification.verify_code(test_qq, test_input)
print(f"验证结果: {verify_success}, 消息: {verify_message}")

BIN
Server/QQEmailSend.zip Normal file

Binary file not shown.

1519
Server/TCPGameServer.py Normal file

File diff suppressed because it is too large Load Diff

355
Server/TCPServer.py Normal file
View File

@@ -0,0 +1,355 @@
import socket
import threading
import json
import time
import sys
import logging
import colorama
from datetime import datetime
# 初始化colorama以支持跨平台彩色终端输出
colorama.init()
# 自定义日志格式化器,带有颜色和分类
class MinecraftStyleFormatter(logging.Formatter):
"""Minecraft风格的日志格式化器带有颜色和分类"""
# ANSI颜色代码
COLORS = {
'RESET': colorama.Fore.RESET,
'BLACK': colorama.Fore.BLACK,
'RED': colorama.Fore.RED,
'GREEN': colorama.Fore.GREEN,
'YELLOW': colorama.Fore.YELLOW,
'BLUE': colorama.Fore.BLUE,
'MAGENTA': colorama.Fore.MAGENTA,
'CYAN': colorama.Fore.CYAN,
'WHITE': colorama.Fore.WHITE,
'BRIGHT_BLACK': colorama.Fore.LIGHTBLACK_EX,
'BRIGHT_RED': colorama.Fore.LIGHTRED_EX,
'BRIGHT_GREEN': colorama.Fore.LIGHTGREEN_EX,
'BRIGHT_YELLOW': colorama.Fore.LIGHTYELLOW_EX,
'BRIGHT_BLUE': colorama.Fore.LIGHTBLUE_EX,
'BRIGHT_MAGENTA': colorama.Fore.LIGHTMAGENTA_EX,
'BRIGHT_CYAN': colorama.Fore.LIGHTCYAN_EX,
'BRIGHT_WHITE': colorama.Fore.LIGHTWHITE_EX,
}
# 日志级别颜色类似于Minecraft
LEVEL_COLORS = {
'DEBUG': COLORS['BRIGHT_BLACK'],
'INFO': COLORS['WHITE'],
'WARNING': COLORS['YELLOW'],
'ERROR': COLORS['RED'],
'CRITICAL': COLORS['BRIGHT_RED'],
}
# 类别及其颜色
CATEGORIES = {
'SERVER': COLORS['BRIGHT_CYAN'],
'NETWORK': COLORS['BRIGHT_GREEN'],
'CLIENT': COLORS['BRIGHT_YELLOW'],
'SYSTEM': COLORS['BRIGHT_MAGENTA'],
}
def format(self, record):
# 获取日志级别颜色
level_color = self.LEVEL_COLORS.get(record.levelname, self.COLORS['WHITE'])
# 从记录名称中确定类别默认为SERVER
category_name = getattr(record, 'category', 'SERVER')
category_color = self.CATEGORIES.get(category_name, self.COLORS['WHITE'])
# 格式化时间戳类似于Minecraft[HH:MM:SS]
timestamp = datetime.now().strftime('%H:%M:%S')
# 格式化消息
formatted_message = f"{self.COLORS['BRIGHT_BLACK']}[{timestamp}] {category_color}[{category_name}] {level_color}{record.levelname}: {record.getMessage()}{self.COLORS['RESET']}"
return formatted_message
class TCPServer:
def __init__(self, host='127.0.0.1', port=9000, buffer_size=4096):
"""初始化TCP服务器"""
self.host = host
self.port = port
self.buffer_size = buffer_size
self.socket = None
self.clients = {} # 存储客户端连接 {client_id: (socket, address)}
self.running = False
self.client_buffers = {} # 每个客户端的消息缓冲区
# 配置日志
self._setup_logging()
def _setup_logging(self):
"""设置Minecraft风格的日志系统"""
# 创建日志器
self.logger = logging.getLogger('TCPServer')
self.logger.setLevel(logging.INFO)
# 清除任何现有的处理器
if self.logger.handlers:
self.logger.handlers.clear()
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
# 设置格式化器
formatter = MinecraftStyleFormatter()
console_handler.setFormatter(formatter)
# 添加处理器到日志器
self.logger.addHandler(console_handler)
def log(self, level, message, category='SERVER'):
"""使用指定的分类和级别记录日志"""
record = logging.LogRecord(
name=self.logger.name,
level=getattr(logging, level),
pathname='',
lineno=0,
msg=message,
args=(),
exc_info=None
)
record.category = category
self.logger.handle(record)
def start(self):
"""启动服务器"""
try:
# 创建TCP套接字
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 禁用Nagle算法
# 绑定地址和监听
self.socket.bind((self.host, self.port))
self.socket.listen(5)
self.running = True
self.log('INFO', f"服务器启动,监听 {self.host}:{self.port}", 'SERVER')
# 接受客户端连接的主循环
self._accept_clients()
except Exception as e:
self.log('ERROR', f"服务器启动错误: {e}", 'SYSTEM')
self.stop()
def _accept_clients(self):
"""接受客户端连接的循环"""
while self.running:
try:
# 接受新的客户端连接
client_socket, address = self.socket.accept()
client_id = f"{address[0]}:{address[1]}"
self.log('INFO', f"新客户端连接: {client_id}", 'NETWORK')
# 存储客户端信息
self.clients[client_id] = (client_socket, address)
self.client_buffers[client_id] = ""
# 创建处理线程
client_thread = threading.Thread(
target=self._handle_client,
args=(client_id,)
)
client_thread.daemon = True
client_thread.start()
# 通知客户端连接成功
self.send_data(client_id, {"type": "connection_status", "status": "connected"})
except KeyboardInterrupt:
self.log('INFO', "收到中断信号,服务器停止中...", 'SYSTEM')
break
except Exception as e:
self.log('ERROR', f"接受连接时出错: {e}", 'NETWORK')
time.sleep(1) # 避免CPU过度使用
def _handle_client(self, client_id):
"""处理客户端消息的线程"""
client_socket, _ = self.clients.get(client_id, (None, None))
if not client_socket:
return
# 设置超时,用于定期检查连接状态
client_socket.settimeout(30)
while self.running and client_id in self.clients:
try:
# 接收数据
data = client_socket.recv(self.buffer_size)
if not data:
# 客户端断开连接
self.log('INFO', f"客户端 {client_id} 断开连接", 'CLIENT')
self._remove_client(client_id)
break
# 处理接收的数据
self._process_data(client_id, data)
except socket.timeout:
# 发送保活消息
try:
self.send_data(client_id, {"type": "ping"})
except:
self.log('INFO', f"客户端 {client_id} 连接超时", 'CLIENT')
self._remove_client(client_id)
break
except Exception as e:
self.log('ERROR', f"处理客户端 {client_id} 数据时出错: {e}", 'CLIENT')
self._remove_client(client_id)
break
def _process_data(self, client_id, data):
"""处理从客户端接收的数据"""
# 将接收的字节添加到缓冲区
try:
decoded_data = data.decode('utf-8')
self.client_buffers[client_id] += decoded_data
# 处理可能包含多条JSON消息的缓冲区
self._process_buffer(client_id)
except UnicodeDecodeError as e:
self.log('ERROR', f"解码客户端 {client_id} 数据出错: {e}", 'CLIENT')
def _process_buffer(self, client_id):
"""处理客户端消息缓冲区"""
buffer = self.client_buffers.get(client_id, "")
# 按换行符分割消息
while '\n' in buffer:
message_end = buffer.find('\n')
message_text = buffer[:message_end].strip()
buffer = buffer[message_end + 1:]
# 处理非空消息
if message_text:
try:
# 解析JSON消息
message = json.loads(message_text)
self.log('INFO', f"从客户端 {client_id} 接收JSON: {message}", 'CLIENT')
# 处理消息 - 实现自定义逻辑
self._handle_message(client_id, message)
except json.JSONDecodeError:
# 非JSON格式作为原始文本处理
self.log('INFO', f"从客户端 {client_id} 接收文本: {message_text}", 'CLIENT')
self._handle_raw_message(client_id, message_text)
# 更新缓冲区
self.client_buffers[client_id] = buffer
def _handle_message(self, client_id, message):
"""处理JSON消息 - 可被子类覆盖以实现自定义逻辑"""
# 默认实现:简单回显
response = {
"type": "response",
"original": message,
"timestamp": time.time()
}
self.send_data(client_id, response)
def _handle_raw_message(self, client_id, message):
"""处理原始文本消息 - 可被子类覆盖以实现自定义逻辑"""
# 默认实现:简单回显
response = {
"type": "text_response",
"content": f"收到: {message}",
"timestamp": time.time()
}
self.send_data(client_id, response)
def send_data(self, client_id, data):
"""向指定客户端发送JSON数据"""
if client_id not in self.clients:
self.log('WARNING', f"客户端 {client_id} 不存在,无法发送数据", 'NETWORK')
return False
client_socket, _ = self.clients[client_id]
try:
# 转换为JSON字符串添加换行符
if isinstance(data, (dict, list)):
message = json.dumps(data) + '\n'
else:
message = str(data) + '\n'
# 发送数据
client_socket.sendall(message.encode('utf-8'))
return True
except Exception as e:
self.log('ERROR', f"向客户端 {client_id} 发送数据时出错: {e}", 'NETWORK')
self._remove_client(client_id)
return False
def broadcast(self, data, exclude=None):
"""向所有客户端广播消息,可选排除特定客户端"""
exclude = exclude or []
for client_id in list(self.clients.keys()):
if client_id not in exclude:
self.send_data(client_id, data)
def _remove_client(self, client_id):
"""断开并移除客户端连接"""
if client_id in self.clients:
client_socket, _ = self.clients[client_id]
try:
client_socket.close()
except:
pass
del self.clients[client_id]
if client_id in self.client_buffers:
del self.client_buffers[client_id]
self.log('INFO', f"客户端 {client_id} 已移除", 'CLIENT')
def stop(self):
"""停止服务器"""
self.running = False
# 关闭所有客户端连接
for client_id in list(self.clients.keys()):
self._remove_client(client_id)
# 关闭服务器套接字
if self.socket:
try:
self.socket.close()
except:
pass
self.log('INFO', "服务器已停止", 'SERVER')
# 使用示例
if __name__ == "__main__":
try:
# 创建并启动服务器
server = TCPServer()
# 以阻塞方式启动服务器
server_thread = threading.Thread(target=server.start)
server_thread.daemon = True
server_thread.start()
# 运行直到按Ctrl+C
print("服务器运行中按Ctrl+C停止...")
while True:
time.sleep(1)
except KeyboardInterrupt:
print("\n程序被用户中断")
if 'server' in locals():
server.stop()
sys.exit(0)

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -0,0 +1,28 @@
{
"测试作物": {"花费": 1, "生长时间": 3, "收益": 9999, "品质": "普通", "描述": "测试作物", "耐候性": 10, "等级": 1, "经验": 999},
"小麦": {"花费": 120, "生长时间": 120, "收益": 100, "品质": "普通", "描述": "基础作物,品质较低,适合新手种植", "耐候性": 10, "等级": 1, "经验": 10},
"稻谷": {"花费": 100, "生长时间": 240, "收益": 120, "品质": "普通", "描述": "适合大规模种植的基础作物", "耐候性": 10, "等级": 1, "经验": 10},
"玉米": {"花费": 70, "生长时间": 600, "收益": 90, "品质": "普通", "描述": "营养丰富的优良作物,适合稍有经验的玩家", "耐候性": 15, "等级": 2, "经验": 15},
"土豆": {"花费": 75, "生长时间": 360, "收益": 90, "品质": "普通", "描述": "容易种植的耐寒作物", "耐候性": 12, "等级": 1, "经验": 10},
"胡萝卜": {"花费": 60, "生长时间": 480, "收益": 80, "品质": "普通", "描述": "适合新手的健康作物", "耐候性": 12, "等级": 1, "经验": 10},
"草莓": {"花费": 120, "生长时间": 960, "收益": 150, "品质": "优良", "描述": "营养丰富的果实,收益不错", "耐候性": 14, "等级": 2, "经验": 20},
"番茄": {"花费": 100, "生长时间": 720, "收益": 130, "品质": "优良", "描述": "常见作物,适合小规模种植", "耐候性": 12, "等级": 2, "经验": 15},
"大豆": {"花费": 90, "生长时间": 840, "收益": 110, "品质": "优良", "描述": "富含蛋白质的基础作物", "耐候性": 11, "等级": 2, "经验": 12},
"蓝莓": {"花费": 150, "生长时间": 1200, "收益": 200, "品质": "稀有", "描述": "较为稀有的作物,市场价值较高", "耐候性": 18, "等级": 3, "经验": 25},
"洋葱": {"花费": 85, "生长时间": 600, "收益": 105, "品质": "稀有", "描述": "烹饪常用的作物,适合中级种植", "耐候性": 10, "等级": 2, "经验": 10},
"南瓜": {"花费": 180, "生长时间": 1440, "收益": 250, "品质": "稀有", "描述": "秋季收获的高收益作物", "耐候性": 20, "等级": 4, "经验": 30},
"葡萄": {"花费": 200, "生长时间": 1200, "收益": 300, "品质": "稀有", "描述": "需要特殊管理的高收益作物", "耐候性": 15, "等级": 4, "经验": 35},
"柿子": {"花费": 160, "生长时间": 1080, "收益": 240, "品质": "稀有", "描述": "富含营养的秋季作物", "耐候性": 18, "等级": 3, "经验": 28},
"花椰菜": {"花费": 130, "生长时间": 960, "收益": 170, "品质": "稀有", "描述": "耐寒的高品质作物,适合经验丰富的玩家", "耐候性": 17, "等级": 3, "经验": 22},
"芦笋": {"花费": 200, "生长时间": 1560, "收益": 280, "品质": "稀有", "描述": "市场需求量高的稀有作物", "耐候性": 15, "等级": 4, "经验": 30},
"香草": {"花费": 250, "生长时间": 1800, "收益": 400, "品质": "史诗", "描述": "非常稀有且收益极高的作物", "耐候性": 22, "等级": 5, "经验": 40},
"西瓜": {"花费": 240, "生长时间": 2400, "收益": 420, "品质": "史诗", "描述": "夏季丰产的高价值作物", "耐候性": 21, "等级": 5, "经验": 45},
"甜菜": {"花费": 220, "生长时间": 2160, "收益": 350, "品质": "史诗", "描述": "营养丰富的根茎作物,收益较高", "耐候性": 20, "等级": 5, "经验": 38},
"甘蔗": {"花费": 260, "生长时间": 3000, "收益": 450, "品质": "史诗", "描述": "需要充足水源的高价值作物", "耐候性": 18, "等级": 5, "经验": 50},
"龙果": {"花费": 400, "生长时间": 4800, "收益": 600, "品质": "传奇", "描述": "极为稀有的热带作物,产量和价值都极高", "耐候性": 25, "等级": 6, "经验": 60},
"松露": {"花费": 500, "生长时间": 7200, "收益": 700, "品质": "传奇", "描述": "极其珍贵的地下作物,市场价格极高", "耐候性": 23, "等级": 7, "经验": 80},
"人参": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
"富贵竹": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
"芦荟": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
"金橘": {"花费": 420, "生长时间": 4800, "收益": 620, "品质": "传奇", "描述": "少见的耐寒果树,市场需求量极大", "耐候性": 26, "等级": 7, "经验": 70}
}

View File

@@ -0,0 +1,32 @@
{
"测试作物": {"花费": 1, "生长时间": 3, "收益": 9999, "品质": "普通", "描述": "测试作物", "耐候性": 10, "等级": 1, "经验": 999},
"小麦": {"花费": 120, "生长时间": 120, "收益": 100, "品质": "普通", "描述": "基础作物,品质较低,适合新手种植", "耐候性": 10, "等级": 1, "经验": 10},
"稻谷": {"花费": 100, "生长时间": 240, "收益": 120, "品质": "普通", "描述": "适合大规模种植的基础作物", "耐候性": 10, "等级": 1, "经验": 10},
"玉米": {"花费": 70, "生长时间": 600, "收益": 90, "品质": "普通", "描述": "营养丰富的优良作物,适合稍有经验的玩家", "耐候性": 15, "等级": 2, "经验": 15},
"土豆": {"花费": 75, "生长时间": 360, "收益": 90, "品质": "普通", "描述": "容易种植的耐寒作物", "耐候性": 12, "等级": 1, "经验": 10},
"胡萝卜": {"花费": 60, "生长时间": 480, "收益": 80, "品质": "普通", "描述": "适合新手的健康作物", "耐候性": 12, "等级": 1, "经验": 10},
"草莓": {"花费": 120, "生长时间": 960, "收益": 150, "品质": "优良", "描述": "营养丰富的果实,收益不错", "耐候性": 14, "等级": 2, "经验": 20},
"番茄": {"花费": 100, "生长时间": 720, "收益": 130, "品质": "优良", "描述": "常见作物,适合小规模种植", "耐候性": 12, "等级": 2, "经验": 15},
"大豆": {"花费": 90, "生长时间": 840, "收益": 110, "品质": "优良", "描述": "富含蛋白质的基础作物", "耐候性": 11, "等级": 2, "经验": 12},
"蓝莓": {"花费": 150, "生长时间": 1200, "收益": 200, "品质": "稀有", "描述": "较为稀有的作物,市场价值较高", "耐候性": 18, "等级": 3, "经验": 25},
"洋葱": {"花费": 85, "生长时间": 600, "收益": 105, "品质": "稀有", "描述": "烹饪常用的作物,适合中级种植", "耐候性": 10, "等级": 2, "经验": 10},
"南瓜": {"花费": 180, "生长时间": 1440, "收益": 250, "品质": "稀有", "描述": "秋季收获的高收益作物", "耐候性": 20, "等级": 4, "经验": 30},
"葡萄": {"花费": 200, "生长时间": 1200, "收益": 300, "品质": "稀有", "描述": "需要特殊管理的高收益作物", "耐候性": 15, "等级": 4, "经验": 35},
"柿子": {"花费": 160, "生长时间": 1080, "收益": 240, "品质": "稀有", "描述": "富含营养的秋季作物", "耐候性": 18, "等级": 3, "经验": 28},
"花椰菜": {"花费": 130, "生长时间": 960, "收益": 170, "品质": "稀有", "描述": "耐寒的高品质作物,适合经验丰富的玩家", "耐候性": 17, "等级": 3, "经验": 22},
"芦笋": {"花费": 200, "生长时间": 1560, "收益": 280, "品质": "稀有", "描述": "市场需求量高的稀有作物", "耐候性": 15, "等级": 4, "经验": 30},
"香草": {"花费": 250, "生长时间": 1800, "收益": 400, "品质": "史诗", "描述": "非常稀有且收益极高的作物", "耐候性": 22, "等级": 5, "经验": 40},
"西瓜": {"花费": 240, "生长时间": 2400, "收益": 420, "品质": "史诗", "描述": "夏季丰产的高价值作物", "耐候性": 21, "等级": 5, "经验": 45},
"甜菜": {"花费": 220, "生长时间": 2160, "收益": 350, "品质": "史诗", "描述": "营养丰富的根茎作物,收益较高", "耐候性": 20, "等级": 5, "经验": 38},
"甘蔗": {"花费": 260, "生长时间": 3000, "收益": 450, "品质": "史诗", "描述": "需要充足水源的高价值作物", "耐候性": 18, "等级": 5, "经验": 50},
"龙果": {"花费": 400, "生长时间": 4800, "收益": 600, "品质": "传奇", "描述": "极为稀有的热带作物,产量和价值都极高", "耐候性": 25, "等级": 6, "经验": 60},
"松露": {"花费": 500, "生长时间": 7200, "收益": 700, "品质": "传奇", "描述": "极其珍贵的地下作物,市场价格极高", "耐候性": 23, "等级": 7, "经验": 80},
"人参": {"花费": 450, "生长时间": 6600, "收益": 650, "品质": "传奇", "描述": "需要耐心等待的珍贵药材", "耐候性": 22, "等级": 6, "经验": 75},
"金橘": {"花费": 420, "生长时间": 4800, "收益": 620, "品质": "传奇", "描述": "少见的耐寒果树,市场需求量极大", "耐候性": 26, "等级": 7, "经验": 70}
}

View File

@@ -0,0 +1,64 @@
{
"experience": 0,
"level": 1,
"money": 1000,
"farm_name": "农场",
"user_name": "shumengya",
"player_name": "玩家昵称",
"user_password": "0123456789",
"last_login_time": "2025年12时09分35秒",
"total_login_time": "0时0分0秒",
"farm_lots": [
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": true, "is_planted": false, "max_grow_time": 3},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5},
{"crop_type": "", "grow_time": 0, "is_dead": false, "is_diged": false, "is_planted": false, "max_grow_time": 5}
],
"player_bag": []
}

View File

@@ -0,0 +1,6 @@
{
"1232132": {
"code": "5UFH2Z",
"expire_at": 1748005553.6155243
}
}

124
Server/deployment_guide.md Normal file
View File

@@ -0,0 +1,124 @@
# 萌芽农场游戏服务器部署指南
## 系统要求
- Python 3.7 或更高版本
- 稳定的互联网连接
- 建议2GB+ 内存,足够的磁盘空间存储玩家数据
## 安装步骤
### 1. 准备环境
```bash
# 在服务器上创建项目文件夹
mkdir MengYaFarm
cd MengYaFarm
# 克隆或上传服务器代码到此文件夹
# (手动上传文件或使用Git)
```
### 2. 安装依赖
```bash
# 创建虚拟环境(推荐)
python -m venv venv
# Linux/Mac激活虚拟环境
source venv/bin/activate
# Windows激活虚拟环境
# venv\Scripts\activate
# 安装依赖
pip install -r requirements.txt
```
### 3. 配置服务器
1. 确保已创建所需文件夹:
```bash
mkdir -p game_saves config
```
2. 创建初始玩家数据模板 (如果尚未存在):
```bash
# 在config目录中创建initial_player_data_template.json
```
3. 检查 TCPGameServer.py 中的服务器地址和端口配置:
```python
server_host: str = "0.0.0.0" # 使用0.0.0.0允许所有网络接口访问
server_port: int = 9000 # 确保此端口在防火墙中开放
```
4. 如需使用QQ邮箱验证功能请在QQEmailSend.py中更新发件邮箱配置:
```python
SENDER_EMAIL = 'your_qq_number@qq.com' # 发件人邮箱
SENDER_AUTH_CODE = 'your_auth_code' # 授权码
```
### 4. 启动服务器
```bash
# 直接启动
python Server/TCPGameServer.py
# 或使用nohup在后台运行
nohup python Server/TCPGameServer.py > server.log 2>&1 &
```
### 5. 监控与维护
- 服务器日志会输出到控制台或server.log
- 玩家数据存储在game_saves文件夹中
- 定期备份game_saves文件夹以防数据丢失
### 6. 防火墙配置
确保服务器防火墙允许TCP 9000端口的入站连接:
```bash
# Ubuntu/Debian
sudo ufw allow 9000/tcp
# CentOS/RHEL
sudo firewall-cmd --permanent --add-port=9000/tcp
sudo firewall-cmd --reload
```
### 7. 系统服务配置 (可选)
可以创建systemd服务使服务器自动启动:
```bash
# 创建服务文件
sudo nano /etc/systemd/system/mengyafarm.service
# 添加以下内容
[Unit]
Description=MengYa Farm Game Server
After=network.target
[Service]
Type=simple
User=your_username
WorkingDirectory=/path/to/MengYaFarm
ExecStart=/path/to/MengYaFarm/venv/bin/python /path/to/MengYaFarm/Server/TCPGameServer.py
Restart=on-failure
RestartSec=5
[Install]
WantedBy=multi-user.target
# 启用并启动服务
sudo systemctl enable mengyafarm.service
sudo systemctl start mengyafarm.service
```
## 常见问题
### 服务器无法启动
- 检查Python版本
- 确认所有依赖已正确安装
- 检查端口是否被占用
### 客户端无法连接
- 确认服务器IP和端口配置正确
- 检查防火墙设置
- 验证网络连接
### 发送验证码失败
- 检查QQ邮箱和授权码设置
- 确认SMTP服务器可访问

View File

@@ -1,5 +1,13 @@
{
"experience": 0,
"level": 1,
"money": 1000,
"farm_name": "柚大青の小农场",
"player_name": "柚大青",
"user_name": "2143323382",
"user_password": "tyh@19900420",
"last_login_time": "2025年05月25日16时43分38秒",
"total_login_time": "0时0分29秒",
"farm_lots": [
{
"crop_type": "",
@@ -285,7 +293,7 @@
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
@@ -293,7 +301,7 @@
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
@@ -301,7 +309,7 @@
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
@@ -309,7 +317,7 @@
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
@@ -317,14 +325,90 @@
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
}
],
"farm_name": "1234",
"level": 0,
"money": 1000,
"user_name": "1242423",
"user_password": "1234"
"player_bag": []
}

View File

@@ -0,0 +1,370 @@
{
"experience": 508,
"farm_lots": [
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "花椰菜",
"grow_time": 960,
"is_dead": false,
"is_diged": true,
"is_planted": true,
"max_grow_time": 960
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 4800
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 1200
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 4800
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 2400
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 120
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 720
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 2400
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 1200
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 1800
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 600
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 480
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 960
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 960
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": false,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 5
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 2400
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 3
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 960
},
{
"crop_type": "",
"grow_time": 0,
"is_dead": false,
"is_diged": true,
"is_planted": false,
"max_grow_time": 960
}
],
"player_bag": [
{
"count": 18,
"name": "测试作物",
"quality": "普通"
},
{
"count": 253,
"name": "金橘",
"quality": "传奇"
},
{
"name": "胡萝卜",
"quality": "普通",
"count": 17
},
{
"name": "花椰菜",
"quality": "稀有",
"count": 11
},
{
"name": "芦笋",
"quality": "稀有",
"count": 8
},
{
"name": "草莓",
"quality": "优良",
"count": 4
},
{
"name": "香草",
"quality": "史诗",
"count": 3
}
],
"farm_name": "树萌芽の大农场",
"player_name": "树萌芽",
"level": 36,
"money": 40013,
"last_login_time": "2025年05月24日11时51分35秒",
"total_login_time": "100时33分38秒",
"user_name": "3205788256",
"user_password": "tyh@19900420"
}

4
Server/requirements.txt Normal file
View File

@@ -0,0 +1,4 @@
# Game Server Dependencies
colorama>=0.4.6 # For colored terminal output
# Email Requirements
# Standard library dependencies are not listed (socket, threading, json, etc.)

View File

@@ -0,0 +1 @@
uid://cmrhmjs8fohm

4
Temp/login.json Normal file
View File

@@ -0,0 +1,4 @@
{
"user_name":"",
"password":""
}

View File

@@ -0,0 +1,46 @@
extends Control
@onready var http_texture_rect = $VBoxContainer/ImageContainer/HTTPTextureRect
@onready var url_input = $VBoxContainer/HBoxContainer/URLInput
@onready var load_url_button = $VBoxContainer/HBoxContainer/LoadURLButton
@onready var qq_input = $VBoxContainer/HBoxContainer2/QQInput
@onready var load_qq_button = $VBoxContainer/HBoxContainer2/LoadQQButton
@onready var status_label = $VBoxContainer/StatusLabel
func _ready():
# 设置默认URL和QQ号
url_input.text = "https://picsum.photos/200"
qq_input.text = "3205788256"
# 连接按钮信号
load_url_button.pressed.connect(_on_load_url_button_pressed)
load_qq_button.pressed.connect(_on_load_qq_button_pressed)
# 连接HTTP纹理矩形的信号
http_texture_rect.loading_started.connect(_on_loading_started)
http_texture_rect.loading_finished.connect(_on_loading_finished)
func _on_load_url_button_pressed():
var url = url_input.text.strip_edges()
if url.is_empty():
status_label.text = "状态: URL不能为空"
return
http_texture_rect.load_from_url(url)
func _on_load_qq_button_pressed():
var qq_number = qq_input.text.strip_edges()
if qq_number.is_empty() or not qq_number.is_valid_int():
status_label.text = "状态: 无效的QQ号"
return
http_texture_rect.load_qq_avatar(qq_number)
func _on_loading_started():
status_label.text = "状态: 正在加载..."
func _on_loading_finished(success: bool):
if success:
status_label.text = "状态: 加载成功"
else:
status_label.text = "状态: 加载失败"

View File

@@ -0,0 +1 @@
uid://cgylg6qxwg1f0

View File

@@ -0,0 +1,76 @@
[gd_scene load_steps=3 format=3 uid="uid://dyh0q82ytbk3v"]
[ext_resource type="Script" uid="uid://cgylg6qxwg1f0" path="res://Test/HTTPTextureRectDemo.gd" id="1_vgcbi"]
[ext_resource type="Script" uid="uid://0d2j5m6j2ema" path="res://Components/HTTPTextureRect.gd" id="2_pujh8"]
[node name="HTTPTextureRectDemo" type="Control"]
layout_mode = 3
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
script = ExtResource("1_vgcbi")
[node name="VBoxContainer" type="VBoxContainer" parent="."]
layout_mode = 1
anchors_preset = 15
anchor_right = 1.0
anchor_bottom = 1.0
grow_horizontal = 2
grow_vertical = 2
alignment = 1
[node name="Label" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "HTTP图像加载演示"
horizontal_alignment = 1
[node name="ImageContainer" type="CenterContainer" parent="VBoxContainer"]
layout_mode = 2
size_flags_vertical = 3
[node name="HTTPTextureRect" type="TextureRect" parent="VBoxContainer/ImageContainer"]
custom_minimum_size = Vector2(200, 200)
layout_mode = 2
stretch_mode = 5
script = ExtResource("2_pujh8")
[node name="HBoxContainer" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="URLLabel" type="Label" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "URL:"
[node name="URLInput" type="LineEdit" parent="VBoxContainer/HBoxContainer"]
custom_minimum_size = Vector2(300, 0)
layout_mode = 2
placeholder_text = "输入HTTP图像URL"
[node name="LoadURLButton" type="Button" parent="VBoxContainer/HBoxContainer"]
layout_mode = 2
text = "加载图像"
[node name="HBoxContainer2" type="HBoxContainer" parent="VBoxContainer"]
layout_mode = 2
alignment = 1
[node name="QQLabel" type="Label" parent="VBoxContainer/HBoxContainer2"]
layout_mode = 2
text = "QQ号:"
[node name="QQInput" type="LineEdit" parent="VBoxContainer/HBoxContainer2"]
custom_minimum_size = Vector2(200, 0)
layout_mode = 2
placeholder_text = "输入QQ号"
[node name="LoadQQButton" type="Button" parent="VBoxContainer/HBoxContainer2"]
layout_mode = 2
text = "加载QQ头像"
[node name="StatusLabel" type="Label" parent="VBoxContainer"]
layout_mode = 2
text = "状态: 等待加载"
horizontal_alignment = 1

View File

@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="mengyafarm.smy"
android:versionCode="1"
android:versionName="1.0">
<!-- 网络权限 -->
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<application
android:label="萌芽农场"
android:icon="@mipmap/icon"
android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="true">
<activity
android:name="org.godotengine.godot.GodotApp"
android:theme="@style/GodotAppMainTheme"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<domain-config cleartextTrafficPermitted="true">
<domain includeSubdomains="true">192.168.1.110</domain>
<domain includeSubdomains="true">127.0.0.1</domain>
<domain includeSubdomains="true">localhost</domain>
<!-- 添加你的服务器域名 -->
</domain-config>
<!-- 允许所有明文流量(测试用,生产环境不推荐) -->
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="system"/>
</trust-anchors>
</base-config>
</network-security-config>

BIN
assets/GUI/农场名称.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 915 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://clvhlo0mc3e7v"
path="res://.godot/imported/农场名称.png-1101845a74d5743a7bffe958ff6ddc5d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/农场名称.png"
dest_files=["res://.godot/imported/农场名称.png-1101845a74d5743a7bffe958ff6ddc5d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/GUI/小提示.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://ckqy5yq2ltax6"
path="res://.godot/imported/小提示.png-af968d997d0bd20225a871f0c885a204.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/小提示.png"
dest_files=["res://.godot/imported/小提示.png-af968d997d0bd20225a871f0c885a204.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

Binary file not shown.

After

Width:  |  Height:  |  Size: 943 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://by5qcip8tel1f"
path="res://.godot/imported/服务器连接状态.png-a55f4e8174e2bbf848a6ffd7e554646c.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/服务器连接状态.png"
dest_files=["res://.godot/imported/服务器连接状态.png-a55f4e8174e2bbf848a6ffd7e554646c.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/GUI/玩家昵称.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 881 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://b4wi8yusmbbu8"
path="res://.godot/imported/玩家昵称.png-1608f19521048d10cd2802563f027b42.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/玩家昵称.png"
dest_files=["res://.godot/imported/玩家昵称.png-1608f19521048d10cd2802563f027b42.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/GUI/等级.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c87kujyuxnx2s"
path="res://.godot/imported/等级.png-571abcf9eb5b920577c180e41ac1eb10.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/等级.png"
dest_files=["res://.godot/imported/等级.png-571abcf9eb5b920577c180e41ac1eb10.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/GUI/经验球.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 784 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://cwloibftcmp76"
path="res://.godot/imported/经验球.png-e7ec652ceb5e77fccd6dbb0e0fbd7dcd.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/经验球.png"
dest_files=["res://.godot/imported/经验球.png-e7ec652ceb5e77fccd6dbb0e0fbd7dcd.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/GUI/钱币.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 786 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c6i00d35fnl12"
path="res://.godot/imported/钱币.png-f6b277b749721715d601deace45b6e54.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/GUI/钱币.png"
dest_files=["res://.godot/imported/钱币.png-f6b277b749721715d601deace45b6e54.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/Test/g.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

34
assets/Test/g.jpg.import Normal file
View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://y4gfkorl1g4f"
path="res://.godot/imported/g.jpg-9129d5433d811daa5a9462e7b8751277.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Test/g.jpg"
dest_files=["res://.godot/imported/g.jpg-9129d5433d811daa5a9462e7b8751277.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/Test/g.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

34
assets/Test/g.png.import Normal file
View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c3vng0nal1wr8"
path="res://.godot/imported/g.png-3604f64f9277adc00d21f2056f418ce3.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/Test/g.png"
dest_files=["res://.godot/imported/g.png-3604f64f9277adc00d21f2056f418ce3.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

View File

Before

Width:  |  Height:  |  Size: 138 KiB

After

Width:  |  Height:  |  Size: 138 KiB

34
assets/tu1.jpg.import Normal file
View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://duoqp854aoeeg"
path="res://.godot/imported/tu1.jpg-c72e43aa0cb817520503d62990aab61d.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/tu1.jpg"
dest_files=["res://.godot/imported/tu1.jpg-c72e43aa0cb817520503d62990aab61d.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/tu2.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

34
assets/tu2.png.import Normal file
View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://d046caoogul6g"
path="res://.godot/imported/tu2.png-5c1f2dd0cefd9388c2780247e294469e.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/tu2.png"
dest_files=["res://.godot/imported/tu2.png-5c1f2dd0cefd9388c2780247e294469e.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/tu3.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 193 KiB

34
assets/tu3.png.import Normal file
View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c4l0qn0p4yav8"
path="res://.godot/imported/tu3.png-53f50a01a12d698dc896d7552428cb9a.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/tu3.png"
dest_files=["res://.godot/imported/tu3.png-53f50a01a12d698dc896d7552428cb9a.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/作物/人参/0.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 794 KiB

View File

@@ -0,0 +1,34 @@
[remap]
importer="texture"
type="CompressedTexture2D"
uid="uid://c461olyam2wi"
path="res://.godot/imported/0.png-f9b471f54a505f90386a524550e73d6f.ctex"
metadata={
"vram_texture": false
}
[deps]
source_file="res://assets/作物/人参/0.png"
dest_files=["res://.godot/imported/0.png-f9b471f54a505f90386a524550e73d6f.ctex"]
[params]
compress/mode=0
compress/high_quality=false
compress/lossy_quality=0.7
compress/hdr_compression=1
compress/normal_map=0
compress/channel_pack=0
mipmaps/generate=false
mipmaps/limit=-1
roughness/mode=0
roughness/src_normal=""
process/fix_alpha_border=true
process/premult_alpha=false
process/normal_map_invert_y=false
process/hdr_as_srgb=false
process/hdr_clamp_exposure=false
process/size_limit=0
detect_3d/compress_to=1

BIN
assets/作物/人参/1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 MiB

Some files were not shown because too many files have changed in this diff Show More