• There is NO official Otland's Discord server and NO official Otland's server list. The Otland's Staff does not manage any Discord server or server list. Moderators or administrator of any Discord server or server lists have NO connection to the Otland's Staff. Do not get scammed!

NPC Name change item

Ciosny

Member
Joined
Aug 16, 2024
Messages
116
Solutions
1
Reaction score
15
Hi,
I've seen various scripts to create an item that allows you to change your nickname... But how to exclude nicknames such as "GM" "Tutor" "Admin".....?

For TFS 1.4.2

I found one for 1.2 T.F.S but without what I'm looking for and it probably won't work on 1.4.2:
This should work for 1.0, 1.1, 1.2

local x = {
length = {['min'] = 4, ['max'] = 20},
itemid = 8189
}

function setPlayerName(cid, newName)
local player = Player(cid)
local name = db.escapeString(player:getName())
newName = db.escapeString(newName)
player:remove()
db.query('UPDATE players SET name = '.. newName ..' WHERE name = '.. name ..';')
end

function correctNameLength(str)
local value = str:len()
return x.length['min'] >= value and x.length['max'] <= value
end

function onSay(cid, words, param, channel)
local player = type(cid) == "userdata" and cid or Player(cid)

if not param or param == '' then
player:sendCancelMessage('Use: '.. words ..' <newname>')
return true
end

local position, item = player:getPosition(), ItemType(x.itemid)

if player:getItemCount(x.itemid) < 1 then
player:sendCancelMessage("You do not have enough ".. item:getName()..".")
position:sendMagicEffect(CONST_ME_POFF)
return true
end

if param:find("[^%a%s]") then
player:sendCancelMessage("This name ".. param .." is invalid")
return true
end

if not correctNameLength(param) then
player:sendCancelMessage("The new name has to be between " .. x.length['min'] .. " and " .. x.length['max'] .. " characters.")
return true
end
item:remove()
setPlayerName(player:getId(), param)
return true
end
 
Try this:

LUA:
local x = {
    length = {['min'] = 4, ['max'] = 20},
    itemid = 8189
}

-- List of forbidden names
local forbiddenNames = {
    "GM",
    "Tutor",
    "Admin",
    "Owner",
    "Developer",
    "Support",
    "Moderator"
}

function setPlayerName(cid, newName)
    local player = Player(cid)
    local name = db.escapeString(player:getName())
    newName = db.escapeString(newName)
    player:remove()
    db.query('UPDATE players SET name = '.. newName ..' WHERE name = '.. name ..';')
end

function correctNameLength(str)
    local value = str:len()
    return x.length['min'] <= value and x.length['max'] >= value
end

function isNameForbidden(name)
    for _, forbiddenName in ipairs(forbiddenNames) do
        if name:lower() == forbiddenName:lower() then
            return true
        end
    end
    return false
end

function onSay(cid, words, param, channel)
    local player = type(cid) == "userdata" and cid or Player(cid)

    if not param or param == '' then
        player:sendCancelMessage('Use: '.. words ..' <newname>')
        return true
    end

    local position, item = player:getPosition(), ItemType(x.itemid)

    if player:getItemCount(x.itemid) < 1 then
        player:sendCancelMessage("You do not have enough ".. item:getName()..".")
        position:sendMagicEffect(CONST_ME_POFF)
        return true
    end

    if param:find("[^%a%s]") then
        player:sendCancelMessage("This name ".. param .." is invalid")
        return true
    end

    if isNameForbidden(param) then
        player:sendCancelMessage("This name is not allowed.")
        return true
    end

    if not correctNameLength(param) then
        player:sendCancelMessage("The new name has to be between " .. x.length['min'] .. " and " .. x.length['max'] .. " characters.")
        return true
    end

    item:remove()
    setPlayerName(player:getId(), param)
    player:sendTextMessage(MESSAGE_INFO_DESCR, "Your name has been changed to " .. param .. ".")
    return true
end
 
I'm not an expert... but here is the "function onSay" function, and shouldn't there be something onUse if I want the ability to change the nickname to be activated when clicking the scroll wheel?
 
I was just making the script from scratch for fun because I enjoy learning haha. Check out the gif to see how it works. For example, you click the item, a window pops up where you type the name and confirm. After waiting for 3 seconds, the player is automatically kicked out. They have to log back in, and their account should now have the name changed.

data/scripts/changeName ~~ Revscripts.
LUA:
local config = {
    ItemID = 1950, -- Defining the item ID
    forbiddenNames = {
        "CM", "GM", "GOD", "GAME MASTER", "GAMEMASTER", "HOSTER", "RACIST",
        "Tutor", "Admin", "Owner", "Developer", "Support", "Moderator"
    },
    length = {['min'] = 4, ['max'] = 20}, -- Defining the minimum and maximum name length allowed
}

local function isNameForbidden(name)
    for i = 1, #config.forbiddenNames do
        if name:find(config.forbiddenNames[i]) then
            return true
        end
    end
    return false
end

local function updatePlayerName(player, newName)
    local guid = player:getGuid()
    db.query("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. guid .. " LIMIT 1;")
end

local function isNameLengthValid(name)
    local length = name:len()
    return length >= config.length['min'] and length <= config.length['max']
end

local function onTextEdit(player, item, text)
    player:unregisterEvent("TextEdit")

    local name = text:gsub("Enter the desired name: ", "")

    if #name == 0 then
        return true
    end

    if not isNameLengthValid(name) then
        player:sendCancelMessage("The name must be between " .. config.length['min'] .. " and " .. config.length['max'] .. " characters.")
        return true
    end

    local query = "SELECT `name` FROM `players` WHERE `name` = " .. db.escapeString(name) .. ";"
    local result = db.storeQuery(query)

    if result then
        player:sendCancelMessage("This name already exists. Please choose another one.")
        return true
    end

    if isNameForbidden(name) then
        player:sendCancelMessage("Sorry, this name is forbidden.")
    else
        updatePlayerName(player, name)
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Your name has been changed to: " .. name .. ". In 3 seconds, you will be kicked. Please log in again for the changes to take effect.")
        player:getPosition():sendMagicEffect(CONST_ME_HEARTS)
     
        local playerGuid = player:getGuid()

        addEvent(function()
            local player = Player(playerGuid)
            if player then
                local itemToRemove = player:getItemById(config.ItemID, true)
                if itemToRemove then
                    itemToRemove:remove(1)
                end
                player:remove()
            end
        end, 3000) -- 3-second delay for kick and item removal
    end

    return true
end

local textEditEvent = CreatureEvent("TextEdit")
function textEditEvent.onTextEdit(player, item, text)
    return onTextEdit(player, item, text)
end
textEditEvent:register()

local testAction = Action()

function testAction.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    local tile = player:getTile()

    if not tile or not tile:hasFlag(TILESTATE_PROTECTIONZONE) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You can only use this item in a protection zone (PZ).")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    if player:getCondition(CONDITION_INFIGHT) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot use this item while in combat.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    local parent = item:getParent()
    if parent and parent:isContainer() and parent:getTopParent() == player then
        player:registerEvent("TextEdit")
        player:showTextDialog(1948, "Enter the desired name: ", true)
    else
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "The item must be in your backpack to be used.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
    end
    return true
end

testAction:id(config.ItemID)
testAction:register()


chanename.gif
 
Last edited:
I was just making the script from scratch for fun because I enjoy learning haha. Check out the gif to see how it works. For example, you click the item, a window pops up where you type the name and confirm. After waiting for 3 seconds, the player is automatically kicked out. They have to log back in, and their account should now have the name changed.

data/scripts/changeName ~~ Revscripts.
LUA:
local config = {
    ItemID = 1950, -- Defining the item ID
    forbiddenNames = {
        "CM", "GM", "GOD", "GAME MASTER", "GAMEMASTER", "HOSTER", "RACIST",
        "Tutor", "Admin", "Owner", "Developer", "Support", "Moderator"
    },
    length = {['min'] = 4, ['max'] = 20}, -- Defining the minimum and maximum name length allowed
}

local function isNameForbidden(name)
    for i = 1, #config.forbiddenNames do
        if name:find(config.forbiddenNames[i]) then
            return true
        end
    end
    return false
end

local function updatePlayerName(player, newName)
    local guid = player:getGuid()
    db.query("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. guid .. " LIMIT 1;")
end

local function isNameLengthValid(name)
    local length = name:len()
    return length >= config.length['min'] and length <= config.length['max']
end

local function onTextEdit(player, item, text)
    player:unregisterEvent("TextEdit")

    local name = text:gsub("Enter the desired name: ", "")

    if #name == 0 then
        return true
    end

    if not isNameLengthValid(name) then
        player:sendCancelMessage("The name must be between " .. config.length['min'] .. " and " .. config.length['max'] .. " characters.")
        return true
    end

    local query = "SELECT `name` FROM `players` WHERE `name` = " .. db.escapeString(name) .. ";"
    local result = db.storeQuery(query)

    if result then
        player:sendCancelMessage("This name already exists. Please choose another one.")
        return true
    end

    if isNameForbidden(name) then
        player:sendCancelMessage("Sorry, this name is forbidden.")
    else
        updatePlayerName(player, name)
        player:sendTextMessage(MESSAGE_INFO_DESCR, "Your name has been changed to: " .. name .. ". In 3 seconds, you will be kicked. Please log in again for the changes to take effect.")
        player:getPosition():sendMagicEffect(CONST_ME_HEARTS)
    
        local playerGuid = player:getGuid()

        addEvent(function()
            local player = Player(playerGuid)
            if player then
                local itemToRemove = player:getItemById(config.ItemID, true)
                if itemToRemove then
                    itemToRemove:remove(1)
                end
                player:remove()
            end
        end, 3000) -- 3-second delay for kick and item removal
    end

    return true
end

local textEditEvent = CreatureEvent("TextEdit")
function textEditEvent.onTextEdit(player, item, text)
    return onTextEdit(player, item, text)
end
textEditEvent:register()

local testAction = Action()

function testAction.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    local tile = player:getTile()

    if not tile or not tile:hasFlag(TILESTATE_PROTECTIONZONE) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You can only use this item in a protection zone (PZ).")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    if player:getCondition(CONDITION_INFIGHT) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot use this item while in combat.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    local parent = item:getParent()
    if parent and parent:isContainer() and parent:getTopParent() == player then
        player:registerEvent("TextEdit")
        player:showTextDialog(1948, "Enter the desired name: ", true)
    else
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "The item must be in your backpack to be used.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
    end
    return true
end

testAction:id(config.ItemID)
testAction:register()


View attachment 87563
Wow this works perfectly! Even better than I imagined when trying to create it :O!!

Thanks you!
 
LUA:
    local query = "SELECT `name` FROM `players` WHERE `name` = " .. db.escapeString(name) .. ";"
    local result = db.storeQuery(query)

    if result then
        player:sendCancelMessage("This name already exists. Please choose another one.")
        return true
    end
IDK, if they changed something in new TFS, but in all old TFSes you had to remove SQL result from C++ manually.
If value returned by storeQuery is not false (it's number of result ID), you have to free result using result.free(resultId). Also your local result variable overwrites global result variable defined in C++.
Fixed code:
LUA:
    local query = "SELECT `name` FROM `players` WHERE `name` = " .. db.escapeString(name) .. ";"
-- rename to "resultId"
    local resultId = db.storeQuery(query)

    if resultId then
-- free SQL result
        result.free(resultId)
        player:sendCancelMessage("This name already exists. Please choose another one.")
        return true
    end

Also this part looks like a bug:
LUA:
        addEvent(function()
            local player = Player(playerGuid)
            if player then
                local itemToRemove = player:getItemById(config.ItemID, true)
                if itemToRemove then
                    itemToRemove:remove(1)
                end
                player:remove()
            end
        end, 3000) -- 3-second delay for kick and item removal
There is 3 seconds delay between name change and item remove, player can use item, type new name, throw away item on ground, get kick after 3 seconds and item won't be removed.
Item can be also put on ground, used by player and then it won't be remove too, as it won't be one of player items.
Script should remove item in moment, when it changes player name.
 
IDK, if they changed something in new TFS, but in all old TFSes you had to remove SQL result from C++ manually.
If value returned by storeQuery is not false (it's number of result ID), you have to free result using result.free(resultId). Also your local result variable overwrites global result variable defined in C++.
Fixed code:
I wasn't familiar with the concept of result.free(resultId) before, but now I understand thanks to your explanation. I didn't know that we need to manually release SQL results to avoid memory leaks, but I'm learning and improving. Thank you for helping me!

Also this part looks like a bug:
I noticed this bug before, but I didn’t feel like fixing it for almost two months. Now I’m back to working on it, and after your explanation, I had a great idea. For example, when a player clicks the item, throws it on the ground, and the name change window still appears, they could press OK, get kicked, but the item would stay on the ground. I’ve now fixed that issue.

I made an improvement: if a player clicks the item in their backpack, the name change window appears, but if they throw the item on the ground and try to confirm the name change, the system will check if the item is still in the backpack. If not, the process will be canceled. The item must remain in the backpack to successfully change the name.

Thanks again for your help! I’m learning new things about C++ and Lua thanks to you!

LUA:
 local itemToCheck = player:getItemById(config.ItemID, true)
    if not itemToCheck then
        player:sendCancelMessage("The item must remain in your backpack during this process. Name change canceled.")
        return true
    end


script FULL.

LUA:
local config = {
    ItemID = 1950, -- Defining the item ID
    forbiddenNames = {
        "CM", "GM", "GOD", "GAME MASTER", "GAMEMASTER", "HOSTER", "RACIST",
        "Tutor", "Admin", "Owner", "Developer", "Support", "Moderator"
    },
    length = {['min'] = 4, ['max'] = 20}, -- Defining the minimum and maximum name length allowed
}

local function isNameForbidden(name)
    for _, forbidden in ipairs(config.forbiddenNames) do
        if name:lower():find(forbidden:lower()) then
            return true
        end
    end
    return false
end

local function updatePlayerName(player, newName)
    local guid = player:getGuid()
    db.query("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. guid .. " LIMIT 1;")
end

local function isNameLengthValid(name)
    local length = name:len()
    return length >= config.length['min'] and length <= config.length['max']
end

local function onTextEdit(player, item, text)
    player:unregisterEvent("TextEdit")
    local name = text:match("Enter the desired name: (.+)")
    if not name or #name == 0 then
        return true
    end

    local itemToCheck = player:getItemById(config.ItemID, true)
    if not itemToCheck then
        player:sendCancelMessage("The item must remain in your backpack during this process. Name change canceled.")
        return true
    end

    if not isNameLengthValid(name) then
        player:sendCancelMessage("The name must be between " .. config.length['min'] .. " and " .. config.length['max'] .. " characters.")
        return true
    end

    local query = "SELECT `name` FROM `players` WHERE `name` = " .. db.escapeString(name) .. ";"
    local resultId = db.storeQuery(query)

    if resultId then
        result.free(resultId)
        player:sendCancelMessage("This name already exists. Please choose another one.")
        return true
    end

    if isNameForbidden(name) then
        player:sendCancelMessage("Sorry, this name is forbidden.")
        return true
    end

    updatePlayerName(player, name)
    player:sendTextMessage(MESSAGE_INFO_DESCR, "Your name has been changed to: " .. name .. ". You will be logged out in 3 seconds.")
    player:getPosition():sendMagicEffect(CONST_ME_HEARTS)

    local playerGuid = player:getGuid()

    if itemToCheck then
        itemToCheck:remove(1)
    end

    addEvent(function()
        local onlinePlayer = Player(playerGuid)
        if onlinePlayer then
            onlinePlayer:remove()
        end
    end, 3000)

    return true
end

local textEditEvent = CreatureEvent("TextEdit")
function textEditEvent.onTextEdit(player, item, text)
    return onTextEdit(player, item, text)
end
textEditEvent:register()

local ChangeName = Action()

function ChangeName.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    local tile = player:getTile()

    if not tile or not tile:hasFlag(TILESTATE_PROTECTIONZONE) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You can only use this item in a protection zone (PZ).")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    if player:getCondition(CONDITION_INFIGHT) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot use this item while in combat.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    local parent = item:getParent()
    if parent and parent:isContainer() and parent:getTopParent() == player then
        player:registerEvent("TextEdit")
        player:showTextDialog(1948, "Enter the desired name: ", true)
    else
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "The item must be in your backpack to be used.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
    end
    return true
end

ChangeName:id(config.ItemID)
ChangeName:register()
 
Good solution. There is still a problem when the player uses "Enter" to confirm instead of clicking "okay" after entering the nickname.
Which script are you using? onUse to click and open a small window (onTextEdit)? It's not a modal window; it's either onTextEdit or onSay via a command to change the name 🤔? I saw in another topic that you were asking for "onSay"
 
Which script are you using? onUse to click and open a small window (onTextEdit)? It's not a modal window; it's either onTextEdit or onSay via a command to change the name 🤔? I saw in another topic that you were asking for "onSay"
I used the previous one but it had the error of throwing it to the ground. I'm using this from the topic now.

If I enter a nickname and press "Enter" and then OK - it causes an error as in the picture.

Without "enter", just "okay" does not cause an error
Post automatically merged:

I tried to record a video and post it, but I can't post it :D I recorded it with Windows and posted it here (maybe it will help)

You can watch without downloading (look at the chat)
 
Last edited:
I tested several TFS versions from 1.3 to 1.7, including 8.6, 10.98, and up to 13.10, and I never had any issues. What happened to yours? View attachment 89073🤔
Write your nickname, press "enter" on the keyboard and then press "OK" :d Then write something in the chat. I wrote about it. Then this causes an error in displaying the nickname and you have to buy another change name scroll
 

1741637264534.webp

LUA:
local config = {
    ItemID = 1950, -- Defining the item ID
    forbiddenNames = {
        "CM", "GM", "GOD", "GAME MASTER", "GAMEMASTER", "HOSTER", "RACIST",
        "Tutor", "Admin", "Owner", "Developer", "Support", "Moderator"
    },
    length = {['min'] = 4, ['max'] = 20}, -- Defining the minimum and maximum name length allowed
}

local function isNameForbidden(name)
    for _, forbidden in ipairs(config.forbiddenNames) do
        if name:lower():find(forbidden:lower()) then
            return true
        end
    end
    return false
end

local function updatePlayerName(player, newName)
    local guid = player:getGuid()
    db.query("UPDATE `players` SET `name` = " .. db.escapeString(newName) .. " WHERE `id` = " .. guid .. " LIMIT 1;")
end

local function isNameLengthValid(name)
    local length = name:len()
    return length >= config.length['min'] and length <= config.length['max']
end

local function onTextEdit(player, item, text)
    player:unregisterEvent("TextEdit")
 
    -- Handle both enter key and OK button cases
    local name = text:match("Enter the desired name: (.+)")
 
    if not name then
        name = text  -- If pattern not found, use the whole text
    end
 
    -- Handle "It is empty" case
    if name == "It is empty." then
        player:sendCancelMessage("Name cannot be empty. Please try again.")
        return true
    end
 
    -- Additional validation for characters and format
    if name:find("^[a-zA-Z ]+$") == nil then
        --print("[NameChange] Invalid characters found in name")
        player:sendCancelMessage("The name can only contain letters and spaces.")
        return true
    end
 
    -- Check for proper capitalization of each word
    local function properCase(str)
        local result = str:gsub("(%a)([%w_']*)", function(first, rest)
            return first:upper() .. rest:lower()
        end)
        return result
    end
 
    -- Ensure proper formatting (each word capitalized, no double spaces)
    local originalName = name
    name = properCase(name:gsub("%s+", " "))
    --print("[NameChange] After formatting: '" .. name .. "' (original: '" .. originalName .. "')")
 
    if not name or #name == 0 then
        player:sendCancelMessage("Invalid name. Name change canceled.")
        return true
    end

    local itemToCheck = player:getItemById(config.ItemID, true)
    if not itemToCheck then
        player:sendCancelMessage("The item must remain in your backpack during this process. Name change canceled.")
        return true
    end

    if not isNameLengthValid(name) then
        player:sendCancelMessage("The name must be between " .. config.length['min'] .. " and " .. config.length['max'] .. " characters.")
        return true
    end

    local query = "SELECT `name` FROM `players` WHERE `name` = " .. db.escapeString(name) .. ";"
    local resultId = db.storeQuery(query)

    if resultId then
        result.free(resultId)
        player:sendCancelMessage("This name already exists. Please choose another one.")
        return true
    end

    if isNameForbidden(name) then
        player:sendCancelMessage("Sorry, this name is forbidden.")
        return true
    end

    updatePlayerName(player, name)
    player:sendTextMessage(MESSAGE_INFO_DESCR, "Your name has been changed to: " .. name .. ". You will be logged out in 3 seconds.")
    player:getPosition():sendMagicEffect(CONST_ME_HEARTS)

    local playerGuid = player:getGuid()

    if itemToCheck then
        itemToCheck:remove(1)
    end

    addEvent(function()
        local onlinePlayer = Player(playerGuid)
        if onlinePlayer then
            onlinePlayer:remove()
        end
    end, 3000)

    return true
end

local textEditEvent = CreatureEvent("TextEdit")
function textEditEvent.onTextEdit(player, item, text)
    return onTextEdit(player, item, text)
end
textEditEvent:register()

local ChangeName = Action()

function ChangeName.onUse(player, item, fromPosition, target, toPosition, isHotkey)
    local tile = player:getTile()

    if not tile or not tile:hasFlag(TILESTATE_PROTECTIONZONE) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You can only use this item in a protection zone (PZ).")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    if player:getCondition(CONDITION_INFIGHT) then
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "You cannot use this item while in combat.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
        return true
    end

    local parent = item:getParent()
    if parent and parent:isContainer() and parent:getTopParent() == player then
        player:registerEvent("TextEdit")
        player:showTextDialog(1948, "Enter the desired name: ", true)
    else
        player:sendTextMessage(MESSAGE_STATUS_SMALL, "The item must be in your backpack to be used.")
        player:getPosition():sendMagicEffect(CONST_ME_POFF)
    end
    return true
end

ChangeName:id(config.ItemID)
ChangeName:register()
 

Similar threads

Back
Top