diff --git a/README.md b/README.md index d7e449a..86333e6 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ ## 如何安装 -方法一(推荐):在小真寻后台的插件商店下载即可。 +方法一(推荐):在小真寻后台的插件商店下载即可
方法二:下载源码放在小真寻`plugin`目录下 ## 使用指令 @@ -22,22 +22,32 @@ | 收获 | 收获成熟作物 | | | 铲除 | 铲除荒废作物 | | | 我的作物 | | | -| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 作物不填默认清空仓库 数量不填默认全部 | +| 出售作物 [作物名称] [数量] | 从仓库里向系统售卖作物 | 不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 | | @美波理 偷菜 | 偷别人的菜 | 每人每天只能偷5次 | | 购买农场币 | 将真寻金币兑换成农场币 | 兑换比例默认为1:2 手续费默认20% | --- +## 更新日志[详细](./log/log.md): +- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。 +- 更正数据库写法,但是会导致V1.0用户的作物和种子丢失 +- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了 +- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物 +- 完善播种逻辑,现在可以空置数量来一键播种指定作物了 +- 新增更改农场名指令 +- 改进对土地开垦条件判断 +--- + ## 待办事宜 `Todo` 列表 - [x] 完善我的农场图片,例如左上角显示用户数据 -- [x] 完善升级数据、作物数据、作物图片 -- [x] 签到功能 -- [x] 添加渔场功能 -- [x] 增加活动、交易行功能 -- [x] 增加交易行总行功能 -- [x] 添加其他游戏种子素材 -- [x] 想不到了,想到再说 +- [ ] 完善升级数据、作物数据、作物图片 +- [ ] 签到功能 +- [ ] 添加渔场功能 +- [ ] 增加活动、交易行功能 +- [ ] 增加交易行总行功能 +- [ ] 添加其他游戏种子素材 +- [ ] 想不到了,想到再说 --- diff --git a/__init__.py b/__init__.py index ca568a8..ba53d5c 100644 --- a/__init__.py +++ b/__init__.py @@ -24,18 +24,19 @@ __plugin_meta__ = PluginMetadata( 种子商店 [页数] 购买种子 [作物/种子名称] [数量] 我的种子 - 播种 [作物/种子名称] [数量] + 播种 [作物/种子名称] [数量] (数量不填默认将最大可能播种 收获 铲除 我的作物 - 出售作物 [作物/种子名称] [数量] (不填写作物 - 偷菜 at + 出售作物 [作物/种子名称] [数量] (不填写作物名将售卖仓库种全部作物 填作物名不填数量将指定作物全部出售 + 偷菜 at (每人每天只能偷5次 开垦 购买农场币 [数量] 数量为消耗金币的数量 + 更改农场名 [新农场名] """.strip(), extra=PluginExtraData( author="Art_Sakura", - version="1.0", + version="1.1", commands=[Command(command="我的农场")], menu_type="群内小游戏", configs=[ diff --git a/command.py b/command.py index cee7761..1d4ae4a 100644 --- a/command.py +++ b/command.py @@ -1,9 +1,9 @@ from nonebot.adapters import Event, MessageTemplate from nonebot.rule import to_me from nonebot.typing import T_State -from nonebot_plugin_alconna import (Alconna, AlconnaQuery, Args, Arparma, At, - Match, MultiVar, Option, Query, Subcommand, - on_alconna, store_true) +from nonebot_plugin_alconna import (Alconna, AlconnaMatch, AlconnaQuery, Args, + Arparma, At, Match, MultiVar, Option, + Query, Subcommand, on_alconna, store_true) from nonebot_plugin_uninfo import Uninfo from nonebot_plugin_waiter import waiter @@ -46,18 +46,18 @@ async def handle_register(session: Uninfo): # 获取原始用户名并安全处理 raw_name = str(session.user.name) safe_name = sanitize_username(raw_name) - + # 初始化用户信息 success = await g_pSqlManager.initUserInfoByUid( uid=uid, name=safe_name, exp=0, - point=100 + point=500 ) msg = ( - "✅ 农场开通成功!\n💼 初始资金:100农场币" - if success + "✅ 农场开通成功!\n💼 初始资金:500农场币" + if success else "⚠️ 开通失败,请稍后再试" ) logger.info(f"用户注册 {'成功' if success else '失败'}:{uid}") @@ -65,9 +65,9 @@ async def handle_register(session: Uninfo): except Exception as e: msg = "⚠️ 系统繁忙,请稍后再试" logger.error(f"注册异常 | UID:{uid} | 错误:{str(e)}") - + await MessageUtils.build_message(msg).send(reply_to=True) - + def sanitize_username(username: str, max_length: int = 15) -> str: """ 安全处理用户名 @@ -81,14 +81,14 @@ def sanitize_username(username: str, max_length: int = 15) -> str: # 处理空值 if not username: return "神秘农夫" - + # 基础清洗 cleaned = username.strip() - + # 允许的字符白名单(可自定义扩展) safe_chars = { '_', '-', '!', '@', '#', '$', '%', '^', '&', '*', '(', ')', - '+', '=', '.', ',', '~', '·', ' ', + '+', '=', '.', ',', '~', '·', ' ', 'a','b','c','d','e','f','g','h','i','j','k','l','m', 'n','o','p','q','r','s','t','u','v','w','x','y','z', 'A','B','C','D','E','F','G','H','I','J','K','L','M', @@ -97,24 +97,24 @@ def sanitize_username(username: str, max_length: int = 15) -> str: } # 添加常用中文字符(Unicode范围) safe_chars.update(chr(c) for c in range(0x4E00, 0x9FFF+1)) - + # 过滤危险字符 filtered = [ - c if c in safe_chars or 0x4E00 <= ord(c) <= 0x9FFF - else '' + c if c in safe_chars or 0x4E00 <= ord(c) <= 0x9FFF + else '' for c in cleaned ] - + # 合并处理结果 safe_str = ''.join(filtered) - + # 转义单引号(双重保障) escaped = safe_str.replace("'", "''") - + # 处理空结果 if not escaped: return "神秘农夫" - + # 长度限制 return escaped[:max_length] @@ -130,24 +130,24 @@ diuse_farm = on_alconna( Subcommand("harvest", help_text="收获"), Subcommand("eradicate", help_text="铲除"), Subcommand("my-plant", help_text="我的作物"), - # Subcommand("reclamation", Args["isBool?", str], help_text="开垦"), Subcommand("sell-plant", Args["name?", str]["num?", int], help_text="出售作物"), Subcommand("stealing", Args["target?", At], help_text="偷菜"), Subcommand("buy-point", Args["num?", int], help_text="购买农场币"), #Subcommand("sell-point", Args["num?", int], help_text="转换金币") + Subcommand("change-name", Args["name?", str], help_text="更改农场名") ), priority=5, block=True, ) @diuse_farm.assign("$main") -async def _(session: Uninfo, nickname: str = UserName()): +async def _(session: Uninfo): uid = str(session.user.id) if await isRegisteredByUid(uid) == False: return - image = await g_pFarmManager.drawFarmByUid(uid, nickname) + image = await g_pFarmManager.drawFarmByUid(uid) await MessageUtils.build_message(image).send(reply_to=True) diuse_farm.shortcut( @@ -339,12 +339,7 @@ diuse_farm.shortcut( ) @diuse_farm.assign("sell-plant") -async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", 1),): - if not name.available: - await MessageUtils.build_message( - "请在指令后跟需要出售的作物名称" - ).finish(reply_to=True) - +async def _(session: Uninfo, name: Match[str], num: Query[int] = AlconnaQuery("num", -1),): uid = str(session.user.id) if await isRegisteredByUid(uid) == False: @@ -401,3 +396,32 @@ async def _(session: Uninfo, num: Query[int] = AlconnaQuery("num", 0)): result = await g_pFarmManager.buyPointByUid(uid, num.result) await MessageUtils.build_message(result).send(reply_to=True) + + +diuse_farm.shortcut( + "更改农场名(?P)", + command="我的农场", + arguments=["change-name", "{name}"], + prefix=True, +) + +@diuse_farm.assign("change-name") +async def _(session: Uninfo, name: Match[str]): + if not name.available: + await MessageUtils.build_message( + "请在指令后跟需要更改的用户名" + ).finish(reply_to=True) + + uid = str(session.user.id) + + if await isRegisteredByUid(uid) == False: + return + + safeName = sanitize_username(name.result) + + result = await g_pSqlManager.updateUserNameByUid(uid, safeName) + + if result == True: + await MessageUtils.build_message("更新用户名成功!").send(reply_to=True) + else: + await MessageUtils.build_message("更新用户名失败!").send(reply_to=True) diff --git a/config/plant.json b/config/plant.json index 6f4fb86..5c5e523 100644 --- a/config/plant.json +++ b/config/plant.json @@ -213,6 +213,21 @@ "sell": false }, "番茄": + { + "level": 6, + "buy": 251, + "limit": 0, + "experience": 22, + "harvest": 21, + "price": 26, + "time": 17, + "crop": 1, + "again": 0, + "phase": 6, + "general": true, + "sell": false + }, + "ATTomato": { "level": 6, "buy": 99999, diff --git a/config/sign_in.json b/config/sign_in.json new file mode 100644 index 0000000..4b4e999 --- /dev/null +++ b/config/sign_in.json @@ -0,0 +1,4 @@ +{ + "date": "202505", + "" +} diff --git a/database.py b/database.py index a056dad..ba45c4c 100644 --- a/database.py +++ b/database.py @@ -143,12 +143,20 @@ class CSqlManager: "count": "INTEGER NOT NULL DEFAULT 0", "PRIMARY KEY": "(uid, seed)" } + # 5. 用户道具明细表 + userItem = { + "uid": "INTEGER NOT NULL", + "item": "TEXT NOT NULL", + "count": "INTEGER NOT NULL DEFAULT 0", + "PRIMARY KEY": "(uid, item)" + } #建表(或增列) await cls.ensureTableSchema("user", userInfo) await cls.ensureTableSchema("soil", userSoilInfo) await cls.ensureTableSchema("userPlant", userPlant) await cls.ensureTableSchema("userSeed", userSeed) + await cls.ensureTableSchema("userItem", userItem) return True @@ -175,7 +183,7 @@ class CSqlManager: return False @classmethod - async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 100): + async def initUserInfoByUid(cls, uid: str, name: str = "", exp: int = 0, point: int = 500): """初始化用户信息 Args: @@ -190,11 +198,6 @@ class CSqlManager: INSERT INTO user (uid, name, exp, point, soil, stealing) VALUES ({uid}, '{name}', {exp}, {point}, 3, '{date.today()}|5') """ - #用户仓库 - userStorehouse = f""" - INSERT INTO storehouse (uid) VALUES ({uid}); - """ - #用户土地 userSoilInfo = f""" INSERT INTO soil (uid) VALUES ({uid}); @@ -203,9 +206,6 @@ class CSqlManager: if not await cls.executeDB(userInfo): return False - if not await cls.executeDB(userStorehouse): - return False - if not await cls.executeDB(userSoilInfo): return False @@ -244,6 +244,55 @@ class CSqlManager: logger.warning(f"getUserInfoByUid查询失败: {e}") return {} + @classmethod + async def getUserNameByUid(cls, uid: str) -> str: + """根据用户uid查询用户名 + + Args: + uid (str): 用户uid + + Returns: + str: 用户名,如果失败返回空字符串 + """ + if not uid: + return "" + + try: + async with cls.m_pDB.execute( + "SELECT name FROM user WHERE uid = ?", + (uid,) + ) as cursor: + row = await cursor.fetchone() + return row["name"] if row else "" + except Exception as e: + logger.warning(f"真寻农场getUserNameByUid查询失败: {e}") + return "" + + @classmethod + async def updateUserNameByUid(cls, uid: str, name: str) -> bool: + """根据用户uid修改用户名 + + Args: + uid (str): 用户uid + name (str): 新用户名 + + Returns: + bool: 是否更新成功 + """ + if not uid or not name: + return False + + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "UPDATE user SET name = ? WHERE uid = ?", + (name, uid) + ) + return True + except Exception as e: + logger.warning(f"真寻农场updateUserNameByUid失败: {e}") + return False + @classmethod async def getUserPointByUid(cls, uid: str) -> int: """根据用户Uid获取用户农场币 @@ -709,4 +758,152 @@ class CSqlManager: logger.warning(f"真寻农场deleteUserPlantByName 失败: {e}") return False + + @classmethod + async def getUserItemByName(cls, uid: str, item: str) -> Optional[int]: + """根据道具名称查询某一项数量 + + Args: + uid (str): 用户uid + item (str): 道具名称 + + Returns: + Optional[int]: 数量(不存在返回None) + """ + if not uid or not item: + return None + try: + async with cls.m_pDB.execute( + "SELECT count FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) as cursor: + row = await cursor.fetchone() + return row[0] if row else None + except Exception as e: + logger.warning(f"真寻农场getUserItemByName查询失败: {e}") + return None + + @classmethod + async def getUserItemByUid(cls, uid: str) -> dict: + """根据用户Uid获取全部道具信息 + + Args: + uid (str): 用户uid + + Returns: + dict: {itemName: count, ...} + """ + if not uid: + return {} + try: + cursor = await cls.m_pDB.execute( + "SELECT item, count FROM userItem WHERE uid = ?", + (uid,) + ) + rows = await cursor.fetchall() + return {row["item"]: row["count"] for row in rows} + except Exception as e: + logger.warning(f"真寻农场getUserItemByUid查询失败: {e}") + return {} + + @classmethod + async def deleteUserItemByName(cls, uid: str, item: str) -> bool: + """根据道具名删除道具 + + Args: + uid (str): 用户uid + item (str): 道具名称 + + Returns: + bool: 是否删除成功 + """ + if not uid or not item: + return False + try: + async with cls._transaction(): + await cls.m_pDB.execute( + "DELETE FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) + return True + except Exception as e: + logger.warning(f"真寻农场deleteUserItemByName失败: {e}") + return False + + @classmethod + async def updateUserItemByName(cls, uid: str, item: str, count: int) -> bool: + """根据道具名直接更新道具数量 + + Args: + uid (str): 用户uid + item (str): 道具名称 + count (int): 要更新的新数量 + + Returns: + bool: 是否更新成功 + """ + if not uid or not item: + return False + try: + async with cls._transaction(): + if count <= 0: + await cls.m_pDB.execute( + "DELETE FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) + else: + await cls.m_pDB.execute( + "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", + (count, uid, item) + ) + return True + except Exception as e: + logger.warning(f"真寻农场updateUserItemByName失败: {e}") + return False + + @classmethod + async def addUserItemByUid(cls, uid: str, item: str, count: int = 1) -> bool: + """根据用户uid添加道具信息 + + Args: + uid (str): 用户uid + item (str): 道具名称 + count (int, optional): 数量.Defaults to 1. + + Returns: + bool: 是否添加成功 + """ + if not uid or not item: + return False + try: + async with cls._transaction(): + async with cls.m_pDB.execute( + "SELECT count FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) as cursor: + row = await cursor.fetchone() + + if row: + newCount = row[0] + count + if newCount <= 0: + await cls.m_pDB.execute( + "DELETE FROM userItem WHERE uid = ? AND item = ?", + (uid, item) + ) + else: + await cls.m_pDB.execute( + "UPDATE userItem SET count = ? WHERE uid = ? AND item = ?", + (newCount, uid, item) + ) + else: + if count > 0: + await cls.m_pDB.execute( + "INSERT INTO userItem (uid, item, count) VALUES (?, ?, ?)", + (uid, item, count) + ) + return True + except Exception as e: + logger.warning(f"真寻农场addUserItemByUid失败: {e}") + return False + g_pSqlManager = CSqlManager() diff --git a/farm/farm.py b/farm/farm.py index 89e458d..ff0870a 100644 --- a/farm/farm.py +++ b/farm/farm.py @@ -51,7 +51,7 @@ class CFarmManager: return f"充值{point}农场币成功,手续费{tax}金币,当前农场币:{number}" @classmethod - async def drawFarmByUid(cls, uid: str, name: str) -> bytes: + async def drawFarmByUid(cls, uid: str) -> bytes: """绘制用户农场 Args: @@ -72,7 +72,9 @@ class CFarmManager: await grass.resize(0, soilSize[0], soilSize[1]) soilPos = g_pJsonManager.m_pSoil['soil'] - soilUnlock = await g_pSqlManager.getUserSoilByUid(uid) + + userInfo = await g_pSqlManager.getUserInfoByUid(uid) + soilUnlock = int(userInfo['soil']) x = 0 y = 0 @@ -128,7 +130,7 @@ class CFarmManager: await img.paste(frame, (75, 44)) #用户名 - nameImg = await BuildImage.build_text_image(name, size = 24, font_color = (77, 35, 4)) + nameImg = await BuildImage.build_text_image(userInfo['name'], size = 24, font_color = (77, 35, 4)) await img.paste(nameImg, (300, 92)) #经验值 @@ -148,12 +150,10 @@ class CFarmManager: await img.paste(levelImg, (660, 187)) #金币 - point = await g_pSqlManager.getUserPointByUid(uid) - pointImg = await BuildImage.build_text_image(str(point), size = 24, font_color = (253, 253, 253)) + pointImg = await BuildImage.build_text_image(str(userInfo['point']), size = 24, font_color = (253, 253, 253)) await img.paste(pointImg, (330, 255)) - #点券 - bonds = await g_pSqlManager.getUserPointByUid(uid) + #点券 TODO bondsImg = await BuildImage.build_text_image("0", size = 24, font_color = (253, 253, 253)) await img.paste(bondsImg, (570, 255)) @@ -473,14 +473,14 @@ class CFarmManager: if plant is None: result = await ImageTemplate.table_page( "作物仓库", - "播种示例:@小真寻 出售作物 大白菜 [数量]", + "出售示例:@小真寻 出售作物 大白菜 [数量]", column_name, data_list, ) return result.pic2bytes() sell = "" - for name, count in plant.items(): # 使用 .items() 来遍历字典 + for name, count in plant.items(): plantInfo = g_pJsonManager.m_pPlant['plant'][name] icon = "" icon_path = g_sResourcePath / f"plant/{name}/icon.png" @@ -507,7 +507,7 @@ class CFarmManager: result = await ImageTemplate.table_page( "作物仓库", - "播种示例:@小真寻 出售作物 大白菜 [数量]", + "出售示例:@小真寻 出售作物 大白菜 [数量]", column_name, data_list, ) diff --git a/farm/shop.py b/farm/shop.py index 167c91c..c028194 100644 --- a/farm/shop.py +++ b/farm/shop.py @@ -23,7 +23,7 @@ class CShopManager: column_name = [ "-", "种子名称", - "种子单价" + "种子单价", "解锁等级", "果实单价", "收获经验", @@ -132,39 +132,39 @@ class CShopManager: Returns: str: """ - plantDict = await g_pSqlManager.getUserPlantByUid(uid) - if not plantDict: + if not isinstance(name, str) or name.strip() == "": + name = "" + + plant = await g_pSqlManager.getUserPlantByUid(uid) + if not plant: return "你仓库没有可以出售的作物" - totalPoint = 0 + point = 0 + totalSold = 0 + isAll = (num == -1) - if not name: - for plantName, count in plantDict.items(): + if name == "": + for plantName, count in plant.items(): plantInfo = g_pJsonManager.m_pPlant['plant'][plantName] - totalPoint += plantInfo['price'] * count - await g_pSqlManager.deleteUserPlantByName(uid, plantName) + point += plantInfo['price'] * count + await g_pSqlManager.updateUserPlantByName(uid, plantName, 0) else: - currentCount = plantDict.get(name) - if currentCount is None: - return f"出售作物{name}出错:你没有这种作物" + if name not in plant: + return f"出售作物{name}出错:仓库中不存在该作物" + available = plant[name] + sellAmount = available if isAll else min(available, num) + if sellAmount <= 0: + return f"出售作物{name}出错:数量不足" + await g_pSqlManager.updateUserPlantByName(uid, name, available - sellAmount) + totalSold = sellAmount - if num == -1: - sellCount = currentCount - else: - if num > currentCount: - return f"出售作物{name}出错:数量不足" - sellCount = num + totalPoint = point if name == "" else totalSold * g_pJsonManager.m_pPlant['plant'][name]['price'] + currentPoint = await g_pSqlManager.getUserPointByUid(uid) + await g_pSqlManager.updateUserPointByUid(uid, currentPoint + totalPoint) - plantInfo = g_pJsonManager.m_pPlant['plant'][name] - totalPoint = plantInfo['price'] * sellCount - await g_pSqlManager.addUserPlantByUid(uid, name, -sellCount) - - point = await g_pSqlManager.getUserPointByUid(uid) - await g_pSqlManager.updateUserPointByUid(uid, point + totalPoint) - - if not name: - return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{point + totalPoint}" + if name == "": + return f"成功出售所有作物,获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}" else: - return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{point + totalPoint}" + return f"成功出售{name},获得农场币:{totalPoint},当前农场币:{currentPoint + totalPoint}" g_pShopManager = CShopManager() diff --git a/log/log.md b/log/log.md new file mode 100644 index 0000000..590d27c --- /dev/null +++ b/log/log.md @@ -0,0 +1,15 @@ +# 真寻农场更新日志 + +## V1.1 + +- 感谢[quanquan1014](https://github.com/quanquan1014)对农场名称中包含特殊字符的处理。 +- 更正数据库写法,但是会导致V1.0用户的作物和种子丢失 +- 完善我的农场图片资源,现在会在左上角显示经验、等级、金币等详细信息了 +- 完善出售作物逻辑,现在可以空置作物名称来一键出售全部作物了,也可以选择空置数量来一键出售仓库种指定作物 +- 完善播种逻辑,现在可以空置数量来一键播种指定作物了 +- 新增更改农场名指令 +- 改进对土地开垦条件判断 + +## V1.0 + +世界的起源。 diff --git a/request.py b/request.py index b3daab8..3031324 100644 --- a/request.py +++ b/request.py @@ -1,5 +1,4 @@ -from email.mime import base -from unittest import result +import os import httpx @@ -11,11 +10,11 @@ class CRequestManager: @classmethod async def download(cls, url: str, savePath: str, fileName: str) -> bool: - """下载文件到指定路径 + """下载文件到指定路径并覆盖已存在的文件 Args: url (str): 文件的下载链接 - savePath (str): 保存文件的文件夹路径 + savePath (str): 保存文件夹路径 fileName (str): 保存后的文件名 Returns: @@ -25,7 +24,8 @@ class CRequestManager: async with httpx.AsyncClient(timeout=10.0) as client: response = await client.get(url) if response.status_code == 200: - fullPath = savePath.rstrip("/") + "/" + fileName + fullPath = os.path.join(savePath, fileName) + os.makedirs(os.path.dirname(fullPath), exist_ok=True) with open(fullPath, "wb") as f: f.write(response.content) return True diff --git a/resource/plant/ATTomato/1.png b/resource/plant/ATTomato/1.png new file mode 100644 index 0000000..470bbe2 Binary files /dev/null and b/resource/plant/ATTomato/1.png differ diff --git a/resource/plant/ATTomato/2.png b/resource/plant/ATTomato/2.png new file mode 100644 index 0000000..1c729bc Binary files /dev/null and b/resource/plant/ATTomato/2.png differ diff --git a/resource/plant/ATTomato/3.png b/resource/plant/ATTomato/3.png new file mode 100644 index 0000000..fa2521a Binary files /dev/null and b/resource/plant/ATTomato/3.png differ diff --git a/resource/plant/ATTomato/4.png b/resource/plant/ATTomato/4.png new file mode 100644 index 0000000..b399ef0 Binary files /dev/null and b/resource/plant/ATTomato/4.png differ diff --git a/resource/plant/ATTomato/5.png b/resource/plant/ATTomato/5.png new file mode 100644 index 0000000..3ddc306 Binary files /dev/null and b/resource/plant/ATTomato/5.png differ diff --git a/resource/plant/ATTomato/icon.png b/resource/plant/ATTomato/icon.png new file mode 100644 index 0000000..7d09384 Binary files /dev/null and b/resource/plant/ATTomato/icon.png differ