自用DOF插件DP代码
---@type DPlocal dp = _DP
---@type DPXGame
local dpx = _DPX
local game = require("df.game")
local logger = require("df.logger")
local luv = require("luv")
local luasql = require("luasql.mysql")
logger.info("opt: %s", dpx.opt())
local item_handler = { }
-- see dp2/lua/df/doc for document !
-- 记录在线情况
local online = {}
-- 以下是跨界石代码 只要在背包装备栏的第一格,无论什么品级都可以被转移(不用就删掉这一大段)!
item_handler = function(user, item_id)
if not user:MoveToAccCargo(game.ItemSpace.INVENTORY, 9) then
dpx.item.add(user.cptr, item_id)
end
end
-- 以上是跨界石代码 只要在背包装备栏的第一格,无论什么品级都可以被转移(不用就删掉这一大段)!
-- 以下是异界2 次数重置券(不用就删掉这一大段)!
item_handler = function(user, item_id)
user:ResetDimensionInout(0)
user:ResetDimensionInout(1)
user:ResetDimensionInout(2)
end
-- 以下是异界3 次数重置券(不用就删掉这一大段)!
item_handler = function(user, item_id)
user:ResetDimensionInout(3)
user:ResetDimensionInout(4)
user:ResetDimensionInout(5)
end
---------------------------------- 以下代码是装备继承券,不需要就删除-------------------------------- !
--[[
装备继承券
仿照7576新建一个道具, 删除节, 以实现一个可以在城镇中使用后毫无效果的道具
将装备背包中的第一格道具的强化/增幅继承到第二格道具上
]]
item_handler = function(user, item_id)
local mask = game.InheritMask.FLAG_UPGRADE | game.InheritMask.FLAG_AMPLIFY
mask = mask | game.InheritMask.FLAG_MOVE_UPGRADE | game.InheritMask.FLAG_MOVE_AMPLIFY
if not dpx.item.inherit(user.cptr, 9, 10, mask) then
dpx.item.add(user.cptr, item_id)
end
end
-- 以下是主线任务完成券代码 自动完成符合等级的主线任务(不用就删掉这一大段)!
item_handler = function(user, item_id)
local quest = dpx.quest
local lst = quest.all(user.cptr)
local chr_level = user:GetCharacLevel()
--下面{}内写要排除的任务编号即可,就不会自动完成如下编号的任务,例子是辅助装备魔法石
local evade_lst = {}
for i, v in ipairs(lst) do
for j, w in ipairs(evade_lst) do
if v == w then
table.remove(lst, i)
end
end
end
for i, v in ipairs(lst) do
local id = v
local info = quest.info(user.cptr, id)
if info then
if not info.is_cleared and info.type == game.QuestType.epic and info.min_level <= chr_level then
quest.clear(user.cptr, id)
end
end
end
quest.update(user.cptr)
end
-- 以上是任务完成券代码 自动完成符合等级的主线任务(不用就删掉这一大段)!
-- 以下是普通任务完成券代码 自动完成符合等级的普通任务(不用就删掉这一大段)!
item_handler = function(user, item_id)
local quest = dpx.quest
local lst = quest.all(user.cptr)
local chr_level = user:GetCharacLevel()
--下面{}内写要排除的任务编号即可,就不会自动完成如下编号的任务,例子是辅助装备魔法石
local evade_lst = { 2708, 2710, 2712, 2702 }
for i, v in ipairs(lst) do
for j, w in ipairs(evade_lst) do
if v == w then
table.remove(lst, i)
end
end
end
for i, v in ipairs(lst) do
local id = v
local info = quest.info(user.cptr, id)
if info then
if not info.is_cleared and info.type == game.QuestType.common_unique and info.min_level <= chr_level then
quest.clear(user.cptr, id)
end
end
end
quest.update(user.cptr)
end
-- 以上是任务完成券代码 自动完成符合等级的普通任务(不用就删掉这一大段)!
-- 以下是宠物删除券代码 删除宠物前2栏(不用就删掉这一大段)!
item_handler = function(user, item_id)
for i = 0, 13, 1 do
local info = dpx.item.info(user.cptr, game.ItemSpace.CREATURE, i)
if info then
logger.info(string.format("will delete id: %d count: %d name: %s attach: %d",
info.id, info.count, info.name, info.attach_type))
dpx.item.delete(user.cptr, game.ItemSpace.CREATURE, i)
end
end
user:SendItemSpace(game.ItemSpace.CREATURE)
user:SendNotiPacketMessage("\n已清理宠物前2栏!", 1)
end
-- 以下是宠物删除券代码 删除宠物前2栏(不用就删掉这一大段)!
-- 副职业一键分解券 需要学习分解师副职
item_handler = function(user, item_id)
dpx.item.add(user.cptr, item_id)
for i = 9, 100, 1 do
local info = dpx.item.info(user.cptr, game.ItemSpace.INVENTORY, i)
if info then
logger.info(string.format("will Disjoint id: %d count: %d name: %s attach: %d",
info.id, info.count, info.name, info.attach_type))
user:Disjoint(game.ItemSpace.INVENTORY, i, user)
end
end
user:SendItemSpace(game.ItemSpace.INVENTORY)
end
-- 以下是20级直升券代码 升到20级并清理前19级任务,可在20级前任意一级使用(不用就删掉这一大段)!
item_handler = function(user, item_id)
local currentLevel = user:GetCharacLevel()
if currentLevel >= 20 then
dpx.item.add(user.cptr, item_id)
user:SendNotiPacketMessage("\n当前用户已满20级,无法使用20级直升券!", 1)
else
for i = currentLevel, 18, 1 do
user:AddCharacExpPercent(1)
end
logger.info(string.format("%d 升到19级", user:GetCharacNo()))
local quest = dpx.quest
local lst = quest.all(user.cptr)
local chr_level = user:GetCharacLevel()
--下面{}内写要排除的任务编号即可,就不会自动完成如下编号的任务,例子是辅助装备魔法石
local evade_lst = {}
for i, v in ipairs(lst) do
for j, w in ipairs(evade_lst) do
if v == w then
table.remove(lst, i)
end
end
end
for i, v in ipairs(lst) do
local id = v
local info = quest.info(user.cptr, id)
if info then
if not info.is_cleared and info.type == game.QuestType.epic and info.min_level <= chr_level then
quest.clear(user.cptr, id)
end
end
end
quest.update(user.cptr)
logger.info(string.format("%d 19级主线清理完成", user:GetCharacNo()))
user:AddCharacExpPercent(1)
logger.info(string.format("%d 升到20级", user:GetCharacNo()))
end
end
-- 以下是20级直升券代码 升到20级并清理前19级任务(不用就删掉这一大段)!
-- 以下是懸賞令 - 牛頭械王 强制接取懸賞令 - 牛頭械王任务(不用就删掉这一大段)!
item_handler = function(user, item_id)
dpx.quest.accept(user, 1289, true)
dpx.quest.update(user)
end
-- 以下是懸賞令 - 牛頭械王 强制接取懸賞令 - 牛頭械王任务(不用就删掉这一大段)!
-- 物品回收券 无限使用
item_handler = function(user, item_id)
dpx.item.add(user.cptr, item_id)
local env = luasql.mysql()
local conn = env:connect("dp2", "game", "uu5!^%jg", "127.0.0.1", 3306)
conn:execute "SET NAMES latin1"
-- 宠物回收
-- 宠物装备回收
for i = 140, 280, 1 do
local info = dpx.item.info(user.cptr, game.ItemSpace.CREATURE, i)
if info then
logger.info(string.format("%d will CREATURE id: %d count: %d name: %s attach: %d", i, info.id, info.count, info.name, info.attach_type))
cur = conn:execute(string.format("select return_id from recycle_item_dict where item_id=%d", info.id));
row = cur:fetch({}, "a");
while row do
logger.info(string.format("%d will CREATURE id: %d count: %d name: %s attach: %d return_id %d", i, info.id, info.count, info.name, info.attach_type, row.return_id))
dpx.item.add(user.cptr, row.return_id)
dpx.item.delete(user.cptr, game.ItemSpace.CREATURE, i)
row = cur:fetch(row, "a")
end
end
end
-- 时装回收
-- 徽章回收
-- 装备回收
user:SendItemSpace(game.ItemSpace.CREATURE)
conn:close()
env:close()
end
local my_useitem2 = function(_user, item_id)
local user = game.fac.user(_user)
local handler = item_handler
if handler then
handler(user, item_id)
logger.info(" acc: %d chr: %d item_id: %d", user:GetAccId(), user:GetCharacNo(), item_id)
end
end
-- 聊天框GM功能
local on_input = function(next, _user, input)
local user = game.fac.user(_user)
local env = luasql.mysql()
local conn = env:connect("taiwan_billing", "game", "uu5!^%jg", "127.0.0.1", 3306)
conn:execute "SET NAMES latin1"
logger.info("INPUT|%d|%s|%s", user:GetAccId(), user:GetCharacName(), input)
if input == "//expert_job_lv_max" then
--todo 缺少是否学习副职业的判断
conn:execute(string.format("update taiwan_cain.charac_stat set expert_job_exp=2054 where charac_no=%d",
user:GetCharacNo()))
user:SendNotiPacketMessage("\n机器人已为您将副职业提升至满级,请重新登录!", 1)
return 0
elseif input == "//g" then
user:SendNotiPacketMessage("==========机器人指令大全==========", 1)
user:SendNotiPacketMessage("1.开启原始台服GM //open_gm", 1)
user:SendNotiPacketMessage("2.发送点券 //cash 数量 (默认发送100w点券)", 1)
user:SendNotiPacketMessage("3.解除地图难度限制 //open_all_dungeon", 1)
user:SendNotiPacketMessage("4.解除角色创建限制 //open_create_limit", 1)
user:SendNotiPacketMessage("5.副职业满级 //expert_job_lv_max", 1)
user:SendNotiPacketMessage("6.设置pvp等级 //set_pvp_lv 等级 (0-34,默认设置为29 即至尊10)", 1)
user:SendNotiPacketMessage("7.开启活动 //start_event 活动编号 (默认开启泡点活动)", 1)
user:SendNotiPacketMessage("8.关闭活动 //stop_event 活动编号 (默认关闭泡点活动)", 1)
user:SendNotiPacketMessage("9.关闭原始台服GM //close_gm", 1)
user:SendNotiPacketMessage("10.清理时装 //clean_avatar 格数 (默认清理前2栏)", 1)
user:SendNotiPacketMessage("11.查询物品代码 //itemcode 物品名", 1)
user:SendNotiPacketMessage("12.发送物品 //send_item 物品代码 数量", 1)
user:SendNotiPacketMessage("==========机器人指令大全==========", 1)
return 0
elseif string.startWith(input, "//cash") then
local cashInfoList = string.split(input, " ")
if #cashInfoList >= 2 then
local cashInfo = tonumber(cashInfoList)
conn:execute(string.format("update taiwan_billing.cash_cera set cera=cera+%d where account = %d",
cashInfo, user:GetAccId()))
user:SendNotiPacketMessage(string.format("\n机器人已为您发送点券%d,请查收!", cashInfo), 1)
else
conn:execute(string.format("update taiwan_billing.cash_cera set cera=cera+%d where account = %d",
1000000, user:GetAccId()))
user:SendNotiPacketMessage("\n机器人已为您发送点券100万,请查收!", 1)
end
return 0
elseif input == "//open_all_dungeon" then
conn:execute(string.format("update taiwan_cain.member_dungeon set dungeon='2|3,3|3,4|3,5|3,6|3,7|3,8|3,9|3,11|3,12|3,13|3,14|3,15|3,17|3,21|3,22|3,23|3,24|3,25|3,26|3,27|3,31|3,32|3,33|3,34|3,35|3,36|3,37|3,40|3,42|3,43|3,44|3,45|3,50|3,51|3,52|3,53|3,60|3,61|3,65|2,66|1,67|2,70|3,71|3,72|3,73|3,74|3,75|3,76|3,77|3,80|3,81|3,82|3,83|3,84|3,85|3,86|3,87|3,88|3,89|3,90|3,91|2,92|3,93|3,100|3,101|3,102|3,103|3,104|3,110|3,111|3,112|3,140|3,141|3,502|3,511|3,521|3,1000|3,1500|3,1501|3,1502|3,1504|1,1506|3,3506|3,10000|3' where m_id=%d",
user:GetAccId()))
user:SendNotiPacketMessage("\n机器人已为您解除地图难度限制,请重新登录!", 1)
return 0
elseif input == "//open_create_limit" then
conn:execute(string.format("update d_taiwan.limit_create_character set count=2 where count> 0 and m_id=%d",
user:GetAccId()))
user:SendNotiPacketMessage("\n机器人已为您暂时解除角色创建限制,可再创建2次角色,请重新登录!", 1)
return 0
elseif string.startWith(input, "//set_pvp_lv") then
local pvpInfoList = string.split(input, " ")
if #pvpInfoList >= 2 then
local pvpLevel = tonumber(pvpInfoList)
if pvpLevel >= 0 and pvpLevel <= 34 then
conn:execute(string.format("UPDATE taiwan_cain.pvp_result t SET t.pvp_grade = %d WHERE t.charac_no=%d",
pvpLevel,
user:GetCharacNo()))
user:SendNotiPacketMessage(string.format("\n机器人已为您设置pvp等级为%d,请重新登录!",
pvpLevel), 1)
else
user:SendNotiPacketMessage(string.format("\n命令输出错误 请按照(//set_pvp_lv 20)格式输入!",
pvpLevel), 1)
end
else
conn:execute(string.format("UPDATE taiwan_cain.pvp_result t SET t.pvp_grade = 29 WHERE t.charac_no=%d",
pvpLevel,
user:GetCharacNo()))
user:SendNotiPacketMessage(string.format("\n机器人已为您设置pvp等级至尊10,请重新登录!",
pvpLevel), 1)
end
return 0
elseif input == "//start_event" then
-- todo 解析活动编号
conn:execute(string.format("UPDATE dp2.event t SET t.status = 1 WHERE t.id=1",
user:GetCharacNo()))
user:SendNotiPacketMessage("\n机器人已开启泡点活动,请保持登录获取福利!", 1)
return 0
elseif input == "//stop_event" then
-- todo 解析活动编号
conn:execute(string.format("UPDATE dp2.event t SET t.status = 0 WHERE t.id=1",
user:GetCharacNo()))
user:SendNotiPacketMessage("\n机器人已关闭泡点活动,感谢您的持续支持!", 1)
return 0
elseif input == "//open_gm" then
conn:execute(string.format("insert into taiwan_login.gm_manifest (m_id,level) VALUES(%d,7) ON DUPLICATE KEY UPDATE level=7",
user:GetAccId()))
user:SendNotiPacketMessage("\n机器人已为您开启台服真GM模式,请重新登录", 1)
return 0
elseif input == "//close_gm" then
conn:execute(string.format("delete from taiwan_login.gm_manifest where m_id=%d",
user:GetAccId()))
user:SendNotiPacketMessage("\n机器人已为您关闭台服真GM模式,请重新登录", 1)
return 0
elseif string.startWith(input, "//clean_avatar") then
local avatarInfoList = string.split(input, " ")
if #avatarInfoList >= 2 then
local avatarInfo = tonumber(avatarInfoList)
for i = 0, avatarInfo - 1, 1 do
local info = dpx.item.info(user.cptr, game.ItemSpace.AVATAR, i)
if info then
logger.info(string.format("will delete id: %d count: %d name: %s attach: %d",
info.id, info.count, info.name, info.attach_type))
dpx.item.delete(user.cptr, game.ItemSpace.AVATAR, i)
end
end
user:SendItemSpace(game.ItemSpace.AVATAR)
user:SendNotiPacketMessage(string.format("机器人已为您清理时装前%d格,请及时查看确认!", avatarInfo), 1)
else
for i = 0, 13, 1 do
local info = dpx.item.info(user.cptr, game.ItemSpace.AVATAR, i)
if info then
logger.info(string.format("will delete id: %d count: %d name: %s attach: %d",
info.id, info.count, info.name, info.attach_type))
dpx.item.delete(user.cptr, game.ItemSpace.AVATAR, i)
end
end
user:SendItemSpace(game.ItemSpace.AVATAR)
user:SendNotiPacketMessage("机器人已为您清理时装前2栏,请及时查看确认!", 1)
end
return 0
elseif string.startWith(input, "//itemcode") then
local itemcodeInfoList = string.split(input, " ")
if #itemcodeInfoList >= 2 then
local cur = conn:execute(
string.format("select EquipmentCode, EquipmentNameCN from dp2.equipment where EquipmentName like '%%%s%%' union all select StackableCode, StackableNameCN from dp2.stackable where stackable.StackableName like '%%%s%%'",
itemcodeInfoList, itemcodeInfoList))
local row = cur:fetch({}, "a")
user:SendNotiPacketMessage("查询结果:", 1)
while row do
user:SendNotiPacketMessage(string.format("%s %d", row.EquipmentNameCN, row.EquipmentCode), 1)
row = cur:fetch({}, "a")
end
else
user:SendNotiPacketMessage("\n命令输出错误 请按照(//itemcode 无色小晶块)格式输入!", 1)
end
return 0
elseif string.startWith(input, "//send_item") then
local itemcodeInfoList = string.split(input, " ")
if #itemcodeInfoList >= 3 then
local cur = conn:execute(
string.format("select EquipmentCode, EquipmentNameCN from dp2.equipment where EquipmentCode = %d union all select StackableCode, StackableNameCN from dp2.stackable where stackable.StackableCode = %d",
tonumber(itemcodeInfoList), tonumber(itemcodeInfoList)))
local row = cur:fetch({}, "a")
if row then
dpx.item.add(user.cptr, tonumber(itemcodeInfoList), tonumber(itemcodeInfoList))
user:SendNotiPacketMessage(string.format("机器人已为您发送 %s %d个,请及时查看确认!",
row.EquipmentNameCN, tonumber(itemcodeInfoList)), 1)
else
user:SendNotiPacketMessage("\n您要发送的物品不存在,请查询物品代码后再发!", 1)
end
else
user:SendNotiPacketMessage("\n命令输出错误 请按照(//send_item 3037 10)格式输入!", 1)
end
return 0
end
conn:close()
env:close()
return next()
end
-- 每日福利
local daily_fuli = function(_user)
local user = game.fac.user(_user)
local uid = user:GetAccId()
online = user --登录的时候记录
local env = luasql.mysql()
local conn = env:connect("taiwan_billing", "game", "uu5!^%jg", "127.0.0.1", 3306)
conn:execute "SET NAMES latin1"
conn:execute "create database if not exists dp2"
conn:execute "create table if not exists dp2.login(id INT(10) not null primary key AUTO_INCREMENT,account INT(10) default 0 not null, loginTime INT(10) UNSIGNED default 0 not null, firstLogin INT(10) UNSIGNED default 0 not null)"
local cera = 1000000 -- 需要发送的点券数量
-- 获取账号的登录是否当日首次登录
local cur = conn:execute(
string.format("select firstLogin from dp2.login where account = %d and loginTime >= %d",
user:GetAccId(), GetCurrentDayZeroTimestamp()))
local row = cur:fetch({}, "a")
if not row then
-- 未查询到用户记录 说明这次是首次登录 发放每日福利
conn:execute(string.format("insert into dp2.login(account,loginTime,firstLogin) values (%d,%d,0)",
user:GetAccId(), os.time(), 0))
conn:execute(string.format("update taiwan_billing.cash_cera set cera=cera+%d where account = %d",
cera, user:GetAccId()))
dpx.item.add(user.cptr, 21060501, 1)
dpx.item.add(user.cptr, 2021120302, 1)
user:SendNotiPacketMessage("\n每日首次登录,奖励100w点券,一束鲜花*1赛丽亚抽奖卷*1", 1)
else
user:SendNotiPacketMessage("\n每日首次登录,奖励100w点券,一束鲜花*1赛丽亚抽奖卷*1", 1)
end
conn:close()
env:close()
end
function GetCurrentDayZeroTimestamp(_timerStamp)
--获得当前的时间
local timeStamp = _timerStamp
if not timeStamp then
timeStamp = os.time()
end
--获得时间格式
local formatTime = os.date("*t", timeStamp)
formatTime.hour = 0
formatTime.min = 0
formatTime.sec = 0
--获得当天零点的时间戳
local curTimestamp = os.time(formatTime)
return curTimestamp
end
-- 判断字符串起始
function string:startWith(substr)
if self == nil or substr == nil then
return nil, "the string or the sub-stirng parameter is nil"
end
if string.find(self, substr) ~= 1 then
return false
else
return true
end
end
-- 分隔字符串
function string:split(sep)
local sep, fields = sep or "\t", {}
local pattern = string.format("([^%s]+)", sep)
self:gsub(pattern, function(c)
fields[#fields + 1] = c
end)
return fields
end
--修复绝望之塔金币异常
local function MyUseAncientDungeonItems(fnext, _party, _dungeon, _item)
local dungeon = game.fac.dungeon(_dungeon)
local dungeon_index = dungeon:GetIndex()
if dungeon_index >= 11008 and dungeon_index <= 11107 then
return true
end
return fnext()
end
--泡点活动
local paodianTimer = luv.new_timer()
local function paodianEvent()
local pdCash = 100 -- 每分钟需要发送的点券数量
local env = luasql.mysql()
local conn = env:connect("dp2", "game", "uu5!^%jg", "127.0.0.1", 3306)
conn:execute "SET NAMES latin1"
myEventCur = assert(conn:execute "SELECT id, status from dp2.event where id = 1")
myEvent = myEventCur:fetch({}, "a")
if myEvent.status == "1" then
for k, v in pairs(online) do
logger.info(string.format("泡点发送至%s", v:GetCharacName()))
conn:execute(string.format("update taiwan_billing.cash_cera set cera=cera+%d where account = %d",
pdCash, v:GetAccId()))
v:SendNotiPacketMessage(string.format("泡点奖励获得%d点券", pdCash))
end
logger.info("泡点活动已开启,本次泡点发放完毕!")
else
logger.info("泡点活动未开启!")
end
conn:close()
env:close()
end
paodianTimer:start(10000, 60000, paodianEvent) --启动后10s开始执行,随后每60s执行一次
--初始化
local initTimer = luv.new_timer()
local function initFun()
local env = luasql.mysql()
local conn = env:connect("taiwan_billing", "game", "uu5!^%jg", "127.0.0.1", 3306)
conn:execute "SET NAMES latin1"
conn:execute "create database if not exists dp2"
conn:execute "create table if not exists dp2.event( id int not null, status int default 0 not null, constraint event_pk primary key (id))"
conn:execute "insert into dp2.event (id,status) VALUES(1,0) ON DUPLICATE KEY UPDATE status=0"
logger.info("服务器重启,默认关闭泡点活动")
--logger.info("开始加载pvf信息到数据库,预计需要10分钟")
--os.execute("rm -rf /var/log/dp2/dp2server.log")
--os.execute("chmod +x /dp2/dp2server/dp2-server")
--os.execute("/dp2/dp2server/dp2-server")
end
initTimer:start(1000, 0, initFun) --启动后1s开始执行,只执行一次
-- 登出的时候移除
local function onLogout(_user)
local user = game.fac.user(_user)
local uid = user:GetAccId()
online = nil
end
dpx.hook(game.HookType.GmInput, on_input)
dpx.hook(game.HookType.UseItem2, my_useitem2)
dpx.hook(game.HookType.Reach_GameWord, daily_fuli)
dpx.hook(game.HookType.Leave_GameWord, onLogout)
dpx.hook(game.HookType.CParty_UseAncientDungeonItems, MyUseAncientDungeonItems) --修复绝望之塔金币异常
-- 以下这些,自行选用,不用的删除当前行就行,用的自行按照说明文档添加!
dpx.disable_item_routing()-- 史诗免确认提示框 !
dpx.disable_giveup_panalty()-- 退出副本后角色默认不虚弱 !
dpx.extend_teleport_item() --瞬间移动药剂扩展
dpx.enable_creator() -- 允许创建缔造者
dpx.set_unlimit_towerofdespair() --绝望之塔通关后仍可继续挑战(需门票)
--dpx.enable_game_master() --开启台服GM模式
这些代码都是我自己开群服自用的、自己添加文件道具导入即可。DP用2.6以上的版本、
版本低了一些不支持、其中一键分解需要个人学习分解师并且开启商店然后使用道具才会实现
这个代码自己复制到df_game_r.lua覆盖即可 剩余的自己对接
包含自动每日邮件奖励 那些ID自己需要的物品改即可
念了十几年书,还是幼儿园。 还是哥哥稳666666 还是哥哥稳666666 谢谢楼主分享 开车无难事,只怕有新人! 哈哈,白嫖就是舒服。 我是个凑数的 还是哥哥稳666666 谢谢分享哈哈哈哈哈哈哈 念了十几年书,还是幼儿园。 小手一抖,积分到手 谢谢分享哈哈哈哈哈哈哈 看帖回帖是美德 大家一起刷刷分吧!!!