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

Spell [TFS 1.X] Shield Bash

Leo32

Getting back into it...
Joined
Sep 21, 2007
Messages
987
Solutions
14
Reaction score
532
This is a spell knights can use to stun.
It's basically a Leona Q, it's damage is similar to a brutal strike, with the damage scaling off of the player's SKILL_SHIELD.
Because of the power of a stun, its duration does not increase - I have it at 3 seconds by default which halves to 1.5 on players.

vwS0Nj2.gif


spells.xml
XML:
<instant group="attack" name="Shield Bash" words="shield bash" lvl="15" mana="80" prem="1" aggressive="1" needtarget="1" range="1" exhaustion="10000" groupcooldown="2000" script="custom/shield bash.lua">
    <vocation name="..." />
</instant>

custom/shield bash.lua
Lua:
local checkWeaponSlots = {
    CONST_SLOT_LEFT,
    CONST_SLOT_RIGHT
}

local combat = Combat()
    combat:setParameter(COMBAT_PARAM_TYPE, COMBAT_PHYSICALDAMAGE)
    combat:setParameter(COMBAT_PARAM_BLOCKARMOR, 1)
    combat:setParameter(COMBAT_PARAM_EFFECT, CONST_ME_BLOCKHIT)

function stunAnimation(stunnedcreature, stunnedpos, counter)
    if counter ~= 0 and Creature(stunnedcreature) then
        stunnedpos:sendMagicEffect(CONST_ME_STUN)
        counter = counter - 1
        addEvent(stunAnimation, 500, stunnedcreature, stunnedpos, counter)
    end
    return true
end

function onCastSpell(creature, var)

    local stunDuration = 3000
    local shield = 0

    -- Check for shield
    for i = 1,#checkWeaponSlots do -- Check what weapon is being used
        if creature:getSlotItem(checkWeaponSlots[i]) ~= nil then
            local weaponLiteral = creature:getSlotItem(checkWeaponSlots[i]) -- weapon object
            local itemType = ItemType(weaponLiteral:getId()) -- itemtype object
            local weaponType = itemType:getWeaponType()
            if weaponType > 0 then
                if weaponType == WEAPON_SHIELD then
                    shield = 1
                end
            end
        end
    end

    -- No weapon
    if shield == 0 then
        creature:sendCancelMessage("This spell requires a shield.")
        creaturePos:sendMagicEffect(CONST_ME_POFF)
        return false
    end

    -- Check if target is Player
    local stunCreature = Creature(var.number)
    if stunCreature:isPlayer() then
        stunDuration = stunDuration / 2 -- Halve stunDuration if Player
    end

    -- Stun
    local stun = Condition(CONDITION_STUN)
    stun:setParameter(CONDITION_PARAM_TICKS, stunDuration)
    stunCreature:addCondition(stun)

    -- Mute
    local mute = Condition(CONDITION_MUTED)
    mute:setParameter(CONDITION_PARAM_TICKS, stunDuration)
    stunCreature:addCondition(mute)

    -- Add animation
    addEvent(stunAnimation, 0, stunCreature.uid, stunCreature:getPosition(), (stunDuration / 1000) * 2)

    -- Damage formula
    local skill = creature:getSkillLevel(SKILL_SHIELD)
    local skillTotal = skill * (skill / 2)
    local levelTotal = creature:getLevel() / 5
    local min, max = -(((skillTotal * 0.02) + 4) + (levelTotal)), -(((skillTotal * 0.04) + 9) + (levelTotal))
    combat:setFormula(COMBAT_FORMULA_SKILL, 0, min, 0, max)

    -- Execute Damage
    return combat:execute(creature, var)
end


Source edits are required:

Condition
enums.h
after:
C++:
CONDITION_SPELLGROUPCOOLDOWN = 1 << 27,
add:
C++:
CONDITION_STUN = 1 << 28,

luascript.cpp
after:
C++:
registerEnum(CONDITION_SPELLGROUPCOOLDOWN)
add:
C++:
registerEnum(CONDITION_STUN)

condition.cpp
after:
C++:
case CONDITION_MANASHIELD:
    return new ConditionGeneric(id, type, ticks, buff, subId);
add:
C++:
case CONDITION_STUN:
    return new ConditionGeneric(id, type, ticks, buff, subId);

after:
C++:
switch (conditionType) {
    case CONDITION_MANASHIELD:
        icons |= ICON_MANASHIELD;
        break;
add:
C++:
case CONDITION_STUN:
    icons |= ICON_DAZZLED;
    break;

player.cpp
after:
C++:
case CONDITION_BLEEDING:
    sendTextMessage(MESSAGE_STATUS_DEFAULT, "You are bleeding.");
    break;
add:
C++:
case CONDITION_STUN:
    sendTextMessage(MESSAGE_STATUS_DEFAULT, "You have been stunned.");
    break;


You will then need to add checks to stop monsters from moving/changing direction, and stop players from attacking when stunned:

Monsters
monster.cpp
after:
C++:
bool updateLook = true;
bool resetTicks = interval != 0;
attackTicks += interval;
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    updateLook = false;
}
after:
C++:
if (isHostile()) {
    const Position& targetPos = target->getPosition();
    uint32_t distance = std::max<uint32_t>(Position::getDistanceX(pos, targetPos), Position::getDistanceY(pos, targetPos));
    for (const spellBlock_t& spellBlock : mType->info.attackSpells) {
        if (spellBlock.range != 0 && distance <= spellBlock.range) {
            return g_game.isSightClear(pos, targetPos, true);
        }
    }
    return false;
}
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    return false;
}

after:
C++:
inRange = true;
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    return false;
}

after:
C++:
bool resetTicks = true;
defenseTicks += interval;
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    defenseTicks = 0;
}

after:
C++:
if (isIdle || getHealth() <= 0) {
    //we dont have anyone watching might aswell stop walking
    eventWalk = 0;
    return false;
}
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    return false;
}

after:
C++:
const Position& creaturePos = getPosition();
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    return false;
}


Players
game.cpp
after:
C++:
if (creature->getDirection() == dir) {
    return false;
}
add:
C++:
if (creature->hasCondition(CONDITION_STUN)) {
    return false;
}

after:
C++:
creature->setLastPosition(creature->getPosition());
const Position& currentPos = creature->getPosition();
Position destPos = getNextPosition(direction, currentPos);
Player* player = creature->getPlayer();
add:
C++:
if (creature->hasCondition(CONDITION_STUN)) {
    return RETURNVALUE_NOTPOSSIBLE;
}

replace:
C++:
ss << "You are still muted for " << muteTime << " seconds.";
with: (We apply CONDITION_MUTE in the script to make use of this timer AND to stop players from casting instant spells)
C++:
ss << "You are still ";
if (player->hasCondition(CONDITION_STUN)) {
    ss << "stunned";
} else {
    ss << "muted";
}
ss << " for " << muteTime << " seconds.";

player.cpp (stop being able to attack)
after:
C++:
if (hasCondition(CONDITION_PACIFIED)) {
    return;
}
add:
C++:
if (hasCondition(CONDITION_STUN)) {
    sendCancelTarget(); // might need to add a 0 inbetween the brackets if you use older TFS build
    return;
}

spells.cpp (stop being able to use runes)
after:
C++:
if (!playerSpellCheck(player)) {
    return false;
}

add:
C++:
if (player->hasCondition(CONDITION_STUN)) {
    return false;
}
 
Last edited:
I would personally avoid that onMove event. It's for every creature on the map, that can be executed hundreds of times per second.
 
I would personally avoid that onMove event. It's for every creature on the map, that can be executed hundreds of times per second.

It's only registered for players, but yeah true.
Removed onMove, replaced with C++ check in game.cpp

after:
C++:
creature->setLastPosition(creature->getPosition());
const Position& currentPos = creature->getPosition();
Position destPos = getNextPosition(direction, currentPos);
Player* player = creature->getPlayer();
add:
C++:
if (creature->hasCondition(CONDITION_STUN)) {
return RETURNVALUE_NOTPOSSIBLE;
}
 
Last edited:
It's only registered for players, but yeah true.
Removed onMove, replaced with C++ check in game.cpp
But when people will start using it for monsters and more than this stun for players, it'll affect performance.
 
But when people will start using it for monsters and more than this stun for players, it'll affect performance.

Already does monsters.
Take note of edit too, removed the creatureevent anyway as it was unnecessary.
 
How does it work on players? No bugs found?
 
How does it work on players? No bugs found?

Works fine, no bugs.

There is one thing to keep in mind though:
Player movement is triggered by the client - because we can't block it at the server end, players will get the classic "snap back" when trying to move.

The only thing I can't test is the sendTargetCancel() line.
It should drop the player's attack target when stunned.

I can't test this as I'm using the flash client for dev.
It seems TFS doesn't send the right "cancel target" packet or isn't using it correctly(?) so targets still stick whenever that function is called for me due to the client.

Should work fine for a normal client though.

Mute timer works fine:
uJRCkd1.png


Gets appropriate status logs like with other conditions:
AAFXcUR.png


CONDITION_DAZZLED icon on the client end works fine:
8MVMAPL.png
 
Last edited:
Have you tried searching for the string "CONDITION_STUN" in your Entire Solution to make sure it is not duplicate?
Post automatically merged:

Thanks, @Leo32 .
I followed your step-by-step guide and it worked.

Edit: for some reason, it stopped working, then it worked again, and so forth. To be more precise, when it doesn't work, it doesn't even apply the condition to the target, whether it is a monster or a player; I'm still able to move, attack, talk, use runes.
PS: the animation always works.

Changed:
Lua:
-- from
combat:setCondition(stun)
combat:setCondition(mute)
-- to
stunCreature:addCondition(stun)
stunCreature:addCondition(mute)
and it is now working.
 
Last edited:
Have you tried searching for the string "CONDITION_STUN" in your Entire Solution to make sure it is not duplicate?
Post automatically merged:

Thanks, @Leo32 .
I followed your step-by-step guide and it worked.

Edit: for some reason, it stopped working, then it worked again, and so forth. To be more precise, when it doesn't work, it doesn't even apply the condition to the target, whether it is a monster or a player.

Show me a gif
 
Oh right, setCondition is deprecated now, replaced with addCondition.
I've updated the script in the main post.

This works I guess:
Lua:
-- from
combat:setCondition(stun)
combat:setCondition(mute)
-- to
stunCreature:addCondition(stun)
stunCreature:addCondition(mute)
and it is now working.

I can't see why there'd be a problem with it going through the combat object tho:
Lua:
combat:addCondition(stun)
combat:addCondition(mute)

Gj 👍
I'll test this and update main post.

EDIT: I found this thread which seems to say the same thing you're saying.
Maybe there is an issue with using addCondition through the combat object >.>
 
Last edited:
in condition.cpp I have only
C++:
case CONDITION_STUN:
    return new ConditionGeneric(id, type, ticks, buff, subId);
and
C++:
case CONDITION_STUN:
    sendTextMessage(MESSAGE_STATUS_DEFAULT, "You have been stunned.");
    break;
no more CONDITION_STUN

maybe ist because of same numbers in enums.h?
1.png
but when I add replace
C++:
CONDITION_STUN = 1 << 28,
for
C++:
CONDITION_STUN = 1 << 32,
and I put it after
C++:
CONDITION_SUMMON = 1 << 31,
then I have a lot of bugs:
adqwq.png
after I put it back to 28 and now I have this problem:

sdsa.png
CONDITION_STAMINAREGEN has number 28 also in my source.
 
Oh right, setCondition is deprecated now, replaced with addCondition.
I've updated the script in the main post.

This works I guess:


I can't see why there'd be a problem with it going through the combat object tho:
Lua:
combat:addCondition(stun)
combat:addCondition(mute)

Gj 👍
I'll test this and update main post.

EDIT: I found this thread which seems to say the same thing you're saying.
Maybe there is an issue with using addCondition through the combat object >.>
Thanks, @Leo32, for the response.
It seems indeed me and the guy from the other thread (@tuxico ) have the same issue.
I'm glad we could find a workaround.
Nevertheless, your mod is awesome and opens a lot of opportunities.
Thanks again.
Post automatically merged:

in condition.cpp I have only
C++:
case CONDITION_STUN:
    return new ConditionGeneric(id, type, ticks, buff, subId);
and
C++:
case CONDITION_STUN:
    sendTextMessage(MESSAGE_STATUS_DEFAULT, "You have been stunned.");
    break;
no more CONDITION_STUN

maybe ist because of same numbers in enums.h?
View attachment 45098
but when I add replace
C++:
CONDITION_STUN = 1 << 28,
for
C++:
CONDITION_STUN = 1 << 32,
and I put it after
C++:
CONDITION_SUMMON = 1 << 31,
then I have a lot of bugs:
View attachment 45099
after I put it back to 28 and now I have this problem:

View attachment 45100
CONDITION_STAMINAREGEN has number 28 also in my source.
I'm not a C++ expert, but check this (from here):
An example fixing that bit shifting issue:
C++:
// const.h
    PlayerFlag_SetMaxSpeed = 1 << 29,
    PlayerFlag_SpecialVIP = 1 << 30,
    PlayerFlag_NotGenerateLoot = static_cast<uint64_t>(1) << 31,
    PlayerFlag_CanTalkRedChannelAnonymous = static_cast<uint64_t>(1) << 32,
    PlayerFlag_IgnoreProtectionZone = static_cast<uint64_t>(1) << 33,
    PlayerFlag_IgnoreSpellCheck = static_cast<uint64_t>(1) << 34,
    PlayerFlag_IgnoreWeaponCheck = static_cast<uint64_t>(1) << 35,
    PlayerFlag_CannotBeMuted = static_cast<uint64_t>(1) << 36,
    PlayerFlag_IsAlwaysPremium = static_cast<uint64_t>(1) << 37,
 
Last edited:
@Adorius Black ,try these changes:
C++:
// from
enum itemAttrTypes : uint32_t {
// to
enum itemAttrTypes : uint64_t {
  
// and
// from
CONDITION_STUN = 1 << 32,
// to
CONDITION_STUN static_cast<uint64_t>(1) << 32

I'm not sure it is gonna work, but it doesn't hurt to try, right?
 
how did you change "says" to "casts"?
Did you send a packet which could be understood by default client or just modded it for yours?
 
Back
Top