• 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!

Feature Monsters and NPC's that can only be seen by specific players

Itutorial

Excellent OT User
Joined
Dec 23, 2014
Messages
2,326
Solutions
68
Reaction score
999
This was created using Nekiros 1.5 8.6 downgrade. There shouldn't be any problems in adding it in any 1.5+.

This system will allow you to create monsters and npcs that only specific players can see.

Ex. During a quest a NPC comes and talks to a player that only he can see.
Ex. A quest monster spawns that only the player can see to kill.

You can also make it so all party members can see the monster aswell by just creating a loop to add each player to the seen list and the players id to the creatures seen list.

Inspired by WoW this can be used to create all types of cool things. In the future I plan to update this with a more advanced version that can have specific storage values that allow players to see it aswell.

Ex. A whole area is populated with specific monsters/npcs after any player has completed the correct quest.

You will be able to have completly different content for each unique player. I am surprised this wasn't created earlier.
When you want to create a creature make sure you add it to the players seen list and add the player to the creatures seen list.

Lua:
local creature = Game.createMonster(name, pos)
creature:addSeenByPlayer(playerid)
player:addSeenCreature(creatureid)

This code will automatically remove seen players from player list whenever a creature is removed/killed.

Available Methods
Code:
creature:addSeenByPlayer(player or id)
creature:removeSeenByPlayer(player or id)
creature:isSeenByPlayer(player or id)
creature:getSeenByList()

player:addSeenCreature(creature or id)
player:removeSeenCreature(creature or id)
player:getSeenCreatures()
player:hasSeenCreature(creature or id)

In creature.h

UNDER
C++:
void decrementReferenceCounter() {
            if (--referenceCounter == 0) {
                delete this;
            }
        }

ADD
C++:
std::list<uint32_t> getSeenByList() {
            return seenByList;
        }

        bool isInSeenList(uint32_t id) {
            auto it = std::find(seenByList.begin(), seenByList.end(), id);
            if (it != seenByList.end()) {
                return true;
            }
            return false;
        }

        void addSeenByPlayer(uint32_t id) {
            seenByList.push_back(id);
        }

        void removeSeenByPlayer(uint32_t id) {
            seenByList.remove(id);
        }

UNDER
C++:
std::list<Creature*> summons;

ADD
C++:
std::list<uint32_t> seenByList;

in monster.cpp

FIND
C++:
void Monster::addTarget(Creature* creature, bool pushFront/* = false*/)

UNDER
assert(creature != this);

ADD
C++:
if (creature && creature->getPlayer()) {
        std::list<uint32_t> list = getSeenByList();
        if (list.size() != 0) {
            if (!isInSeenList(creature->getID())) {
                return;
            }
        }
    }

in player.h

FIND
C++:
void switchGhostMode() {
            ghostMode = !ghostMode;
        }

ADD
C++:
bool hasSeenCreature(uint32_t id) const;

        void addSeenCreature(uint32_t id) {
            seenCreatures.push_back(id);
        }

        void removeSeenCreature(uint32_t id) {
            seenCreatures.remove(id);
        }

        std::list<uint32_t> getSeenCreatures() {
            return seenCreatures;
        }

FIND
C++:
std::list<ShopInfo> shopItemList;

ADD
C++:
std::list<uint32_t> seenCreatures;

IN player.cpp

FIND
C++:
if (!creature->getPlayer() && !canSeeInvisibility() && creature->isInvisible()) {
        return false;
    }

ADD
C++:
if (creature->getSeenByList().size() != 0 && !hasSeenCreature(creature->getID())) {
        return false;
    }

FIND
C++:
bool Player::canSeeGhostMode(const Creature*) const
{
    return group->access;
}

UNDER ADD
C++:
bool Player::hasSeenCreature(uint32_t id) const
{
    auto it = std::find(seenCreatures.begin(), seenCreatures.end(), id);
    if (it != seenCreatures.end()) {
        return true;
    }

    return false;
}

IN game.cpp

FIND
C++:
for (Creature* spectator : spectators) {
        if (Player* player = spectator->getPlayer()) {
            if (player->canSeeCreature(creature)) {
                player->sendRemoveTileCreature(creature, tilePosition, oldStackPosVector[i++]);
            }
        }
    }

ADD
C++:
// Remove players seen list
    std::list<uint32_t> list = creature->getSeenByList();
    if (list.size() != 0) {
        for (std::list<uint32_t>::iterator it = list.begin(); it != list.end(); it++) {
            Player* tmpPlayer = g_game.getPlayerByID(*it);
            if (tmpPlayer) {
                tmpPlayer->removeSeenCreature(creature->getID());
            }
        }
    }

IN luascript.h

FIND
C++:
static int luaCreatureCanSeeCreature(lua_State* L);

ADD
C++:
static int luaCreatureAddSeenByPlayer(lua_State* L);
static int luaCreatureRemoveSeenByPlayer(lua_State* L);
static int luaCreatureIsSeenByPlayer(lua_State* L);
static int luaCreatureGetSeenByList(lua_State* L);

FIND
C++:
static int luaPlayerSetStorageValue(lua_State* L);

ADD
C++:
static int luaPlayerAddSeenCreature(lua_State* L);
static int luaPlayerRemoveSeenCreature(lua_State* L);
static int luaPlayerHasSeenCreature(lua_State* L);
static int luaPlayerGetSeenCreatures(lua_State* L);

IN luascript.cpp

FIND
C++:
int LuaScriptInterface::luaCreatureCanSeeCreature(lua_State* L)

UNDER WHOLE METHOD ADD
C++:
int LuaScriptInterface::luaCreatureAddSeenByPlayer(lua_State* L)
{
    // creature:addSeenByPlayer(id)
    Creature* creature = getUserdata<Creature>(L, 1);
    if (!creature) {
        lua_pushnil(L);
        return 1;
    }

    Player* player = getUserdata<Player>(L, 2);
    if (player && !creature->isInSeenList(player->getID())) {
        creature->addSeenByPlayer(player->getID());
        pushBoolean(L, true);
        return 1;
    }

    uint32_t id = getNumber<uint32_t>(L, 2);
    if (id && !creature->isInSeenList(id)) {
        creature->addSeenByPlayer(id);
        pushBoolean(L, true);
        return 1;
    }
    pushBoolean(L, false);
    return 1;
}

int LuaScriptInterface::luaCreatureRemoveSeenByPlayer(lua_State* L)
{
    // creature:removeSeenByPlayer(player or id)
    Creature* creature = getUserdata<Creature>(L, 1);
    if (!creature) {
        lua_pushnil(L);
        return 1;
    }

    Player* player = getUserdata<Player>(L, 2);
    if (player && creature->isInSeenList(player->getID())) {
        creature->removeSeenByPlayer(player->getID());
        pushBoolean(L, true);
        return 1;
    }

    uint32_t id = getNumber<uint32_t>(L, 2);
    if (id && creature->isInSeenList(id)) {
        creature->removeSeenByPlayer(id);
        pushBoolean(L, true);
        return 1;
    }
    pushBoolean(L, false);
    return 1;
}

int LuaScriptInterface::luaCreatureIsSeenByPlayer(lua_State* L)
{
    // creature:isSeenByPlayer(player or id)
    Creature* creature = getUserdata<Creature>(L, 1);
    if (!creature) {
        lua_pushnil(L);
        return 1;
    }

    uint32_t newID = 0;
    Player* player = getUserdata<Player>(L, 2);
    if (player) {
        newID = player->getID();
    }
    else {
        newID = getNumber<uint32_t>(L, 2);
    }

    if (newID != 0) {
        std::list<uint32_t>::iterator it;
        std::list<uint32_t> newList = creature->getSeenByList();

        for (it = newList.begin(); it != newList.end(); it++) {
            if (newID == *it) {
                pushBoolean(L, true);
                return 1;
            }
        }
    }
    pushBoolean(L, false);
    return 1;
}

int LuaScriptInterface::luaCreatureGetSeenByList(lua_State* L)
{
    // creature:getSeenByList()
    Creature* creature = getUserdata<Creature>(L, 1);
    if (!creature) {
        lua_pushnil(L);
        return 1;
    }

    std::list<uint32_t> newList = creature->getSeenByList();

    lua_createtable(L, newList.size(), 0);

    std::list<uint32_t>::iterator it;
    int index = 0;
    for (it = newList.begin(); it != newList.end(); it++) {
        lua_pushnumber(L, *it);
        lua_rawseti(L, -2, ++index);
    }
    return 1;
}

FIND
C++:
int LuaScriptInterface::luaPlayerGetStorageValue(lua_State* L)

UNDER WHOLE METHOD ADD
C++:
int LuaScriptInterface::luaPlayerAddSeenCreature(lua_State* L)
{
    // player:addSeenCreature(creature or id)
    Player* player = getUserdata<Player>(L, 1);
    if (!player) {
        lua_pushnil(L);
        return 1;
    }

    const Creature* creature = getUserdata<Creature>(L, 2);
    if (creature) {
        if (!player->hasSeenCreature(creature->getID())) {
            player->addSeenCreature(creature->getID());
            pushBoolean(L, true);
            return 1;
        }
    }

    uint32_t id = getNumber<uint32_t>(L, 2);
    if (id && !player->hasSeenCreature(id)) {
        player->addSeenCreature(id);
        pushBoolean(L, true);
        return 1;
    }
    pushBoolean(L, false);
    return 1;
}

int LuaScriptInterface::luaPlayerRemoveSeenCreature(lua_State* L)
{
    // player:removeSeenCreature(creature or id)
    Player* player = getUserdata<Player>(L, 1);
    if (!player) {
        lua_pushnil(L);
        return 1;
    }

    const Creature* creature = getUserdata<Creature>(L, 2);
    if (creature) {
        if (player->hasSeenCreature(creature->getID())) {
            player->removeSeenCreature(creature->getID());
            pushBoolean(L, true);
            return 1;
        }
    }

    uint32_t id = getNumber<uint32_t>(L, 2);
    if (id && player->hasSeenCreature(id)) {
        player->removeSeenCreature(id);
        pushBoolean(L, true);
        return 1;
    }
    pushBoolean(L, false);
    return 1;
}

int LuaScriptInterface::luaPlayerGetSeenCreatures(lua_State* L)
{
    // player:getSeenCreatures()
    Player* player = getUserdata<Player>(L, 1);
    if (!player) {
        lua_pushnil(L);
        return 1;
    }

    std::list<uint32_t> newList = player->getSeenCreatures();
    if (newList.size() == 0) {
        lua_pushnil(L);
        return 1;
    }

    lua_createtable(L, newList.size(), 0);

    std::list<uint32_t>::iterator it;
    int index = 0;
    for (it = newList.begin(); it != newList.end(); it++) {
        lua_pushnumber(L, *it);
        lua_rawseti(L, -2, ++index);
    }
    return 1;
}

int LuaScriptInterface::luaPlayerHasSeenCreature(lua_State* L)
{
    // player:hasSeenCreature(creature or id)
    Player* player = getUserdata<Player>(L, 1);
    if (!player) {
        lua_pushnil(L);
        return 1;
    }

    const Creature* creature = getUserdata<Creature>(L, 2);
    if (creature) {
        if (player->hasSeenCreature(creature->getID())) {
            pushBoolean(L, true);
            return 1;
        }
    }

    uint32_t id = getNumber<uint32_t>(L, 2);
    if (id && player->hasSeenCreature(id)) {
        pushBoolean(L, true);
        return 1;
    }

    pushBoolean(L, false);
    return 1;
}
Post automatically merged:

Here is an example talkaction code I created.

Lua:
local createPos = Position(19981, 19998, 7)

local TalkCreateSpecialNpc = TalkAction("/spec")
function TalkCreateSpecialNpc.onSay(player, words, param)
    if not player:getGroup():getAccess() or player:getAccountType() < ACCOUNT_TYPE_GOD then
        return false
    end
 
    if param == "" then
        return false
    end
 
    local t = param:split(",")
    local name = t[1]
    local playerName = t[2]
 
    if not t[1] or not t[2] then return false end
 
    local target = Player(playerName)
 
    if not target then
        player:sendCancelMessage("Target player is not online.")
        return false
    end
 
    local creature = Game.createMonster(name, createPos, 0, 1)
 
    if not creature then
        creature = Game.createNpc(name, createPos, 0, 1)
    end
 
    if not creature then
        player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, "Failed to create special creature.")
        return false
    end
 
    creature:addSeenByPlayer(target:getId())
    target:addSeenCreature(creature:getId())
 
    local list = creature:getSeenByList()
    if list and #list > 0 then
        for i = 1, #list do
            print(list[i])
        end
    else
        print("FAILED TO GET LIST")
        creature:remove();
        return false
    end
 
    local pPos = target:getPosition()
    creature:teleportTo(Position(pPos.x, pPos.y - 1, pPos.z))
    player:sendTextMessage(MESSAGE_STATUS_CONSOLE_RED, "Special creature created.")
    return false
end
TalkCreateSpecialNpc:separator(" ")
TalkCreateSpecialNpc:register()

If you see anything that can be fixed/changed or named different let me know. I am working on the more advanced system now. Please share anything you think could make this better as it is now. I know it has flaws and will be working on fixing them.

For now the npc/monster must be created outside any players screens then teleported back to them after lists are set. That is the first thing im working on.
 
Last edited:
Great system 👏 the quest possibilities with this are endless!

An extra feature of this system could be to allow a dungeon of creatures to update based on player level.

Example:
Player clears cave at level 30 -> faces swamp trolls.
Player comes back at level 60 -> faces dwarf guards.

This could allow repayablity of certain areas, mixed together with changed npcs; when the player returns there is a new npc that could tell a story of exploring the cave and dwarf guards killing the swamp trolls and taking over the area and he's been hiding there ever since. With both systems playing hand in hand to help create a new quest + experience in an area they've already explored.
 
Great system 👏 the quest possibilities with this are endless!

An extra feature of this system could be to allow a dungeon of creatures to update based on player level.

Example:
Player clears cave at level 30 -> faces swamp trolls.
Player comes back at level 60 -> faces dwarf guards.

This could allow repayablity of certain areas, mixed together with changed npcs; when the player returns there is a new npc that could tell a story of exploring the cave and dwarf guards killing the swamp trolls and taking over the area and he's been hiding there ever since. With both systems playing hand in hand to help create a new quest + experience in an area they've already explored.
You could do that easily through lua.
 
Just a tip, use map or set, so its way faster to find and remove creatures. Also getSeenCreatures is copying, return const reference if copying was not intended (and probably wasnt)
 
Just a tip, use map or set, so its way faster to find and remove creatures. Also getSeenCreatures is copying, return const reference if copying was not intended (and probably wasnt)
Does it matter that much if there isn't that many ids? I would think that only really matters if there is a lot. I will definitely change it as I want this code to be 100% I just am not the best in the world with C++. I do know somethings are more efficient just not which ones they all are :p.

I see it uses
C++:
std::list<Creature*> summons;

Why would list be used there and not in this instance?

@Nekiro @Sarah Wesker if you could help me with my question in the support board I would be really close to having this system made a lot more effectively and appreciate it greatly :D

Also with the copying I do want it to copy the list instead of directly using it and const was required because of something with const Creature. I was getting an error without it. Maybe you can inform me on something. Would using const_cast be a possibility or even matter? Also, does creating a copy to work from matter at all? I figured not working directly on a private member would matter for reason.

C++:
std::list<uint32_t> list = getSeenByList();
        if (list.size() != 0) {
            if (!isInSeenList(creature->getID())) {
                return;
            }
        }

// or
if (creature.seenList.size() != 0) {
            if (!isInSeenList(creature->getID())) {
                return;
            }
}

Is that what you mean?
 
Last edited:
I think that's not all, but you could start by changing this:

const Unnecessary, although I don't think it affects anything. maybe?
1657610714233.png
C++:
std::list<uint32_t> getSeenByList() {
    return seenByList;
}

1657610807546.png
If you do it this way then you are not copying:
C++:
const std::list<uint32_t>& list = getSeenByList();
and @Nekiro says that using maps is better for adding and removing creatures.
 
honestly the easiest way to do this is doing it farmine way

three different instances of location depending on quest progression
Then you would have areas where a lot of players are by themselves because they are in completely different places. Is that what you meant? Copying map areas and just send them to different ones? It would be a lot easier but I still want to try to complete this system.
Post automatically merged:

I think that's not all, but you could start by changing this:

const Unnecessary, although I don't think it affects anything. maybe?
View attachment 69213
C++:
std::list<uint32_t> getSeenByList() {
    return seenByList;
}

View attachment 69214
If you do it this way then you are not copying:
C++:
const std::list<uint32_t>& list = getSeenByList();
and @Nekiro says that using maps is better for adding and removing creatures.
It doesn't add all of the creatures data at any point. It just adds the id of the creature.

I also want to say that you cannot have a creature saved on the map that will store these values. As of right now this can only be used to bring an NPC/monster into the game and gets removed before the server restarts. It is possible to use storage values right now to implement something like that.
 
Last edited:
Nice thread!

This remembered an idea I had some years ago... how difficult would be to develop a similar system, but instead of NPC, it uses messages?

Example: if an annoying person is advertising his/her otserver address in my server, script checks whether his message contains any string from an array (could be an array of ".servegame.com"; "no-ip) or if contains any IP address (using regex / pattern finding). If yes, the message is sent, but only the sender can see.

This way, the advertiser stays there forever thinking that everybody in the channel (or game window) is seeing the message. And by that, the person doesn't leave the server, and count as +1 player for a long time ;)
 
Back
Top