====== 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) ((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.)) 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 constructor((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.)), 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) ** ((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)
))
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)** ((This returns the greater value between hp - self.dmg and 0, preventing the health from becoming negative)), 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)