Lua scripting for NPCs

Information & discussion about the OnePerOne extension.

Lua scripting for NPCs

Postby Finito » Sun Apr 11, 2010 5:10 pm

For those who do not know what Lua is http://www.lua.org/about.html.

This is currently in development so things below might change. (Also feel free to discuss and make suggestions)

What has been done so far
    The functions behind commands in EVT have been exposed for use within Lua.
    Player variables e.g. Stats have been exposed for use within Lua.
    Falls back to using EVT if there is no Lua script registered for an NPC.

List of functions and variables currently available
Code: Select all
   // Functions implemented for exposing execute commands to Lua
   AddEvent(eventNum)
   AddServerEvent(eventNum)
   AuctionBuyItem(unknown1)
   AuctionBuyOpen(unknown1)
   AuctionDeleteBadList()
   AuctionNoBidDraw(unknown1)
   AuctionNoBuyDraw(unknown1)
   AuctionGetMoney(unknown1)
   AuctionSellOpen(unknown1)
   BBSOpen(bbsNum)
   ChangeCha(type, amount)
   ChangeDex(type, amount)
   ChangeHP(type, amount)
   ChangeInt(type, amount)
   ChangeMageType(mageType)
   ChangeMaxHP(type, amount)
   ChangeMP(type, amount)
   ChangeMaxMP(type, amount)
   ChangeMaxStm(type, amount)
   ChangeMaxWgt(type, amount)
   ChangePKMode(mode)
   ChangeStorageType(storageType)
   ChangeStr(type, amount)
   ChangeWis(type, amount)
   OpenClassPointStore(store)
   CounterInit()
   CounterStart(unknown1, unknown2, unknown3)
   DelEvent(eventNum)
   DelServerEvent(eventNum)
   GiveGold(unsigned money)
   GiveItem(num, count)
   GiveMagic(magic)
   GiveMakeSkill(make)
   GiveSpecialAttack(special)
   OpenGuildTownTaxRate(townNum)
   GuildOpen()
   KillNpc()
   Link(x, y, zone)
   LiveNpc(monsterSetId, count, x, y)
   EventMagic@Magic(magic)
   MakerOpen(makeType1, makeType2, makeType3, makeType4, makeType5, makeType6)
   MessageNone(chatNum)
   MessageOk(chatNum)
   OpenChangeItem()
   OpenGuildTownStone(townNum)
   OpenGuildTownStore(townNum)
   OpenGuildTownStoreReg(townNum)
   OpenHairShop(price1, price2, price3, price4)
   OpenSpecialMakeSkill(make)
   OpenTrader(type1, type2, type3, type4, type5, type6)
   PlusMoral(a, b)
   RepairItemOpen()
   ResetEventCount()
   RobGold(amount)
   RobItem(num, count)
   RobMagic(magic)
   RobSpecialAttack(special)
   Say(npc, chatNum)
   SelectMsg(chatNum, selChatNum1, selChatFunc1, selChatNum2, selChatFunc2, elChatNum3, selChatFunc3, selChatNum4, selChatFunc4)
   SetEventCount(count)
   ShowMagic(postMagic, preMagic = -1)
   SpecialAttack(special)
   StorageOpen()
   StoreOpen(store, buyRate, sellRate)
   TimerInit()
   TimerStart()
   WarpGate(zone, x, y)
   XmasPointGuild()
   XmasPointUser()

   // Functions implemented for exposing logic commands to Lua
   boolean FindEvent(eventNum) // Known in evt script as either EXIST_EVENT or ABSENT_EVENT
   boolean CheckAliveNpc(monsterSetId, check)
   boolean CheckAuctionBadList()
   boolean CheckClassTown()
   boolean CheckClassTownWar()
   boolean CheckCounter(count1, count2)
   boolean CheckDemon()
   boolean CheckMonster()
   boolean CheckEventCount(eventCount1, eventCount2)
   boolean CheckFirstClick()
   boolean CheckGuildRank(guildRank)
   boolean CheckGuildTown(townNum)
   boolean CheckGuildTownAlly(townNum)
   boolean CheckGuildTownLevel(townNum, level)
   boolean CheckGuildTownWar(townNum)
   boolean CheckHuman()
   boolean CheckItemSlot(errorNpcChatId, item1, amount1, item2, amount2, item3, amount3, item4, amount4, item5, amount5)
   boolean CheckJob(job)
   boolean CheckPKMode(check)
   boolean CheckPosition(zone, x1, y1, x2, y2)
   boolean CheckTimer(timer1, timer2)
   boolean FindBasicItem(item, amount) // Known in evt script as either EXIST_ITEM or ABSENT_ITEM
   boolean FindMagicByMageType(mageType) // Known in evt script as either EXIST_MAGETYPE or ABSENT_MAGETYPE
   boolean FindMagic(magicNo) // Known in evt script as either EXIST_MAGIC or ABSENT_MAGIC
   boolean CheckGameTime(gameYear, gameMonth, gameDay, gameHour) // Known in evt script as GAME_TIME
   boolean CheckGameDay(gameDay) // Known in evt script as GAME_DAY
   boolean CheckGameMonth(gameMonth) // Known in evt script as GAME_MONTH
   boolean CheckItemNum(item, min, max) // Known in evt script as HOWMUCH_ITEM Note the check is >= min and < max later on might be best to make function only return the count and do the >= and < check within Lua
   boolean Rand(rand)
   boolean Rank(rank)
   boolean CheckStorageType(storageType)
   boolean CheckItemWeight(errorNpcChatId, item1, amount1, item2, amount2, item3, amount3, item4, amount4, item5, amount5) // Known as WEIGHT in evt script will be changing it to be called CheckItemWeight within Lua

   // Functions for obtaining player variables
   string getName()
   
   number getJob()
   setJob(newJob)

   number getHP() const { return *currentHP; }
   void setHP(int newCurrentHP) { *currentHP = newCurrentHP; }

   number getMaxHP()
   setMaxHP(newMaxHP)

   number getMP()
   setMP(newCurrentMP)

   number getMaxMP()
   setMaxMP(newMaxMP)

   number getWeight()
   SetWeight(newCurrentWeight)

   number getMaxWeight()
   SetMaxWeight(newMaxWeight)

   number getLevel()
   setLevel(newLevel)

   numbergetStrength()
   setStrength(newStrength)

   number getDexterity()
   setDexterity(newDexterity)

   number getIntelligence()
   setIntelligence(newIntelligence)

   number getCharisma()
   setCharisma(newCharisma)

   number getWisdom()
   setWisdom(newWisdom)

   number getConstitution()
   setConstitution(newConstitution)

   number getMoney()
   setMoney(newMoney)

   number getMoral()
   setMoral(newMoral)

   number getGender()

   number getBlueMagicExp()
   setBlueMagicExp(newBlueMagicExp)

   number getWhiteMagicExp()
   setWhiteMagicExp(newWhiteMagicExp)

   number getBlackMagicExp()
   setBlackMagicExp(newBlackMagicExp)

   number getWeaponMakeExp()
   setWeaponMakeExp(newWeaponMakeExp)

   number getArmorMakeExp()
   setArmorMakeExp(newArmorMakeExp)

   number getAccessoryMakeExp()
   setAccessoryMakeExp(newAccessoryMakeExp)

   number getPotionMakeExp()
   setPotionMakeExp(newPotionMakeExp)

   number getCookingExp()
   setCookingExp(newCookingExp)

   number getSwordExp()
   setSwordExp(newSwordExp)

   number getSpearExp()
   setSpearExp(newSpearExp)

   number getBowExp()
   setBowExp(newBowExp)

   number getAxeExp()
   setAxeExp(newAxeExp)

   number getKnuckleExp()
   setKnuckleExp(newKnuckleExp)

   number getStaffExp()
   setStaffExp(newStaffExp)

   number getCurrentExp()
   number getMaxExp()


Working example script 1:
Code: Select all
--[[
Description: NPC Script for those named "Warehouse Keeper" in TYT Area
Author: Finito
Tested: Yes

EVT REF 1: npc_WarehouseKeeper_monsterset_8_event_56.txt
EVT REF 2: npc_WarehouseKeeper_monsterset_287_event_1220.txt
]]

--[[
Using this table to isolate the script so that the functions are within local scope and not global.
Otherwise have problems with other scripts having same function names and therefore override.
]]
local WarehouseKeeper = {}

function WarehouseKeeper.StorageOpen(npc, player)
   if not player:CheckHuman() then return end

   if not player:FindEvent(109) then
      player:AddEvent(109)
      player:ChangeStorageType(0)
   end

   player:StorageOpen()
end

function WarehouseKeeper.StorageOpenGuildTown(npc, player)
   if not player:CheckHuman() then return end

   if player:CheckGuildTownWar(1) then
      player:MessageNone(1154)
   else
      player:StorageOpen()
   end
end

--[[
There is more than one warehouse keeper in TYT Area so instead of duplicating code and Lua files
Add an event for each of them (if appropriate of course)

If the same Npc is registered for the same event multiple times, it will replace the current with newly registered.
Output will be made to console if a replace of an registered event occurs.
]]
RegisterNpcEvent(1, 8, WarehouseKeeper.StorageOpen)
RegisterNpcEvent(1, 206, WarehouseKeeper.StorageOpen)
RegisterNpcEvent(1, 207, WarehouseKeeper.StorageOpen)
RegisterNpcEvent(1, 287, WarehouseKeeper.StorageOpenGuildTown)


Working example script 2:
Code: Select all
--[[
Description: NPC Script for Bar Owner in TYT Area i
Author: Finito
Tested: Yes
]]
local BarOwner = {}

function BarOwner.OnTalk(npc, player)
   if not player:CheckHuman() then return end

   player:SelectMsg(342, 363, BarOwner.HaveConversation, 344, BarOwner.BuySell,
     265, -1, -1, -1)
end

function BarOwner.HaveConversation(npc, player)
   -- Fortune Ticket
   if not player:FindEvent(48) then
      if player:CheckItemWeight(365, 141, 1, -1, -1, -1, -1, -1, -1, -1, -1) and
        player:CheckItemSlot(393, 141, 1, -1, -1, -1, -1, -1, -1, -1, -1) then
         player:MessageNone(308)
         player:GiveItem(141, 1)
         player:AddEvent(48)
         return
      end
   end

   --[[
   Level 5 Quest Liquor Delivery
   Level 8 Quest Gathering Meats
   ]]
   if player:FindEvent(116) and not player:FindEvent(6) and not player:FindEvent(117) then
      player:SelectMsg(817, 818, BarOwner.LiquorDelivery, 819, BarOwner.GatheringMeats,
        -1, -1, -1, -1)
   elseif not BarOwner.LiquorDelivery(npc, player) and not BarOwner.GatheringMeats(npc, player) then
      player:MessageNone(470)
   end
end

function BarOwner.LiquorDelivery(npc, player)
   if not player:FindEvent(6) and player:getLevel() >= 5 then
      player:SelectMsg(105, 111, BarOwner.LiquorDeliveryAccept, 104, BarOwner.LiquorDeliveryDecline,
        -1, -1, -1, -1)
      return true
   elseif player:FindEvent(6) and not player:FindEvent(7) then
      player:MessageNone(110)
      return true
   elseif player:FindEvent(7) and not player:FindEvent(8) then
      if player:FindBasicItem(126, 1) then
         if player:CheckItemWeight(365, 61, 2, -1, -1, -1, -1, -1, -1, -1, -1) and
              player:CheckItemSlot(393, 61, 2, -1, -1, -1, -1, -1, -1, -1, -1) then
            player:MessageNone(109)
            player:RobItem(126, 1)
            player:GiveItem(61, 2)
            player:GiveGold(1000)
            player:AddEvent(8)
            player:ShowMagic(961)
         end
         return true
      end
   else
      return false
   end
end

function BarOwner.LiquorDeliveryAccept(npc, player)
   if not player:FindEvent(6) and player:getLevel() >= 5 then
      if player:CheckItemWeight(365, 123, 1, -1, -1, -1, -1, -1, -1, -1, -1) and
        player:CheckItemSlot(393, 123, 1, -1, -1, -1, -1, -1, -1, -1, -1) then
         player:MessageNone(107)
         player:GiveItem(123, 1)
         player:AddEvent(6)
         player:ShowMagic(960)
      end
   end
end

function BarOwner.LiquorDeliveryDecline(npc, player)
   player:MessageNone(106)
end

function BarOwner.GatheringMeats(npc, player)
   if not player:FindEvent(114) or player:FindEvent(120) then
      return false
   end

   if not player:FindEvent(118) then
      if not player:FindEvent(117) then
         if player:getLevel() < 8 then
            player:MessageNone(768)
            return true
         else
            player:SelectMsg(769, 315, BarOwner.GatheringMeatsAccept, 316, BarOwner.GatheringMeatsDecline,
              -1, -1, -1, -1)
            return true
         end
      else
         if player:CheckItemNum(87, 0, 20) or player:CheckItemNum(86, 0, 30) then
            player:MessageNone(772)
            return true
         elseif player:FindBasicItem(87, 20) and player:FindBasicItem(86, 30) then
            if player:CheckItemWeight(365, 61, 3, -1, -1, -1, -1, -1, -1, -1, -1) and
              player:CheckItemSlot(393, 61, 3, -1, -1, -1, -1, -1, -1, -1, -1) then
               player:MessageNone(773)
               player:RobItem(87, 20)
               player:RobItem(86, 30)
               player:GiveGold(1200)
               player:GiveItem(61, 3)
               player:AddEvent(118)
               BarOwner.LegendaryCookBook(npc, player)
               return true
            end
         end
      end
   else
      BarOwner.LegendaryCookBook(npc, player)
      return true
   end
   return false
end

function BarOwner.GatheringMeatsAccept(npc, player)
   if not player:FindEvent(118) and not player:FindEvent(117) then
      if player:getLevel() >= 8 then
         player:MessageNone(770)
         player:AddEvent(117)
      end
   end
end

function BarOwner.GatheringMeatsDecline(npc, player)
   player:MessageNone(771)
end

function BarOwner.LegendaryCookBook(npc, player)
   if not player:FindEvent(119) then
      if player:getLevel() < 11 then
         player:MessageNone(774)
      else
         player:MessageNone(775)
         player:AddEvent(119)
      end
   else
      player:MessageNone(776)
   end
end

function BarOwner.BuySell(npc, player)
   local moral = player:getMoral()
   if moral >= -100 and moral < -30 then
      player:MessageNone(647)
   elseif moral >= -30 and moral < -10 then
      player:StoreOpen(5, 120, 80)
   elseif moral >= 50 and moral < 101 then
      player:StoreOpen(5, 95, 100)
   else
      player:StoreOpen(5, 100, 100)
   end
end

RegisterNpcEvent(1, 12, BarOwner.OnTalk)


Post will be updated with more information later on (having hard time thinking how to type it up). Can ask questions/discuss within this topic/forum.
Finito
SomaDev Staff
 
Posts: 425
Joined: Fri Jan 11, 2008 6:25 pm

Re: Lua scripting for NPCs

Postby Finch » Sun Apr 11, 2010 9:50 pm

All If statements, very similar to Java and C++ in how its coded, except its more descriptive in its commands.

Interesting :D
/_MoH

evileddie wrote:Still get the gangsters on there who have no life, who sit there in their smelly piss stained boxers... scratching their arses and clicking Soma.
Finch
SD Three Star
SD Three Star
 
Posts: 145
Joined: Wed Oct 14, 2009 9:40 am

Re: Lua scripting for NPCs

Postby Slayerr » Mon Apr 12, 2010 6:14 am

That actually looks alot better to use.
Soma Maps
Fornway Mountains (South) v2.0 - IN PROGRESS
http://www.somadev.com/forum/viewtopic.php?f=26&t=3385
User avatar
Slayerr
SD Three Star
SD Three Star
 
Posts: 134
Joined: Sun Aug 24, 2008 6:48 pm

Re: Lua scripting for NPCs

Postby Tiffany » Mon Apr 12, 2010 8:06 am

Looks much better than the previous version of EVT scripting. Although, I can see possible security issues since its still possible to cause the current 1p1 to crash although a lot has been done to reduce the threat. The exposing of the stats to the lua methods/functions is slightly concerning but of course the lua coding procedures would not be directly accessible to the user as such.

However, even with a massive team of coders games like WoW, etc still get crackers attention as its a challenge to them plus potentially rewarding financially if they can easily make / sell some gold in the process. I guess MOS will not attract that kind of attention with its userbase, lol.
Tiffany
SD Pro 2 Star
SD Pro 2 Star
 
Posts: 428
Joined: Sat Jan 12, 2008 1:17 pm

Re: Lua scripting for NPCs

Postby Finito » Mon Apr 12, 2010 10:40 am

Tiffany wrote:Looks much better than the previous version of EVT scripting.


Not a new version of EVT scripting, just i have most of the same names for things so that the transition is easier. I am open to suggestions for making changes and introducing new functions (possible to expose anything from the current 1p1 and also code new things).

Tiffany wrote:Although, I can see possible security issues since its still possible to cause the current 1p1 to crash although a lot has been done to reduce the threat. The exposing of the stats to the lua methods/functions is slightly concerning but of course the lua coding procedures would not be directly accessible to the user as such.


It's all server side and no different than using LEVEL, STR, DEX within EVT only it just gives you the value instead of doing comparisons for you therefore more flexible.
Finito
SomaDev Staff
 
Posts: 425
Joined: Fri Jan 11, 2008 6:25 pm

Re: Lua scripting for NPCs

Postby arcanine » Mon Apr 12, 2010 11:03 am

A function that returns the amount of barr currently in game and the amount of players in game might be useful for inflation control

I.E. a script that modifies shop prices according to

Amount of barr in game / amount of players if over thershold increase buy rate in all shops and decrease sell rate
User avatar
arcanine
SD Pro 5 Star
SD Pro 5 Star
 
Posts: 1056
Joined: Mon Apr 21, 2008 2:53 pm

Re: Lua scripting for NPCs

Postby Remix » Tue Apr 13, 2010 12:46 am

a function to make an npc use global shout could come in handy;o
Image
User avatar
Remix
SD Five Star
SD Five Star
 
Posts: 344
Joined: Sun Feb 24, 2008 11:39 pm

Re: Lua scripting for NPCs

Postby arcanine » Tue Apr 13, 2010 7:44 pm

I'm looking to write tutorials for lua in the other section what I wanted to know is can you write your own variables? and what's the syntax?

for example I'd like to assign this pseudo code to monsters
if(killnpc());
{quest1++}
if (quest1 > 10)
{quest1 = 0;
reward code here}
for basic monster kill counting for quests, would that work in theory? I suppose it'll be easier once this is released so I can check for myself then write code snippets for people to use
User avatar
arcanine
SD Pro 5 Star
SD Pro 5 Star
 
Posts: 1056
Joined: Mon Apr 21, 2008 2:53 pm

Re: Lua scripting for NPCs

Postby Finito » Tue Apr 13, 2010 9:49 pm

arcanine wrote:I'm looking to write tutorials for lua in the other section what I wanted to know is can you write your own variables? and what's the syntax?

for example I'd like to assign this pseudo code to monsters
if(killnpc());
{quest1++}
if (quest1 > 10)
{quest1 = 0;
reward code here}
for basic monster kill counting for quests, would that work in theory? I suppose it'll be easier once this is released so I can check for myself then write code snippets for people to use


Short Answer
No, not like that.


Long Answer
It would be done by registering the npc for the 'Dead' event. Currently only the 'Talk / Click' event type exists (other events will be available upon first release).

Code: Select all
RegisterNpcEvent(dead_event_type, monster_set_id, function_name)


As for the counter, each player in the game doesn't have it's own lua state so if was to do
Code: Select all
local Npc = {}

Npc.counter = 1

function Npc.onTalk(npc, player)
    Npc.counter = Npc.counter + 1
end

RegisterNpcEvent(1, 8, Npc.onTalk)


The first player that clicked the NPC the value of counter would be 1 for the next player it would be 2 and so on. So need to keep a separate counter value for each player.

One way of doing this within Lua (this been done pretty quickly so could do with alot of improvement)
Code: Select all
Counters = {}
counter = {}

function counter.contains(element)
  for _, v in pairs(Counters) do
    if v == element then
      return true
    end
  end
  return false
end

function counter.Add(player, name, value)
   local newCounter = {}
   newCounter.player = player
   newCounter.name = name
   newCounter.value = value
   if not counter.contains(newCounter) then
      table.insert(Counters, newCounter)
   end 
end

function counter.Remove(player, name)
   for i, v in pairs(Counters) do
      if v.name == name and v.player == player then
         table.remove(Counters, i)
      end
   end
end

function counter.Increment(player, name)
   for i, v in pairs(Counters) do
      if v.name == name and v.player == player then
         v.value = v.value + 1
      end
   end
end

function counter.Value(player, name)
   for _, v in pairs(Counters) do
      if v.name == name and v.player == player then
         return v.value
      end
   end
end

That script would be loaded and will have access to it from any other script because it was put in global.
It just acts as a container for counter allowing to add/remove, increment and get the current value.

Example usage would be
Code: Select all
local Npc = {}

function Npc.onTalk(npc, player)
    if not player:FindEvent(1) then
      counter.Add(player, "counter1", 0)
      player:AddEvent(1)
   elseif not player:FindEvent(2) then
      if counter.Value(player, "counter1") < 10 then
         counter.Increment(player, "counter1")
      else
         counter.Remove(player, "counter1")
         player:AddEvent(2)
      end      
   end
end

RegisterNpcEvent(1, 8, Npc.onTalk)


Another way would for the extension to provide better functions for counters. I wont go into any detail about that now though.
Finito
SomaDev Staff
 
Posts: 425
Joined: Fri Jan 11, 2008 6:25 pm


Return to OnePerOne Extension

Who is online

Users browsing this forum: No registered users and 3 guests

cron