Jump to content

Codex

Members
  • Posts

    13
  • Joined

  • Last visited

  • Days Won

    2

Posts posted by Codex

  1. When you write a program/script you are testing the possible outcomes whether they are for the benefit of your idea or the exclusion of things you don't want. So you should always have the thought in the back of your mind (especially if the code is meant for production) of "What if?".

     

    This what if? mindset is what seperates good code from really bad code, all code is written in steps, and 99% of it never works the 1st time around, plus your code is always evolving so its ok to write something out in a complicated manner and then go back later on and shorten it up or add to it.

     

    On a final note, if you are satisfied with the layout and execution of your code... then you are missing or overlooked something.. Good programmers are never satisfied with their code... hell we will spend days working on a small fragment of code til we figure out why the hell it isn't working.. But that is how you become a good coder is learning from your mistakes.. and you will make a lot of them :)

     

    I am sure this tutorial could of been better, but I don't really have a lot of time to devote to expanding it.

     

    For further reading please check out.

    https://www.lua.org/manual/5.1/
    or
    https://www.tutorialspoint.com/lua/index.htm
    
    • Like! 1
  2. Functions and Scope

     

    A function in any programming language is created and used to simplify redundant coding, the basic form of a function body is constructed as:

    function myFunction()
    end
    

    To construct or define a function we start with the keyword function followed by the name of the function, a both open and closed parentheses and the end keyword to close the function.

     

    Functions can also be declared/defined like this.

    myFunction = function()
    end
    

    You can also rename a function by passing the function to another identifier.

    function myFunction(value)
        print(value)
    end
    
    myNewFunction = myFunction
    
    myNewFunction(10)
    -- prints
    10
    

    The value identifier in the parentheses of myFunction is arbitrary and can be named anything, this identifier is known as a parameter, it is used to pass information to the function so that the function can do what it needs to do with that data.

     

    Lets say we wanted to calculate the conversion of fahrenheit to celsius and vice versa we could create a function to calculate those values for us.

    function convertTemperature(temperature, toCelsius)
        local value = "The temperature ".. temperature .. " in "
        if toCelsius then
            value = value .. "fahrenheit is "..(((temperature - 32) * 5) / 9) .. " in celsius."
        else
            value = value .. "celsius is "..(((temperature * 9) / 5) + 32) .. " in fahrenheit."
        end
        print(value)
    end
    

    Now we can call on the function to determine what the temperature of say 23 fahrenheit is in celsius.

    convertTemperature(23, true)
    -- prints
    The temperature 23 in fahrenheit is -5 in celsius.
    

    Now if we wanted to determine what the value of say 0 celsius is in fahrenheit we could simply set the 2nd parameter to false or just omit it entirely.

    convertTemperature(0)
    -- prints
    The temperature 0 in celsius is 32 in fahrenheit.
    

     

    Scope

     

    Scope refers to the visibility of variables, functions or tables. In other words, which parts of your program can see or use them. There are two forms of scope Global & Local.

     

    Local variables are created with the local keyword, their scope or visibility only exist within the body it was declared in where as global is simply defined without using the local keyword and its visibility is seen outside the body it was declared in but only when that body is brought into view.

     

    Lets look at an example of both local and global scope.

     

    function A()
        -- x is global, but only global within the scope of function A
        x = 1
    end
    
    function B()
        -- since we never called on the function A, x returns nil, because it doesn't exist outside of A
        print(x)
    end
    
    B()
    -- prints
    nil
    
    

    As you can see from the comments above, the scope of x defined in A is global to A, but when called by B its looses its scope because we never brought A inside of the scope of B.

     

    Lets try this again but this time calling A inside of B.

    function A()
        -- x is global, but only to the scope of function A
        x = 1
    end
    
    function B()
        A() -- call function A and bring its values which are global into function B's scope
        print(x)
    end
    
    B()
    
    -- prints
    1
    

    Now that we have called function A within function B, B can now clearly see the value of x.

     

    So what if we changed x's scope to local, would B still see the x when we called A from B? Well lets try it.

    function A()
        local x = 1
    end
    
    function B()
        A()
        print(x)
    end
    
    B()
    -- prints
    nil
    

    The answer is no, because the scope of x was declared local to the function of A, so even tho function B called on A, any values which were not declared as global B won't be able to see.

     

    Here is where it might get a little confusing (If you aren't confused already :P ). When a value is declared as local outside of a function then its scope although declared local is actually global. You are probably asking yourself, what do you mean "When a value is declared as local outside of a function then its scope although declared local is actually global?"

     

    Well lets take a look at an example:

    local text = "I am text, although I am declared local, I am actually global to most things"
    
    function test()
        print(text)
    end
    
    test()
    -- prints
    I am text, although I am declared local, I am actually global to most things
    

     

    The reason the test function can see text is because text was declared before test, allowing test to access text's value. If we were to declare text's value after test, it would print nil when we called it.

     

    function test()
        print(text)
    end
    
    local text = "I am text, although I am declared local, I am actually global to most things"
    
    test()
    -- prints
    nil
    

    So now you might be asking yourself, "What if I omit the local keyword from text, will I be able to access text's value?". Lets have a look.

    function test()
       print(text)
    end
    
    text = "I am text"
    
    test()
    -- prints
    I am text
    

    Sure enough you can access text's value from test, even tho test was defined before text, this is because the lua interpreter isn't actually calling the value of text when the function test is defined, it only looks for the value of text when the function test is called.

     

     

    Returning Values

     

    We have covered quite a bit in relation to functions, we've learned how to construct and define a function, pass information to a function, we learned about scope and even learned how to print values to the console. But there is more to functions than just printing values, such as returning them to whatever called the function.

     

    To return a value from a function is pretty straight forward, in order to this we use the return keyword.

     

    function add(a, b)
        return a + b
    end
    

    The add function could be called like this.

    function add(a, b)
        return a + b
    end
    
    local sum = add(6, 7)
    
    print(sum)
    -- prints
    13
    

    Functions can also return not just single values but they can also return true/false, tables, multiple values or even other functions.

     

    Here is an example (although it could be written a lot better :P ) of a function calling other functions but returning either a string or a table of values.

    --[[ 
        if the strings length is greater 2 this will treat a string as an array 
        and pop off the 1st 2 characters of the string returning the remaining string, 
        if not it returns nil ( nil essentially means false )
    ]]
    function pop(v)
        return #v > 2 and v:sub(3, #v) or nil
    end
    
    -- this function will clip the string for the 1st 2 characters and return them as a table
    function clip(j)
        local y = {}
        for I = 1, #j do
            table.insert(y, j:sub(I, I))
        end
        return y
    end
    
    --[[ 
        this function will convert hex color codes to its respective rgb values
        optionally returning a string or table of values
    ]]
    function hexToRgb(hexColor, asString)
        -- all possible hexidecimal values
        local hex = {
            ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5, 
            ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
            ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15
        }
        -- table to hold the rgb color codes
        local rgb = {}
        -- loop while hexColor does not equal nil
        while(hexColor) do
            local value = clip(hexColor:sub(1, 2):lower())
            table.insert(rgb, ((hex[value[1]] * 16) + hex[value[2]]))
            hexColor = pop(hexColor)
        end
        return asString and table.concat(rgb, ", ") or rgb
    end
    
    print(hexToRgb('FF2344', true))
    -- prints
    255, 35, 68
    
    

     

    Admitingly the above code could be written a lot better, with type checking and such... so lets do that now, lets re-write the above code to handle different types of values with the appropriate lengths.

     

    -- all possible hexidecimal values
    local hex = {
        ['0'] = 0, ['1'] = 1, ['2'] = 2, ['3'] = 3, ['4'] = 4, ['5'] = 5,
        ['6'] = 6, ['7'] = 7, ['8'] = 8, ['9'] = 9,
        ['a'] = 10, ['b'] = 11, ['c'] = 12, ['d'] = 13, ['e'] = 14, ['f'] = 15
    }
    
    --[[
        if the strings length is greater 2 this will treat a string as an array
        and pop off the 1st 2 characters of the string returning the remaining string,
        if not it returns nil ( nil essentially means false )
    ]]
    function pop(v)
        return #v > 2 and v:sub(3, #v) or nil
    end
    
    -- this function will clip the string for the 1st 2 characters and return them as a table
    function clip(j)
        local y = {}
        for I = 1, #j do
            table.insert(y, j:sub(I, I))
        end
        return y
    end
    
    --[[
        this function will convert hex color codes to its respective rgb values
        optionally returning a string or table of values
    ]]
    function hexToRgb(hexColor, asString)
        -- table to hold the rgb color codes
        local rgb = {}
        -- determine what type of data hexColor is
        if type(hexColor) == "string" then
    	while(hexColor and #hexColor > 1) do
    	    local value = clip(hexColor:sub(1, 2):lower())
    	    table.insert(rgb, ((hex[value[1]] * 16) + hex[value[2]]))
    	    hexColor = pop(hexColor)
    	end
    	return (#rgb > 0) and (asString and table.concat(rgb, ", ") or rgb) or "You must use at least 2 values"
        else
    	-- this will handle all other types of data which we try to pass to this function 
    	return "This function will not process " .. type(hexColor) .. "s, please place your values in single(\') or double quotes(\")"
        end
    end
    
    

     

    So now lets test that theory. Can this semi-rewritten function handle different values types and proper lengths?

     

    Lets try it passing limited values

    print(hexToRgb('B', true))
    -- prints
    You must use at least 2 values
    
    

    Now lets try passing it just numbers

    print(hexToRgb(00, true))
    -- prints
    This function will not process numbers, please place your values in single(') or double quotes(")
    

    Now lets try with a table, empty or otherwise.

    print(hexToRgb({}, true))
    -- prints
    This function will not process tables, please place your values in single(') or double quotes(")
    

    Next lets try with a function, in this case an anonymos one.

    print(hexToRgb(function() end, true))
    -- prints
    This function will not process functions, please place your values in single(') or double quotes(")
    

    Lets pass it a nil value (literally)

    print(hexToRgb(nil, true))
    -- prints
    This function will not process nils, please place your values in single(') or double quotes(")
    

     

    So... As you can see the function can now handle different values thrown at it ( still not perfect but who/what is :P ).

    • Like! 1
  3. Iterators

     

    There are 4 types of iterators aka loops in lua, the numerical for, the generic for, the while and the repeat. Lets take a close look at each one.

     

    The Numerical For

    	for variable = start, stop, step do
    		-- code goes here
    	end
    
    The numerical for loop consists of the keyword for, variable is simply the local variable associated with the for loop and can be named almost anything, I know there is that word local again :P. Then we have the assignment operator which takes the value stored in start and stores it in variable.
    Start is merely a description of the execution of the for loop, start is a value of where we would like our for loop to start counting, e.g. for I = 1. Stop is a value which would like our for loop to stop at (e.g. for I = 1, 10) and finally Step is the incremental value for our local variable, if omitted the the for loop uses step's default value of 1.
    Lets take a look at an example of the numerical for loop so maybe it will make more sense.
       -- 1st example, with the step value omitted
         for I = 1, 5 do
           print(I)
         end
         -- prints
         1
         2
         3
         4
         5
    
       -- 2nd example, with a step value of 2
         for I = 1, 10, 2 do
           print(I)
         end
         -- prints
         1
         3
         5
         7
         9
        
    In the 1st example the numeric for loop prints the value of I 5 times, by omitting the step value the for loop used the default step value of 1 and printed every iteration of I to screen.
    In the 2nd example the for loop does pretty much everything as the 1st example did except that we manipulated the I's value based on the step value.
    Using 2 as our step value we tricked the for loop into increasing the value of I by 2 rather than 1 per iteration this is why the results of I are 1,3,5,7,9 rather than 1,2,3,4,5,6,7,8,9,10, because we are adding the step value to I per iteration.
    Using this formula per iteration for step being 2.
         I = 1
         step = 2
         max = 10
      -- compare I to max
         -- iteration 1
         print(I) -- 1
         I = (I + step)
      -- compare I to max
         -- iteration 2
         print(I) -- 3
         I = (I + step)
      -- compare I to max
         -- iteration 3
         print(I) -- 5
         I = (I + step)
      -- compare I to max
         -- iteration 4
         -- etc...
    
    
    So how does the numeric for loop know when to stop?
    Simple it initially evaluates all 3 expressions, the value of I, the max value to meet and the step value if any
    And then compares the value of I to the max value to meet based on the step value.. this sounds confusing I bet
    We might need another example to show what I am referring to when I say based on the step value.
       -- 1st example
         for I = 5, 1, -1 do
           print(I)
         end
         -- prints
         5
         4
         3
         2
         1
    
       -- 2nd example
         for I = 10, 1, -2 do
           print(I)
         end
         -- prints
         10
         8
         6
         4
         2
       
    In the 1st example we are using a negative number (-1) as our step value and assigning I a value of 10 while making the max value to meet 1
    Since the step value + I controls the value to increment per iteration we can still use this same formula as we did for a positive step value

     

    Using this formula per iteration for step being -1.
         I = 10
         step = -1
         max = 1
      -- compare I to max
        -- iteration 1
         print(I) -- 10
         I = (I + step)
      -- compare I to max
         -- iteration 2
         print(I) -- 9
         I = (I + step)
      -- compare I to max
         -- iteration 3
         print(I) -- 8
         I = (I + step)
      -- compare I to max
         -- iteration 4
         -- etc...
    
    

    Using this formula per iteration for step being -2

         I = 10
         step = -2
         max = 1
      -- compare I to max
        -- iteration 1
         print(I) -- 10
         I = (I + step)
      -- compare I to max
         -- iteration 2
         print(I) -- 8
         I = (I + step)
      -- compare I to max
         -- iteration 3
         print(I) -- 6
         I = (I + step)
      -- compare I to max
         -- iteration 4
         -- etc...
    
    

    If we add a negative number to a positive number we are essentially subtracting e.g. 10 + (-2) = 8

     

     

    The Generic For

    The generic for loop is always used on a table, it is define as

         for key, value in pairs(table_name) do
            -- code goes here
         end
         -- or
         for key, value in ipairs(table_name) do
            -- code goes here
         end
       
    
    What a generic for loop does it allows us to easily traverse through a table.
    Early on we referred to tables having properties and indexes well the index of a table can be referred to as its key and a property as its value.
    Lets break apart the generic for loop and explain how it works.
    The generic for loop is initialized using the for keyword (just as you did with the numeric for), it is then assigned a variable to be used to hold the table's key value this variable can be named anything even an underscore ( _ ) as long as it follows lua's naming convention.
    We then place a comma after the key's variable name and create another variable this variable will hold the value of the table's key.
    We then place a space and type the in keyword and then another space and use either pairs or ipairs followed by an open parentheses followed by the table name followed by a closing parentheses and then another space followed by the do keyword and the end keyword to close the generic for loop.

     

    The difference between pairs and ipairs is pairs will iterate over everything in the table regardless if it has a numerical key or not in no particular order. Where as ipairs will only iterate through a table if the table has keys that are numerical.

     

    Lets take a look at both pairs & ipairs to see how they are different.

     

    Using pairs

    	local t = {
    		["one"] = 1,
    		[2] = 23,
    		codex = "my name",
    		50
    	}
    
    	for k, v in pairs(t) do
    		print(k, v)
    	end
    	-- output
    	1   50
    	one   1
    	2   23
    	codex   my name
    
    

     

     

    Using ipairs

    	local t = {
    		["one"] = 1,
    		[2] = 23,
    		codex = "my name",
    		50
    	}
    
    	for k, v in ipairs(t) do
    		print(k, v)
    	end
    	-- output
    	1   50
    	2   23
    
    

    As you can see from the 2 examples pairs printed everything to the screen where as ipairs only printed non-text based keys.

     

    While loop

     

    The while loop although hardly ever used in most scripts (at least in my experience), has a simple structure.

    while(condition) do
        -- code to execute here
    end
    

    As long as the condition of the while loop returns true the while loop will execute. Here is an example of a while loop:

    local I = 1
    while(I < 10) do
       I = I + 1
       print(I)
    end
    -- prints
    2
    3
    4
    5
    6
    7
    8
    9
    10
    

    Repeat Until loop

    The repeat until loop has a similar structure to the while loop.

    repeat
        -- code to execute goes here
    until (condition)
    

    Here is an example of the repeat until loop:

    local I = 1
    repeat
        I = I + 1
        print(I)
    until I > 10
    
    -- prints
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    

     

    I rarely if ever use the while/repeat loops for anything in lua due to the fact that they can cause an infinite loop and crash a program.

     

     

  4. Operators

    Its important that we cover the different type operators which are available to us in lua (and many other languages)

     

    ​Arithmetic Operators

    + The plus sign is the addition operator it is used to add 2 operands together.

     

    Example: (2 + 2) = 4
     

     

    - The hyphen is the minus operator it has 2 purposes 1 of which can negate a value (-2) (aka the urinary operator) and the other subtracts 2 operands.

     

    Example: (2 - 2) = 0
     

     

    / The forward slash is the division operator, it is used to divide 2 operands.

     

    Example: (2 / 2) = 1
     

     

    * The asterisk is known as the multiplication operator it is used to multiply 2 operands.

     

    Example: (2 * 2) = 4
     

     

    % The percentage sign is known as the modulo operator, its purpose is a 2 step process, 1st it divides 2 operands and then returns the remainder of what is left over.

     

    Example: (3 % 2) = 1
     

     

    ^ The caret is called the exponent operator, it takes 2 operands and returns the exponent of

     

    Example: (2 ^ 3) = 8
     

     

     

    Relational Operators

    The relational operators return true or false based on the outcome of their comparison.

    == Two equal signs is known as the equivalence operator, it is used to check whether 2 operands are equal or not

    ~= The tilde and the equal sign is known as the not equal to operator, it essentially checks whether the 2 operands are not equal to one another.

    > The greater than checks to see if the value on the left is greater than the value on the right.

    < The less than checks to see if the value on the left is less then the value on the right.

    >= The greater than or equal to is used to check if the value on the left is greater than or equal to the value on the right.

    <= The less than or equal to checks to see if the value on the left is less than or equal to the value on the right.

     

    Logical Operators

    The logical operators also return true or false based on the outcome of their comparison.

    and The and logical operator checks to see if both operands are true, if they both are true, then a value of true is returned.

     

    Example: (1 == 1 and 2 == 1) -- returns false 
     

     

    or The or logical operator checks to see if either the left or the right operand is true, if either is true then it returns a value of true.

     

    Example: (1 == 1 or 2 == 1) -- returns true
     

     

    not The not logical operator returns the opposite of the condition, if the condition is true not makes it false and vice versa.

     

    Example: ( not true) -- returns false 
     

     

     

    Miscellaneous

    .. The 2 dots are used in concatenation.

     

    Example: print("hello" .. " world!") -- prints hello world!
     

     

    # The hash tag is known as the unary which returns the length of a string or table.

     

    Example: print(#"hello world!") -- prints 12
    Example: print(#{1, 2, 3}) -- prints 3
     

     

     

    The Order of Precedence

    Although in programming you can use parentheses to explicitly direct the order execution of an expression its still important to learn the order of precedence.

    Unary 			not # -  	  (right to left)
    Concatenation 	        ..		  (right to left)
    Multiplicative 	        * / % 	          (left to right)
    Additive		+ - 	 	  (left to right)
    Relational 		< > <= >= == ~=   (left to right)
    Equality 		== ~= 		  (left to right)
    Logical AND 	        and 		  (left to right)
    Logical OR 		or 		  (left to right)
    

     

     

     

    Control Structures

    A control structure is simply a logical operation which is used to control the flow of execution.

    Here is an example of a simple control structure.

    if a > b then
        print("a is greater than b")
    elseif a < b then
        print("b is greater than a")
    else 
        print("both a and b are equal in value.")
    end
    

     

    The above code states, if a is greater than b then print a is greater than b. Else if b is greater than a print b is greater then a, else print both a and b are equal in value.

     

    Another way we can use a control structure is through short hand, using the logical operators to assign values to a table or variable.

    Example:

    -- if the player level is greater than 100 (and) assign 50 to experience
    -- else (or) assign 200 to experience
    local experience = (playerLevel > 100) and 50 or 200
    
    

    The short hand assignment can be nested, I'll expand more on this at a later time. If you are familiar with c++ or even php you've already used shorthand

    notation using ? and :

     

    In php or c/c++ the and/or short hand is very much similar to

    // php
    $experience = ($playerLevel > 100) ? 50 : 200;
    
    // c/c++
    int experience = (playerLevel > 100) ? 50 : 200;
  5. Progress so far, if you add the -console in the shortcut to the target.
    y7siWNR.jpg
    Then while at any point in the game you hit tilde (~) key and a persistent window will overlap the screen, then type help, that is as far as I've gotten so far :P

    u74PgsL.jpg

    Currently I am running one of the dll's (s2logic.dll) through a debugger. This is how I came to the conclusion that the game might have a debug window.
    Initially I did a search for et_duration_sec in notepad++ in txt, exe & dll file types, which lead me to this dll, then I opened it up in x64dbg and did a search for all related strings.

  6. So for instance if I were to create a test function, labeled testFunction.

    function testFunction() return (6000 * 60 * 24) end
    

    Then call it from within entry1, it will execute and return a value of 86400, which will then be applied to the duration value for the spell.

    entry1 = {"et_duration_sec", testFunction(), 25, 0, 8 },
    

    Creating a structure in this fashion allows us to consolidate but also easily manage or update the code.

    The way lua works is that the code is evaluated way before it is sent to whatever called the document, this means that we can write code which manipulates the values prior to sending it to the application. (hopefully that makes sense) :P

  7. Some where down the road all of these values can be simplified, in that functions can be created to automatically execute these values so we won't have to search for them all.

    I've completely re-written game server frameworks to do just that, lua is pretty flexible and easy to work with. :)

  8. I intend to break down each field so that either myself or another member can build a tool which will allow you to automatically update the spells as needed.

    For instance in the above example:

    entry0 = {"et_self_shapeshift", 1000, 1995, 0, 41 },
    

    1995 refers to the creatures id, just using notepad++ I searched the entire spells document for that value where it only found 1 occurrence to the value. This means that its a unique id which only applies to this spell.

    So I did a directory search on the value 1995 and found in creatures.txt amongst other places, more data in relation to the spell.

    mgr.createCreature {
    	id = 1995,
    	itemtype_id = 12484,
    	name = "spell_dm_berserker_shapeshift",
    	behaviour = "Invalid",
    	dangerclass = 7,
    	groupmaxcount = 1,
    	elite_creature_id = 1995,
    	probabilityforelite = 0.000000,
    	rank = 0,
    	tenergy_creature_id = 1995,
    	livesremaining = 0,
    	unconscioustime = 20,
    	palettebits = "1111111111111111",
    	monstertype = 0,
    	faction_id = 1,
    	modelscale = 1.000000,
    	rise_from_ground = 0,
    	has_corpse = 1,
    	has_soul = 1,
    	can_strafe = 0,
    }
    

    It could just be a coincidence but spell_dm_berserker_shapeshift does seem to relate to the original spell.

    Next if I take the itemtype id value found in that block of code and I do a search in the itemtype.txt, I find this.

    newItemType = {
    	-- standard info
    	renderfamily = "RENDERFAM_CREATURE",
    	renderprio   = 0,
    	family       = "FAMILY_CREATURE",
    	subfamily    = "SUBFAM_LIFE_DRAGON",
    	classification = "CLF_DEFAULT",
    	flags        = "FLAG_HASPREVIEWIMAGE",
    	weargroup    = "WEARGROUP_INVALID",
    	-- 3d model + animation info
    	model0Data = {
    	  name         = "models/npc/monsters/draconian/h_draconian.GR2",
    	  user         = "WEARGROUP_INVALID",
    	},
    	-- logic bounding box
      logicBox = {
        minx=-34.466, miny=-56.174, minz=-0.641, 
        maxx=29.876, maxy=38.901, maxz=94.403, 
    	},
    	dangerclass   = 0,
    }
    mgr.typeCreate(12484, newItemType);
    

    All of this data is relevant its just a matter of picking things apart to see how they relate to one another.

    • Like! 1
×
×
  • Create New...
Please Sign In or Sign Up