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

Randomizing Container Items on Startup

Dries390

Well-Known Member
Joined
Sep 8, 2007
Messages
91
Solutions
4
Reaction score
70
Hello everyone. I present here some code to create a list of items with actionIds on startup which you might be able to use to make your server a little less static.

1587736636309.png



Requirements: TFS 1.3 and the ability to compile. I reckon you could make the modifications for other distributions if you use your head a little.

Disclaimer: I am not a c++ wizard and I had to drop a more elegant solution because I couldn't quite work out the details. You shouldn't have any problems as far as I can tell and if you use it as it was meant to be used.

item.h l. 1089 under void setUniqueId(uint16_t n);
C++:
void setSubType(uint16_t n);

void setUniqueId(uint16_t n);
void addActionList(uint16_t n); //EXTRA CODE

void setDefaultDuration() {

item.cpp l. 37 by externals
C++:
extern Game g_game;
extern Spells* g_spells;
extern Vocations g_vocations;
extern ScriptEnvironment l_envir; //EXTRA CODE

item.cpp l. 405 case ATTR_ACTION_ID under Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream)
C++:
Attr_ReadValue Item::readAttr(AttrTypes_t attr, PropStream& propStream)
{
    switch (attr) {
        case ATTR_COUNT:
        case ATTR_RUNE_CHARGES: {
            uint8_t count;
            if (!propStream.read<uint8_t>(count)) {
                return ATTR_READ_ERROR;
            }

            setSubType(count);
            break;
        }

        case ATTR_ACTION_ID: {
            uint16_t actionId;
            if (!propStream.read<uint16_t>(actionId)) {
                return ATTR_READ_ERROR;
            }

            setActionId(actionId);
            addActionList(actionId); //EXTRA CODE, adds AIDs to the multimap actionItems when parsing items
            break;
        }

item.cpp l. 1901
C++:
void Item::setUniqueId(uint16_t n)
{
    if (hasAttribute(ITEM_ATTRIBUTE_UNIQUEID)) {
        return;
    }

    if (g_game.addUniqueItem(n, this)) {
        getAttributes()->setUniqueId(n);
    }
}

void Item::addActionList(uint16_t n) //EXTRA FUNCTION
{
    if (n >= 100) {
        l_envir.addActionItem(n, this);
    }
}

luascript.h l. 53
C++:
extern std::multimap<uint16_t, Item*> actionItems; // EXTRA LINE
luascript.h l. 150
C++:
Thing* getThingByUID(uint32_t uid);
bool addActionItem(uint16_t actionId, Item* item); //EXTRA CODE
Item* getItemByUID(uint32_t uid);
Container* getContainerByUID(uint32_t uid);
void removeItemByUID(uint32_t uid);

luascript.cpp l. 53
C++:
LuaEnvironment g_luaEnvironment;

std::multimap<uint16_t, Item*> actionItems; //EXTRA CODE

l. 3966 under function int LuaScriptInterface::luaGameGetPlayers(lua_State* L)
C++:
int LuaScriptInterface::luaGameGetAid(lua_State* L) //EXTRA FUNCTION
{
    uint32_t id = getNumber<uint32_t>(L, 1);
    std::vector<Item*> vecAid;

    for (auto it = actionItems.begin(); it != actionItems.end(); ++it) {
        if (it->first == id) {
            vecAid.push_back(it->second);
        }
    }
    if (vecAid.size() == 0) {
        lua_pushnil(L);
        return 1;
    }
    lua_createtable(L, vecAid.size(), 0);
    int index = 0;
    for (Item* item : vecAid) {
        pushUserdata<Item>(L, item);
        setItemMetatable(L, -1, item);
        lua_rawseti(L, -2, ++index);
    }
    return 1;
}

This creates a function Game.getAid(x) which gets the metatable containing all items with actionId = x on startup. I'm unsure, and for the moment uninterested, in how this table behaves as items with actionIds get created and destroyed. I actually suspect things you assign an AID to might end up on the list but items that are destroyed are NOT taken off of it so I advise only using it in startup.lua.

As an example consider the following code in startup.lua

Lua:
local crateLoot = {{1293, 80}, {1294, 80}, {1295, 80}, {2148, 60, 1}} -- Add items to this list {X, Y, X} with X is itemId, Y is probability it will be added i.e. 80 = 80 % chance and X if stackable (this can be done more elegantly)
-- Populate Crates
    local crates = Game.getAid(15000) -- Get all items with AID 15000
    if crates then
        for i = 1, #crates do
            if crates[i]:isContainer() then -- Check if "crate" is actually a container
                local amountItems = math.random(3, 8) -- Add 3-8 items but it will be less than this because there's only a Y % chance they get added at all
                for j = 1, amountItems do
                    local randItemList = math.random(1, #crateLoot)
                    if math.random(1, 100) <= crateLoot[randItemList][2] then -- Roll to see if item is added
                        if crateLoot[randItemList][3] then -- If stackable
                            crates[i]:addItem(crateLoot[randItemList][1],2*math.floor(0.85+0.005*math.exp(6.6*math.random()))) -- random exponential function to add stackable items [1-4 likely, more unlikely]
                        else
                            crates[i]:addItem(crateLoot[randItemList][1]) -- only add one
                        end
                    end
                end
            end
        end
    end

Here's an example of loot added to six random crates on startup (I use a much longer lootlist on my server)
1587738707927.png

But it's nice if you add weapons, armor, gems, potions with a high rarity because it motivates players to explore their surroundings a bit more. Yes, it means you have to add all items manually to a table and yes, you have to mark each container with the right AID but once you get it going it's actually quite a neat little touch.
 
Last edited:
alternatively you can rewrite questsystem to generate crate loot onUse without source edits

how?
  • add uniqueid to crates
  • add them to actions.xml
  • use this solution for your script:
Lua:
-- reload won't reset crates this way
if not CRATES_LOOTED then
    CRATES_LOOTED = {}
end

function onUse(player, item, pos)
    local uid = item:getUniqueId() -- item.uid for older engines

    if not CRATES_LOOTED[uid] then
        CRATES_LOOTED[uid] = true
        -- generate loot
    end

    -- remember to return false in onUse so player can open the crate
    return false
end

(my old thread for reference, written slightly different, but still can be used as example to write it for 1.3: Action - [TFS 1.x] open world style chests (https://otland.net/threads/tfs-1-x-open-world-style-chests.243164/))

edit: if you use immovable container, you can use actionId and identify crates by pos instead, allowing you to paste unlimited amount of crates this way
 
Last edited:
Yes, this can easily be done. The crates would, however, be unmovable which just kind of kills it for me. I don't think anyone wants 500 chests scattered across the world that've been superglued to the floor. You'd also have to add a separate UID for every single chest which is quite possibly even more bothersome than my ~4 classes of AIDs for containers.
 
Back
Top