User Tools

Site Tools


playground:scriptunreleased:customsword

Custom sword system

In this page, we will create a script that allows players to own, upgrade, and use swords to attack others. This system is designed using Object-oriented programming (OOP) 1) principles in Lua, but before starting. we advise you study object-oriented programming if you haven't, as it will be the main focus of the script.

Sword class

first of all, we must create a table that acts as a blueprint for the swords object, the code should look like this:

 local sword = {} 

after, we create the constructor2), so we must create a function called sword:new() function creates a new sword with properties like damage and maximum damage. the code would look something like this:

code:

function sword:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
 
    -- Sword attributes
    self.playerid = o.playerid -- sword owner
    self.itemid = o.itemid
    self.dmg = o.dmg -- Current damage
    self.maxdmg = o.maxdmg -- max damage
    self.incrementdmg = o.incrementdmg -- dmg increase per upgrade
 
    return o
end

after that, we must create a function to upgrade the sword and its damage, for this script, we will use this operation:

math.min(self.dmg + (quantity or self.incrementdmg), self.maxdmg) 3)

after, we create a function to update the damage, we will call this function sword:addDmg(), where we will print the old and new damage for debugging purposes

function sword:addDmg(quantity)
    print("Old damage: " .. self.dmg)
    self.dmg = math.min(self.dmg + (quantity or self.incrementdmg), self.maxdmg)
    print("New damage: " .. self.dmg)
end

next thing in our list is to create a function to add the damage, we can do this getting the current health of our target and substracting it by the sword's damage, we also must check that the result of this operation is greater than 0, so we use math.max(hp - self.dmg, 0) 4), now, as we want our swords to work for both creatures and players, we will need to make slight adjustments for each case. for creatures, we will use Creature:getAttr() and Creature:setAttr(). For players, we will use Player:getAttr() and Player:setAttr() to get and set their healths respectively, before that, we must check if the hurtactor is a player, if so, we will use the player methods, and if not, the creature ones.

our code should look similar to this:

function sword:hurt(hurtplayerid)
 
    if Actor:isPlayer(hurtplayerid) == ErrorCode.OK then
        local _, hp = Player:getAttr(hurtplayerid, PLAYERATTR.CUR_HP)
        Player:setAttr(hurtplayerid, PLAYERATTR.CUR_HP, math.max(hp - self.dmg, 0))
    else
        local _, hp = Creature:getAttr(hurtplayerid, CREATUREATTR.CUR_HP)
        Creature:setAttr(hurtplayerid, CREATUREATTR.CUR_HP, math.max(hp - self.dmg, 0))
    end
end

With that, we have created a class that can create swords, upgrade them, hurt mobs, etc, but right now, we have no way of managing players and their sword inventories, our goal is that each player should be able to own multiple swords, upgrade them individually, and use them to attack enemies. To achieve our goal, we will create a PlayerObj class that will manage player-specific data, including their sword inventory and attributes, this will make developing and adding new features a whole lot easier.

Playerobj class

First, we will Define a playerobj table that acts as the base for all player-related functions:

local playerobj = {}

then we create a function called playerobj:new() that sets up their inventory:

function playerobj:new(o)
    o = o or {}
    setmetatable(o, self)
    self.__index = self
 
    self.playerid = o.playerid -- Player ID
    self.swords = {} -- Sword inventory
 
    print("Player object created for " .. self.playerid)
    return o
end

after, we create a function to add a sword to a player's inventory:

function playerobj:addsword(data)
    print("Adding sword " .. data.id .. " to player " .. self.playerid)
    self.swords[data.id] = sword:new(data)
end

we then create a function to update a player's sword:

function playerobj:swordupgrade(id, customincrement)
    if self.swords[id] then
        print("Upgrading sword " .. id .. " for player " .. self.playerid)
        self.swords[id]:addDmg(customincrement)
    end
end

we then create a function to attack a target:

function playerobj:swordattack(id, hurtplayerid)
    if self.swords[id] then
        print("Player " .. self.playerid .. " attacks target " .. hurtplayerid)
        self.swords[id]:hurt(hurtplayerid)
    end
end

Events

First, we must create a new playerobj for that player, so we can add swords to that player, but first we are going to create a table to store all the players:

 local players = {} 

then, when any player joins the game, we create an entry in the players table with the same index as their uid. This ensures that each player's data can be easily accessed and managed using their UID as the key and we pass their uid as the playerid.

code should look like this:

ScriptSupportEvent:registerEvent([[Game.AnyPlayer.EnterGame]], function(e)
    players[e.eventobjid] = playerobj:new({playerid = e.eventobjid})
end)

also, to remove any lag and optimize, when any player leaves, we remove their playerobj this way:

 
ScriptSupportEvent:registerEvent([[Game.AnyPlayer.LeaveGame]], function(e)
    players[e.eventobjid] = nil
end)

next, we must check when the player picks up any item, to see if that item is a sword, for that, we must create a sword list that contains all the data of a sword, we will create a sword that has the following data as an example sword:

{id = 4097, dmg = 30, maxdmg = 100, incrementdmg = 10}

and we iterate through the swordlist and if the id matches, we add that sword to the player's inventory:

local swordlist = {
 
  {id = 4097, dmg = 30, maxdmg = 100, incrementdmg = 10}
 
}
 
ScriptSupportEvent:registerEvent([[Player.PickUpItem]], function(e)
    local playerid = e.eventobjid
    local itemid = e.itemid
 
    for _, v in pairs(swordlist) do
        if v.id == itemid then
            v.playerid = playerid
            players[playerid]:addsword(v)
        end
    end
end)

after that, when the player attacks, we must check their current tool and feed it to the function

ScriptSupportEvent:registerEvent([[Player.AttackHit]], function(e)
    local playerid = e.eventobjid
    local hurtplayerid = e.toobjid
    local _, curtoolid = Player:getCurToolID(playerid)
 
    players[playerid]:swordattack(curtoolid, hurtplayerid)
end)

Challenge

Now, the system we’ve built works as intended. However, while this is functional, the system currently doesn't handle updating swords (e.g., modifying damage or managing duplicates)

Here's where you step in! As a challenge for you, try modifying the script to add these features:

  • Allow players to upgrade swords they already own
  • Prevent duplicate swords from being added to the inventory
  • Handle cases where players might drop their swords

This system is extremelly flexible thanks to the structure we have created, this makes it so adding new features or extending functionality is straightforward. The sword and playerobj classes make it easy to adapt and expand the system without rewriting existing code.

Final result

 
local swordlist = {
 
    {id = 4097, dmg = 30, maxdmg = 100, incrementdmg = 10} --example sword
 
}
 
local sword = {}
local playerobj = {}
 
function sword:new(o)
    o = o or {}
 
    setmetatable(o, self)
    self.__index = self
 
    self.playerid = o.playerid -- owner
    self.itemid = o.itemid
 
    self.dmg = o.dmg
    self.maxdmg = o.maxdmg
    self.incrementdmg = o.incrementdmg
 
    return o 
end
 
function sword:addDmg(quantity) 
 
    print("old dmg: " .. self.dmg)
 
    self.dmg = math.min(self.dmg + (quantity or self.incrementdmg), self.maxdmg)
 
    print(" -> " .. self.dmg)
end
 
function sword:hurt(hurtplayerid) 
    if Actor:isPlayer(hurtplayerid) == ErrorCode.OK then
 
        local _, hurtplayerhp = Player:getAttr(hurtplayerid, PLAYERATTR.CUR_HP)
        local newhp = math.max(hurtplayerhp - self.dmg, 0)
 
        Player:setAttr(hurtplayerid, PLAYERATTR.CUR_HP, newhp)
     else
 
        local _, hurtplayerhp = Creature:getAttr(hurtplayerid, CREATUREATTR.CUR_HP)
        local newhp = math.max(hurtplayerhp - self.dmg, 0)
 
        Creature:setAttr(hurtplayerid, CREATUREATTR.CUR_HP, newhp)
    end
end
 
function playerobj:new(o) 
    o = o or {}
 
    setmetatable(o, self)
    self.__index = self
 
    self.playerid = o.playerid -- owner
    self.swords = {}
 
    print("Sword created for " .. self.playerid)
 
    return o 
end
 
function playerobj:addsword(data)
    print("adding sword " .. data.id .. " to " .. self.playerid)
    self.swords[data.id] = sword:new(data)
end
 
function playerobj:swordupgrade(id, customincrement)
    print("increasing damage to " .. self.playerid " sword")
    self.swords[id]:addDmg(customincrement)
end
 
function playerobj:swordattack(id, hurtplayerid)
    print(self.playerid .. " hurting " .. hurtplayerid)
    self.swords[id]:hurt(hurtplayerid)
end
 
local players = {}
 
ScriptSupportEvent:registerEvent([[Game.AnyPlayer.EnterGame]], function(e) 
 
    players[e.eventobjid] = playerobj:new({playerid = e.eventobjid}) -- add a playerobject for any new players
 
end) 
 
ScriptSupportEvent:registerEvent([[Game.AnyPlayer.LeaveGame]], function(e) 
 
    players[e.eventobjid] = nil -- delete the playerobject of any player that leaves
 
end) 
 
ScriptSupportEvent:registerEvent([[Player.AttackHit]], function(e) 
 
    local playerid = e['eventobjid']
    local hurtplayerid = e['toobjid'] --e['targetactorid']
 
    local _, curtoolid = Player:getCurToolID(playerid)
 
    players[playerid]:swordattack(curtoolid, hurtplayerid)
end)
 
ScriptSupportEvent:registerEvent([[Player.PickUpItem]], function(e) 
 
    local playerid = e['eventobjid']
    local itemid = e['itemid']
 
    for k,v in pairs(swordlist) do 
        if v.id == itemid then
            v.playerid = playerid
            players[playerid]:addsword(v)
        end
    end
 
end)
1)
object oriented programming is a way of organizing code using “objects.” These objects group related data (like a name or age) and actions (like moving or speaking) together. OOP helps make programs easier to understand, reuse, and update by using ideas like:
  • Encapsulation: Keeping an object's details private and only showing what's needed.
  • Inheritance: Letting one object share traits or actions from another.
  • Polymorphism: Allowing objects to behave differently based on their type.
It’s a practical way to build software by thinking about things as objects, just like in the real world.
2)
A constructor is a function to create and initialize objects. It sets up the initial state of the object (like setting up its properties) and ensures it's ready to use.
3)
Breakdown:
  • math.min(…, self.maxdmg) : Ensures that the result of the addition does not exceed self.maxdmg and math.min takes two numbers and returns the smaller one.
  • self.damage : it's the current damage of the sword object
  • quantity or self.incrementdmg : this means that if quantity is provided (isn't nil or false), the operation uses quantity (in case you want to increment a custom value), if not, the operation uses self.incrementdmg
  • self.dmg + (quantity or self.incrementdmg) : This adds the current damage (self.dmg) to either quantity or self.incrementdmg (depending on which is available)
4)
This returns the greater value between hp - self.dmg and 0, preventing the health from becoming negative
playground/scriptunreleased/customsword.txt · Last modified: 2024/12/22 20:31 by notsopro