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

[TFS 1.4] Global Monster Limit

Codinablack

Dreamer
Content Editor
Joined
Dec 26, 2013
Messages
1,635
Solutions
11
Reaction score
878
Ok so I was tinkering around with ideas to take away the edge given to cavebotters over players who play. One idea came that came about was a limit on killing monsters instead of the broken stamina system.

I asked for some ideas on which way would be best for optimization here and ultimately stole a bit of code and help from @Roddet and made it happen.

It's small.
It's simple.
It has low footprint.
It has been tested during development.
It builds itself, and resets when the server starts up.
It prevents monsters from dying to a player who has reached the limit.
It works on each monster individually, meaning a limit of 10 doesn't mean 10 monsters total, means 10 of each monster.

It's one file and free for you to enjoy
Lua:
MonsterLimits = {}
Global_Monster_Limit = 3

local startup = CreatureEvent("MonsterLimitStartup")

function startup.onLogin(player)
    local guid = player:getGuid()
    if not MonsterLimits[guid] then
        MonsterLimits[guid] = {}
        player:registerEvent("MonsterKillCounter")
        return true
    end
end

startup:register()

local monsterSpawn = EventCallback

monsterSpawn.onSpawn = function(self, position, startup, artificial)
	self:registerEvent("MonsterDeathPrevention")
	return true
end

monsterSpawn:register()

local killCounter = CreatureEvent("MonsterKillCounter")

function killCounter.onKill(player, target)
	if target:isPlayer() then
		return true
	end
    local counter = MonsterLimits[player:getGuid()][target:getName():lower()]
    counter = (counter or 0) + 1
    MonsterLimits[player:getGuid()][target:getName():lower()] = counter
    if counter == Global_Monster_Limit then
    	player:sendTextMessage(MESSAGE_INFO_DESCR,"You have reached daily limit for " ..target:getName().. "s.")
    end
end

killCounter:register()


local monsterDeathPrevention = CreatureEvent("MonsterDeathPrevention")

function monsterDeathPrevention.onHealthChange(monster, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
	if not attacker:isPlayer() then
		return primaryDamage, primaryType, secondaryDamage, secondaryType
	end
	local count = MonsterLimits[attacker:getGuid()][monster:getName():lower()]
	count = (count or 0) --- this line is important, otherwise can not hurt any creature.
	if count >= Global_Monster_Limit then
		primaryDamage = 0
		secondaryDamage = 0
		monster:addHealth(monster:getMaxHealth())
	end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

monsterDeathPrevention:register()

It's another {SYC} series, so if you want to enhance it in any kind of way, please share here.

I'm sorry I forgot the title prefix. works for any version with eventcallbacks
 
Last edited:
It was brought to my attention, that I should mention.

The kill count will not reset on its own if you do not shut the server down!

THAT MEANS YOU MUST SHUTDOWN AND RESTART YOU SERVER FOR PLAYERS TO START BACK AT 0 KILLS , I SUGGEST YOU DO THIS DAILY


oh and also, there is a small abuse risk, players could potentially walk around and heal monsters that other players are attacking if they have already reached their limit for that monster, to remove that risk, remove line 54
 
At first, I thought this was a bad idea. But once I re-read it, it's not a bad solution.

Normal players will just switch spawns to a different monster once they've reached the limit, whereas the botter will be forced to switch scripts. Although it's not a concrete system, it means they cant hunt for 12 hours straight at the same spawn while they are asleep, they are also forced to switch. Also, if they do stay there, they will just waste supplies while gaining 0 exp.

As you said, there are some risks of abuse, but with some thought there probably is a solution for that.
 
Isn't it be better to use onCombat to prevent hitting monsters than onHealthChange?

And shutdown the server is not comfortable way to reset "kills' counter", wouldn't it be better to make this using exhaustion?
 
Counts reset per 3-6 hours
If you count monsters killed after the limit could ban the player after x kills
Mixed with the stamina system a bot could get around 150-400 kills max before a 24 hour rest.
This could be a good solution for medium-higher rate servers.
 
You could maybe make it so that when the bot/player reach the well-defined-and-public limit, it can still kill the creature and loot it, but it yields zero experience.

But here is the catch! If the player reach a magical limit above the first limit , he is banned for some time automatically.

A normal player would, like someone said, just go somewhere else. But the bot would keep on working to the magic limit and get himself banned.
 
Normal players will just switch spawns to a different monster once they've reached the limit, whereas the botter will be forced to switch scripts. Although it's not a concrete system, it means they cant hunt for 12 hours straight at the same spawn while they are asleep, they are also forced to switch. Also, if they do stay there, they will just waste supplies while gaining 0 exp.
First person to truly see the same things I see! You are exactly right in all that you said, and no this one thing alone won't stop botters or keep them from being useful, but its a damned good start, I have some other ideas I'm working on as well. Love that you realized that they will just waste supplies and get 0 exp, fuck those botters!

Isn't it be better to use onCombat to prevent hitting monsters than onHealthChange?
You are probably right, it might be better to use onCombat, and onAreaCombat, would need both to make sure they don't cheat using firefields or gfbs or something, but I just went with onHealthChange to be positive they weren't going to get the kills

And shutdown the server is not comfortable way to reset "kills' counter", wouldn't it be better to make this using exhaustion?
It is very easy to make a way to clear the kills counter, its just a table, set it to nil, can use talkaction, global time, quest, ect, that part is up to you!

Counts reset per 3-6 hours
If you count monsters killed after the limit could ban the player after x kills
Mixed with the stamina system a bot could get around 150-400 kills max before a 24 hour rest.
This could be a good solution for medium-higher rate servers.
Feels like I gave you some inspiration! Love it!

it can still kill the creature and loot it, but it yields zero experience.
I don't want them to get loot past a certain point either, no loot, no exp, no gains, waste of time.

If the player reach a magical limit above the first limit , he is banned for some time automatically.
Not a bad idea, but how about if he triggers the onhealthchange call on the same monster 100 times, after he clearly can't kill them, then there is automatic ban? Or sends the NPC police, or maybe he only makes it in a log book to be reviewed by gm... all ways could help to stamp out the purpose of the cavebot
Post automatically merged:

So our local buddy @Znote made a few alterations to this just to keep it from being invoked multiple times or possibly incorrectly, since he shared his knowledge and changes with me, I share with you


Lua:
MonsterLimits = {}
Global_Monster_Limit = 3

local startup = CreatureEvent("MonsterLimitStartup")

function startup.onLogin(player)
    local guid = player:getGuid()
    if not MonsterLimits[guid] then
        MonsterLimits[guid] = {}
        player:registerEvent("MonsterKillCounter")
        return true
    end
end

startup:register()

local monsterSpawn = EventCallback

monsterSpawn.onSpawn = function(self, position, startup, artificial)
    self:registerEvent("MonsterDeathPrevention")
    return true
end

monsterSpawn:register()

local killCounter = CreatureEvent("MonsterKillCounter")

function killCounter.onKill(player, target)
    if target:isPlayer() then
        return true
    end
    local targetName = target:getName()
    local playerGuid = player:getGuid()
    local counter = MonsterLimits[playerGuid][targetName:lower()]
    counter = (counter or 0) + 1
    MonsterLimits[playerGuid][targetName:lower()] = counter
    if counter == Global_Monster_Limit then
        player:sendTextMessage(MESSAGE_INFO_DESCR,"You have reached daily limit for " ..targetName.. "s.")
    end
end

killCounter:register()


local monsterDeathPrevention = CreatureEvent("MonsterDeathPrevention")

function monsterDeathPrevention.onHealthChange(monster, attacker, primaryDamage, primaryType, secondaryDamage, secondaryType, origin)
    if not attacker:isPlayer() then
        return primaryDamage, primaryType, secondaryDamage, secondaryType
    end
    local count = MonsterLimits[attacker:getGuid()][monster:getName():lower()]
    count = (count or 0) --- this line is important, otherwise can not hurt any creature.
    if count >= Global_Monster_Limit then
        primaryDamage = 0
        secondaryDamage = 0
        monster:addHealth(monster:getMaxHealth())
    end
    return primaryDamage, primaryType, secondaryDamage, secondaryType
end

monsterDeathPrevention:register()
 
Last edited:
But here is the catch! If the player reach a magical limit above the first limit , he is banned for some time automatically
I just had another idea, instead of secondary hidden limit, just count the damage, and after so much continued damage, the player gets a skull, first a white skull, then again a red skull after another obscene amount of damage (on monsters that are obviously not going to die), then if they get caught, they can be killed without consequences and drop everything :D
 
You could do it so if the player goes over the exceeded amount, they get a temporary red skull for as along as they have pz, and broadcast a global message that they are a potential botter
Hell yes, that's what I'm talking about! These things take away the edge a botter gets by sleep hunting all night, and punishes them for trying to have that edge in the first place :D

or makes it more difficult to do so..

we could even make it so that the player still damages the monster but heals it first, and the damage is minor, which would just further complicate it for the script writer of the bot
 
Wouldnt better to only block onGainExp and onDropLoot players with exceeded limit? It would look way clean
onDropLoot is very nice , and even destroying corpses after they dropped empy would be nice. Look much better, it is more work, but would be harder for the bot to noticed something is off perhaps, but also would take away the chance of the monsters killing the player who didn't have such advanced script.

Doing that, would mean you would definitely need to use onGainExp now too, which is a bit more work as well, but again all that would make it look nicer, and harder to detect, but now you could also just keep counting the kills now instead of damage, which is easier for adding those other features @Boy67 and I spoke about above, with skulls. Definitely very doable, and worth doing in my opinion.
 
onDropLoot is very nice , and even destroying corpses after they dropped empy would be nice. Look much better, it is more work, but would be harder for the bot to noticed something is off perhaps, but also would take away the chance of the monsters killing the player who didn't have such advanced script.

Doing that, would mean you would definitely need to use onGainExp now too, which is a bit more work as well, but again all that would make it look nicer, and harder to detect, but now you could also just keep counting the kills now instead of damage, which is easier for adding those other features @Boy67 and I spoke about above, with skulls. Definitely very doable, and worth doing in my opinion.

It actually would need simple checks on gain & droploot, now by adding red skull and unkillable monster imagine a player being hunting on a stronger monster and he is on the last room/final of the spot, once he reached the monster limit there is 90% chance that player will die anyway and that may be unfair (specially if wasnt botting at all), not to mention player will drop all items due red skull

You have to consider all case scenarios, just my opinion tho 😅
 
would need simple checks on gain & droploot
yes, which would require more work, as you would need to delete the onHealthChange, and add those two events

adding red skull and unkillable monster
the idea with the redskull would go better with killable monsters but utilizing the other two events instead like you said, very much so, and then if they are killable wouldn't have to worry about the player who just hunted too long and got to the end to die from unkillable monsters. Much better implementation than the current one on this thread, I agree. But also the limit is configurable, so it shouldn't be a problem that someone has a red skull just by going thru on a quest, if so, they should increase the limit :D

Marvelous idea, and then add in the other idea about pz, so skull doesn't last too long if the player figures out that killing the monsters is giving them a skull and they chill until skull goes away D: I think this system could really evolve into something great
 
Last edited:
the idea with the redskull would go better with killable monsters
Oh okey, yes alot better
Still there are many unexpected enviroments such as player getting kill on his way to temple/depot, getting kill for monsters itself, internet down, being hunting by enemies (waiting the player to get red skull on limit) etc

I dont know if its already on your plans but would be a good idea do add some margin on red skull, like
Lua:
if counter == (Global_Monster_Limit + 25) then
    setRedSkull
end
That wont work but you get the idea
 
Well I hadn't planned on making this any further than what it is, but I'm kinda feeling inspired to

Yeah the idea was to do x amount past the kill limit for exp and loot, you get white skull, then x amount of kills past the white skill limit, you get redskull, which would take care of most of the issues of an actual player getting an unfair redskull
Post automatically merged:

I remember the issue I had with the dropLoot, there is no way that I know of to tell if the monster who is dropping the loot was killed by a player with the limit reached, as it tells you the monster, but comes after death, also its not a specific monster, its eventcallback for all monsters, so you might be able to get the correct monsterType, but then how do you know if that monster was killed by this player?
Post automatically merged:

nvm, I could get the corpse owner
 
Last edited:
Here is a newer / altered version

  • no longer prevents kill of monster
  • now prevents corpse on death of overlimit monster
  • sets whiteskull for x amount of kills OVER the limit
  • sets redskull for x amount of kills OVER (whiteskull + limit)
  • no experience gained for kills OVER the limit
  • configuration for whiteskull time, and redskull time
  • Can add monsters manual limit in table EnforcedLimits, index by monster name, with value being monsters limit.


Lua:
local Kill_Counter = {}
local EnforcedLimit = {
    ["Orc"] = 5, -- example of how to add manual kill limits by monster name
}
local DailyLimit = 3
local WhiteSkull = 2 -- how many kills passed the limit gives whiteskull
local RedSkull = 3 -- how many kills passed the limit gives redskull
local WhiteSkullTime = 10 * 60 * 1000
local RedSkullTime = 60 * 60 * 1000


---/// Functions \\\---
-----------------------
-- Returns 0 if never killed before, else returns current kills
local function getKillCount(player, target)
    local counter = Kill_Counter[player:getGuid()][target:getName():lower()]
    counter = counter or 0
    return counter
end
-- Sets kills, and returns same value
local function setKillCount(player, target, kills)
    Kill_Counter[player:getGuid()][target:getName():lower()] = kills
    return kills
end

function Monster:getKillLimit()
    return EnforcedLimit[self:getName()] or DailyLimit
end

---/// Events \\\---
--------------------
local startup = CreatureEvent("Start_Kill_Counter")

function startup.onLogin(player)
    local guid = player:getGuid()
    if not Kill_Counter[guid] then
        Kill_Counter[guid] = {}
        player:registerEvent("Kill_Counter")
        return true
    end
end

startup:register()

local killCounter = CreatureEvent("Kill_Counter")

function killCounter.onKill(player, target)
    if not target:isMonster() then
        return
    end

    local kills = setKillCount(player, target, getKillCount(player, target) + 1)
    local limit = EnforcedLimit[target:getName()] or DailyLimit
    if kills < limit then
        return
    end
    target:setDropLoot(false)

    if kills >= (limit + WhiteSkull) and kills < (limit + WhiteSkull + RedSkull) then
        player:setSkull(SKULL_WHITE)
        player:setSkullTime(WhiteSkullTime)
        return
    end

    if kills >= (limit + WhiteSkull + RedSkull) then
        player:setSkull(SKULL_RED)
        player:setSkullTime(RedSkullTime)
        return
    end
end

killCounter:register()

local haltExp = EventCallback

haltExp.onGainExperience = function(self, source, exp, rawExp)
    if not source:isMonster() then
        return exp
    end
    if getKillCount(self, source) and getKillCount(self, source) > source:getKillLimit() then
        exp = 0
    end
    return exp
end

haltExp:register()
 
Last edited:
It's nice to see someone taking this stance against botters rather than the usual very stupid systems. However as per my thread I do have some things to point out that i think are important from my point of view as a botter (and developer that wants to also fix the botting problem).

This punishes players who want to play for extended periods of time and forces them to play a certain way.
  • Hunting for rare items and loot in general would be made much harder
  • If its an oldschool server then lootbags may not be possible
  • Large team hunts may be harder to pull off due to one or more members not having any kills left because they decided to play solo in the same day

Easily circumventable.
  • Bots can switch spawns easily
  • Bots can detect kills and switch
  • Bots can detect skull (if not kills) then switch
  • Botters, instead of switching spawn could run a chain of bots instead logging 1 after the other in an infinite loop each time one character reaches a limit, this is how botters circumvented stamina

Very hard to balance and make fair without punishing legitimate players
  • A monster configured for high level players could be botted by wealthy low level players, here the high level players are punished because these spawns are botted
  • A monster configured for low level characters could not effectively be hunted by legitimate higher level players, here the high level players are punished again because they can't effectively hunt these spawns
  • Effectively disables other hunting areas, if someone wants to gfb tombs he then can't go and hunt demon skeletons or ghouls for example, despite it being a different spawn.
Thinking along the lines of tombs/banuta where 1000s of kills per session is doable through a wide range of player levels with a wide range of monsters we can quickly see how hard it would be to try and balance



Just to be clear, I love this system it's a clear step in the right direction of dealing with botters in ways that can be fine tuned to a server without compromising legitimate players too much. Many of my points can be fixed with the right config and maybe some additional systems too.
I don't think it would fit a typical rlmap server however it would probably work quite well and be interesting to see on some custom ots especially if this system is considered during it's development.
 
Back
Top