Jump to content

Dedicated server mod help


death dealah

Recommended Posts

 

 

Hello all,

 

I just rented out a dedicated server so i could play by my own rules etc (will give server info once fully set up).

 

I was wondering if anyone could tell me a quick way to determine if mods are server side only or client side or both?  Maybe the .lua directory that alters the files?  I want to mainly put mods that players dont have to install themselves.  I suppose knowing what is pulled from the players client vs what files are used from the server would help.  I know some will require it installed client side but im trying to avoid that where i can.

 

One last thing is if someone could point me to a script or mod that sends out a welcome pack email (some ore,credits)  on player creation.  My server is on insane with increased difficulty mods so id like to help the beginners out.

Link to comment
Share on other sites

Hmm a bit tricky depending on which mods you want installed, and how much effort you want to put into investigating the mods in question. I'll leave my thoughts.

 

1. Your most obvious indicator is if the mod maker puts in their mod post's description whether this is the case or not (a good point actually: I'll update my own mod post to reflect this).

 

2. You can ask the modders themselves (or people in the community who might know) by asking in the mod's thread.

 

3. You can go through the mod code and see if there are any calls using the specific if onServer() then or if onClient() then methods. These are indicators that the code expects to be run on both, with different functionality running depending on which of the two is the case (i.e. "this is being run on the server" or "this is being run on a client"). invokeServerFunction(...) and invokeClientFunction(...) are also good indicators of the script expecting to be run on both.

 

4. If the mod code scripts only UI things, it might be client-only. Probably not though, because the functionality that the UI is trying to drive or get information from has to come from somewhere, and that somewhere is the server. If the mod code scripts anything at all that you can think of that shouldn't go out-of-synch between clients / be hackable by a client, you can expect this to be run by the server, but this sadly doesn't give you much indication about whether or not the client needs a copy, too.

 

5. You can try and see (trial-and-error approach): load the server up with the mod installed, start a client without the mod installed and try and login to the server. See if the game breaks if you try and do anything that the mod supposedly adds to the game.

 

 

Ultimately, the answer is no (or: not really). There is no obvious surefire way to tell whether a mod needs to be installed both client- and server-side just from looking at the mod itself. However, there are some indicators you can follow to try and figure this out yourself, or you could just launch the server with the mod installed, launch a client without the mod installed, try and join and see if anything breaks. Testing it yourself is usually a good way to go. I would also go asking around on the forums, myself.

 

All-in-all I'd say I can't think of many mods out there that the client will not also need installed for one part of functionality or another, but don't let my inexperience in that matter cloud your judgment. I'd love to hear more about this, actually.

Link to comment
Share on other sites

Thank you for the detailed reply.  Very insightful.  Ill do some testing by logging in with a vanilla client but it is what it is.

 

Anything  to offer as far as the last thing i asked for.  Any mods already created with this?  Ive been searching the forums and dont see it.  I do know stuff like that exists as ive seen it before.

Link to comment
Share on other sites

This explanation is going to need multiple posts. Turns out the forum has a 20k character limit. Yay, forum breaker is I!

 

----

 

TL; DR: I made you a mod that does what you want. I walk you through the steps on how I created the mod, but you can also just install it on the server and client and it should work. Follow the installation instructions at the bottom of my last post in this chain of posts. Enjoy!

 

----

 

PART I

 

I didn't reply at first because I didn't know. But, I've looked at the insurance system code , so let's build what you want together:

 

Basically, we're just going to hack together a welcome e-mail by copying what the insurance mail does, changing the contents, and changing the conditions that trigger it. On top of that, the insurance system is quite complex, and we don't need nearly all of that behaviour for our purposes.

 

So, let's start by copy-pasting the whole insurance.lua script, and then delete what we don't need. We do it this way, so we ensure that all requires, variables and utility functions that are required by the script to function are present, and we don't forget to copy crucial parts of the script that aren't in the main body of our attention. Basically, it's just a safer approach to copy-pasting code, but you can do it however you want.

 

Next, we're going to implement some code to track how often players have logged in to your server, and only deliver the mail when they log in for the first time.

local player = Player(playerIndex)
local server = Server()
local playerLogIns =  server:getValue("player_"..tostring(player.name).."_logIns")

if playerLogIns == 0 or playerLogIns == nil then
playerLogIns = 1
server:setValue("player_"..tostring(player.name).."_logIns", 1)
else
playerLogIns = playerLogIns + 1
server:setValue("player_"..tostring(player.name).."_logIns", playerLogIns)
end

if playerLogIns == 1 then
...
end

Don't worry about where to put this code for now. We'll get to that later. But this is the driving force of our mail: we only send it once, and that's on the first log in, then never again.

 

What does this code do? Let's take a look.

 

First, we set some local variables, to more easily reference certain things like the player, and make the code more readable. Then we use the special functions getValue("VALUENAME") and setValue("VALUENAME", "VALUE") in order to get or set custom values to the server itself. These values will be read later in order to determine how often the player has logged in. Finally, we perform some checks to see if the player hasn't logged in yet (either by the value being 0 somehow or by way of the value not existing yet), and trigger some behaviour if this is indeed the first time the player logs in. Additionally, we count up the amount of times the player has logged in after this event. Perhaps we want to use the total amount of times the player has logged in at some point? But more importantly, this ensures that the code knows when the player logs in after the first time. We could also have used a boolean here (true/false), but I decided to go with counting, instead.

 

A side effect of using this functionality is that it will only start to work after this script is installed on the server. This means that, on existing servers, all players who log on after this script is installed, will get a Welcome Email. This can be circumvented by manually setting the custom values for all players who you know log in to your server, as long as you know their name:

/run Server():setValue("player_PLAYERNAME_logIns", 1)

Executing this by entering it in Chat before any of the players in question have joined the server will ensure that they never get the e-mail (because the value is set to 2 the first time they log in after installing this script). I'm afraid this is a rather manual approach, but there's nothing else to it if we use the set-up we have.

 

Incidentally, you can remove the server custom value by passing the value nil to it. This special lua object will cause the custom value to be deleted, if ever you want to do so.

/run Server():setValue("player_PLAYERNAME_logIns", nil)

 

Fortunately, even if existing players get this mail, you can instruct them to delete the mail rather than pressing Take All and they shouldn't receive the free materials. That is, if you trust your players enough to actually do so. Deleting the mail without pressing Take All will not grant the recipient of the mail the materials or money enclosed within.

 

OK, so we've got the check in place for when we want to deliver the mail. But, we don't have the mail itself, yet. So let's start hacking away at insurance.lua until only the areas of code remain that we are interested in:

package.path = package.path .. ";data/scripts/lib/?.lua"
require ("galaxy")
require ("utility")
require ("stringutility")
require ("faction")

-- Don't remove or alter the following comment, it tells the game the namespace this script lives in. If you remove it, the script will break.
-- namespace Insurance
Insurance = {}

Insurance.refundedValue = 0

local mailText = ""
local mailHeader = ""
local mailSender = ""

function Insurance.restore(values)
    Insurance.refundedValue = values.refundedValue or 0
    mailText = values.mailText or ""
    mailHeader = values.mailHeader or ""
    mailSender = values.mailSender or ""
end

function Insurance.secure()
    return {
        refundedValue = Insurance.refundedValue
    }
end

-- this function gets called on creation of the entity the script is attached to, on client and server
function Insurance.initialize()
    if onServer() then
        local entity = Entity()
        entity:registerCallback("onDestroyed" , "onDestroyed")
    end

    if onClient() then
        invokeServerFunction("setTranslatedMailText",
                             "Loss Payment enclosed"%_t,
                             Insurance.generateInsuranceMailText(),
                             "S.I.I. /* Abbreviation for Ship Insurance Intergalactical, must match with the email signature */"%_t)
    end
end

function Insurance.setTranslatedMailText(header, text, sender)
    mailHeader = header
    mailText = text
    mailSender = sender
end

-- if ship is destroyed this function is called
function Insurance.onDestroyed(index, lastDamageInflictor)
    if Insurance.refundedValue == 0 then return end

    local faction = Faction()
    if not faction then return end

    local ship = Entity()

    -- don't pay if the faction destroyed his ship by himself
    local damagers = {ship:getDamageContributors()}
    if #damagers == 1 and damagers[1] == faction.index then
        Insurance.sendInfo(faction, "Insurance Fraud detected. You won't receive any payments for %s."%_t, ship.name)
        return
    end

    if faction.isPlayer then
        local player = Player(faction.index)

        local mail = Mail()
        mail.header = mailHeader
        mail.text = mailText
        mail.sender = mailSender
        mail.money = Insurance.refundedValue

        player:addMail(mail)
    else
        faction:receive(Format("Received insurance refund for %1%: %2% credits."%_T, ship.name), Insurance.refundedValue)
    end
end

-- following are mail texts sent to the player
function Insurance.generateInsuranceMailText()
    local entity = Entity()
    local receiver = Faction()

    local insurance_loss_payment = [[Dear ${player},

We received notice of the destruction of your craft '${craft}'. Very unfortunate!
As you are insured at our company you shall receive enclosed the sum insured with us as a loss payment.
The contract for your craft '${craft}' is now fulfilled. We hope we can be of future service to you.

Best wishes,
Ship Insurance Intergalactical
]]%_t

    return insurance_loss_payment % {player = receiver.name, craft = entity.name}
end

function Insurance.getUpdateInterval()
    return 1    --Run once per second
end

function Insurance.getValues()
    if onClient() then return end

    return {
        refundedValue = Insurance.refundedValue
    }
end

 

We copy this code and put it in a new file that we call welcomeMail.lua, and place this file inside a new directory in the Avorion installation space that has the following path: Avorion/mods/WelcomeMail/scripts/player/welcomeMail.lua. We could theoretically write part of this code's functionality by editing vanilla files, but I'm a huge fan of doing as little in-place editing as we need, since it leads to easier installation of the mod later down the line. Now, let's go through the code itself.

 

Let's go through this code one step at a time. I've already made some adjustments, but not all.

 

package.path = package.path .. ";data/scripts/lib/?.lua"
require ("galaxy")
require ("utility")
require ("stringutility")
require ("faction")

-- Don't remove or alter the following comment, it tells the game the namespace this script lives in. If you remove it, the script will break.
-- namespace Insurance
Insurance = {}

Insurance.refundedValue = 0

local mailText = ""
local mailHeader = ""
local mailSender = ""

 

The very first part of the code is the initialisation. This is important for the script to run properly. At the top, we are importing local variables and functions from other scripts. We will need all of these to remain for our functionality to work, so we're leaving that untouched. Next up, we have the namespace of the script. Currently, it still reads Insurance, so we're going to change that into WelcomeMail. Finally is variable initialisation. I got rid of some of the variables that had to do with paying for the insurance, but left the one that's about refunding money, since wherever in the code it is that the insurance mail gives you your money, we want to implement the same behaviour, so for now, we're keeping that variable just where it is (we can always get rid of it later).

You'll notice that throughout the code it says namespace.variableName or namespace.functionName. We're going to do a quick find-and-replace to change all instances of Insurance. into WelcomeMail. so now our namespace changes are completed.

 

Next up, there's the following lines of code:

function WelcomeMail.restore(values)
    WelcomeMail.refundedValue = values.refundedValue or 0
    mailText = values.mailText or ""
    mailHeader = values.mailHeader or ""
    mailSender = values.mailSender or ""
end

function WelcomeMail.secure()
    return {
        refundedValue = WelcomeMail.refundedValue
    }
end

 

These are important for storing and retrieving data whenever the server performs a save. It basically ensures that data is preserved when the game loads data back into memory (i.e. when the server starts). Now, I'm not sure we're going to need to store data in our case, but we're going to leave it intact just in case. Better be safe than sorry. (I should get more knowledgeable on how these functions work and when they are needed - but c'est ça: I simply am not right now, so let's just keep these functions as they are.)

I have removed all variables that we also removed from the initialisation block of the code and kept only what we are initialising in the first place.

 

Next up:

 

-- this function gets called on creation of the entity the script is attached to, on client and server
function WelcomeMail.initialize()
    if onServer() then
        local entity = Entity()
        entity:registerCallback("onDestroyed" , "onDestroyed")
    end

    if onClient() then
        invokeServerFunction("setTranslatedMailText",
                             "Loss Payment enclosed"%_t,
                             WelcomeMail.generateInsuranceMailText(),
                             "S.I.I. /* Abbreviation for Ship Insurance Intergalactical, must match with the email signature */"%_t)
    end
end

function WelcomeMail.setTranslatedMailText(header, text, sender)
    mailHeader = header
    mailText = text
    mailSender = sender
end

 

OK, so here is our first bit of actual functionality. initialize() is run - as explained by the comment - whenever the script is run for the first time. It sets the (default) values for our variables, and registers callbacks to make the script execute whenever a certain server callback is triggered.

 

Let's look at the first item on our list: entity:registerCallback. We're not going to want to register a callback in our case, but instead directly call the functions we want to call from inside this initialize() function. This is because the way we will install our script (see the Installation Instructions at the end of this chain of posts) will inherently imply the callback in place here for the original insurance code: this script is run when the player logs in.

 

So, we can remove the line local entity = Entity() entirely and change the line entity:registerCallback("onDestroyed" , "onDestroyed") into print("Welcome Mail initialised!"). Basically, all we're doing if we're running on the Server, is letting the Console (and server logs) know that this script was successfully loaded and has started executing. That's all we care about on the server-side, at this point. We can see if the script is actually running on our server by opening Console with the quote key ( ' and " ) (that's the default keybinding at least: the user may have changed this in the Key Layout Settings).

 

Next, we add another invokeServerFunction(...) call to the if onClient() then test:

invokeServerFunction("onPlayerLogIn", Player().index)

 

OK, so what have we done? We have made sure that instead of calling a function onDestroyed(...) whenever the entity this script would have been attached to dies (as would be the case for the insurance code), that a function named onPlayerLogIn(...) is called whenever a player logs in, and gets passed the player's index (so the script knows which specific player to target later on).

It's the convention for scripts in Avorion to name the function that is registered to trigger on a callback the same as that callback's name, for ease of reading. However, theoretically you could call it whatever you want, especially considering we're not registering a callback at all, but rather just manually invoking a function. Still, for ease of reading and comprehension, I recommend we follow the convention.

The onPlayerLogIn(...) function that should supposedly be executed whenever the invokeServerFunction calling it is triggered doesn't exist in our code yet. Don't worry about that. We'll get to it in a second.

 

Let's move on to a part of the mail's contents.

if onClient() then
    invokeServerFunction("setTranslatedMailText",
                             "Loss Payment enclosed"%_t,
                             WelcomeMail.generateInsuranceMailText(),
                             "S.I.I. /* Abbreviation for Ship Insurance Intergalactical, must match with the email signature */"%_t)
end

...

function WelcomeMail.setTranslatedMailText(header, text, sender)
    mailHeader = header
    mailText = text
    mailSender = sender
end

 

We see here that on the client-side, we make the server run a function that sets the variables we initialised at the top of the code, thereby creating the subject, body and sender of the mail's text. Change this to whatever you want. I'm just providing an example. WelcomeMail.generateInsuranceMailText() is what is now generating the body of the mail, but we'll get to that later.

if onClient() then
invokeServerFunction("setTranslatedMailText",
				 "Some Assets to Help You Get Rolling"%_t,      --message header
				 WelcomeMail.generateWelcomeMailText(Player().index),
				 "Shrooblord /* The owner of this galaxy. Wait, what? */"%_t)       --sender
end

I've also changed WelcomeMail.generateInsuranceMailText() into WelcomeMail.generateWelcomeMailText() for consistency (and to reduce confusion).

 

With all our changes in place, this block of our code now looks like this:

-- this function gets called on creation of the entity the script is attached to, on client and server
function WelcomeMail.initialize()
    if onServer() then
        print("Welcome Mail initialised!")
    end

    if onClient() then
	invokeServerFunction("setTranslatedMailText",
				 "Some Assets to Help You Get Rolling"%_t,      --message header
				 WelcomeMail.generateWelcomeMailText(Player().index),
				 "Shrooblord /* The owner of this galaxy. Wait, what? */"%_t)       --sender
        invokeServerFunction("onPlayerLogIn", Player().index)
    end
end

function WelcomeMail.setTranslatedMailText(header, text, sender)
    mailHeader = header
    mailText = text
    mailSender = sender
end

 

After this is run, the mail is primed for reception - its data is all generated -, but it has not yet been sent. Let's see what happens when the player logs in.

 

TO BE CONTINUED...

 

Link to comment
Share on other sites

PART II

 

-- if ship is destroyed this function is called
function WelcomeMail.onDestroyed(index, lastDamageInflictor)
    if WelcomeMail.refundedValue == 0 then return end

    local faction = Faction()
    if not faction then return end

    local ship = Entity()

    -- don't pay if the faction destroyed his ship by himself
    local damagers = {ship:getDamageContributors()}
    if #damagers == 1 and damagers[1] == faction.index then
        WelcomeMail.sendInfo(faction, "Insurance Fraud detected. You won't receive any payments for %s."%_t, ship.name)
        return
    end

    if faction.isPlayer then
        local player = Player(faction.index)

        local mail = Mail()
        mail.header = mailHeader
        mail.text = mailText
        mail.sender = mailSender
        mail.money = WelcomeMail.refundedValue

        player:addMail(mail)
    else
        faction:receive(Format("Received insurance refund for %1%: %2% credits."%_T, ship.name), WelcomeMail.refundedValue)
    end
end

 

Right. Baby steps!

 

First of all, we're not interested in the onDestroyed callback, but rather the onPlayerLogIn "callback" (remember this is not a true callback execution but rather a server function call that we are manually invoking). This callback has only one argument: playerIndex, which supplies the code contained within the function with the information about just who it was, who just logged in. Player(playerIndex) inside this function will point to that specific player. We'll use this in sending our mail to the player. Let's also change the comment above it to actually reflect when this function gets triggered.

 

So

-- if ship is destroyed this function is called
function WelcomeMail.onDestroyed(index, lastDamageInflictor)

turns into

-- when the player logs in, this function is called
function WelcomeMail.onPlayerLogIn(playerIndex)

 

Looking at the rest of the body of this function, we can tell that all the handlers for a Faction are unnecessary for our code. After all, we never want to send our mail to a whole Faction (Alliance), but always to individual players. So, we can remove all code that checks for Factions and sends messages to Factions rather than players, and also remove the require("faction") part of the initialisation at the top of our script.

 

Now, we want to add some resources in the mail to get the player started.

 

But how do we do that? If we look at the Documentation, we'll see that the Mail() object has a function exposed to it that's called setResources(), but it doesn't tell us how many arguments it takes, or what its arguments are interpreted as. Furthermore, if you search the vanilla code for setResources(), you only get one entry from entitydbg.lua, but its use is a little... confusing, also given the comment that is set beside it. Oh well, we need to figure this one out ourselves. Time for some testing.

if onServer() then
    local player = Player()

    local mail = Mail()
    mail.header = "Test"
    mail.text = "Here's some stuff."
    mail.sender = "Shrooblord"
    mail.money = 50

    mail:setResources(100, 200, 300, 400, 500, 600, 700)

    player:addMail(mail)
end

 

Copy this code to your clipboard and start the game. Open Chat with Enter and type /run (with a space at the end) followed by your pressing CTRL+V to paste the clipboard into the Chat textbox. Don't worry about the text running off the screen. Chat don't care. He's cool like that.

 

When you press Enter, you'll see you've received an e-mail from Shrooblord titled "Test", with the message body being "Here's some stuff." Furthermore, as an attachment to the e-mail is enclosed 50 Credits, 100 Iron, 200 Titanium, 300 Naonite, 400 Trinium, 500 Xanion, 600 Ogonite and 700 Avorion. Cool! So now we know how to use setResources(). To include any materials in the attachment to your e-mail, all you need to do is call

mail:setResources(amountIron, amountTitanium, amountNaonite, amountTrinium, amountXanion, amountOgonite, amountAvorion)

and we're done! Note the colon ( : ) instead of the period ( . ). This is because this is a function call on the object mail.

 

Alright, so let's initialise some variables at the top of our script for these amounts, define them during the initialisation of the mail contents, and actually add them to the mail:

 

WelcomeMail.refundedValue = 0

WelcomeMail.amountIron = 0
WelcomeMail.amountTitanium = 0
WelcomeMail.amountNaonite = 0
WelcomeMail.amountTrinium = 0
WelcomeMail.amountXanion = 0
WelcomeMail.amountOgonite = 0
WelcomeMail.amountAvorion = 0

local mailText = ""
local mailHeader = ""
local mailSender = ""

function WelcomeMail.restore(values)
    WelcomeMail.refundedValue = values.refundedValue or 0
    
    WelcomeMail.amountIron = values.amountIron or 0
    WelcomeMail.amountTitanium = values.amountTitanium or 0
    WelcomeMail.amountNaonite = values.amountNaonite or 0
    WelcomeMail.amountTrinium = values.amountTrinium or 0
    WelcomeMail.amountXanion = values.amountXanion or 0
    WelcomeMail.amountOgonite = values.amountOgonite or 0
    WelcomeMail.amountAvorion = values.amountAvorion or 0
    
    mailText = values.mailText or ""
    mailHeader = values.mailHeader or ""
    mailSender = values.mailSender or ""
end

function WelcomeMail.secure()
    return {
        refundedValue = WelcomeMail.refundedValue,
        amountIron = WelcomeMail.amountIron,
        amountTitanium = WelcomeMail.amountTitanium,
        amountNaonite = WelcomeMail.amountNaonite,
        amountTrinium = WelcomeMail.amountTrinium,
        amountXanion = WelcomeMail.amountXanion,
        amountOgonite = WelcomeMail.amountOgonite,
        amountAvorion = WelcomeMail.amountAvorion
    }
end

...

function WelcomeMail.getValues()
    if onClient() then return end

    return {
        welcomeMoney = WelcomeMail.welcomeMoney,
        amountIron = WelcomeMail.amountIron,
        amountTitanium = WelcomeMail.amountTitanium,
        amountNaonite = WelcomeMail.amountNaonite,
        amountTrinium = WelcomeMail.amountTrinium,
        amountXanion = WelcomeMail.amountXanion,
        amountOgonite = WelcomeMail.amountOgonite,
        amountAvorion = WelcomeMail.amountAvorion
    }
end

There are some obvious ellipses - I have just shown what I updated to incorporate our new variables.

 

After we do all that, and have removed the check for insurance fraud (which isn't necessary for what we want to do), and remove the check for whether the refundedValue == 0 (since we want to support welcome e-mails that don't give money but do give resources), we end up with a very simple function:

-- when the player logs in, this function is called
WelcomeMail.onPlayerLogIn(playerIndex)
    local player = Player(playerIndex)

    local mail = Mail()
    mail.header = mailHeader
    mail.text = mailText
    mail.sender = mailSender
    mail.money = WelcomeMail.refundedValue

    mail:setResources(WelcomeMail.amountIron, WelcomeMail.amountTitanium, WelcomeMail.amountNaonite, WelcomeMail.amountTrinium, WelcomeMail.amountXanion, WelcomeMail.amountOgonite, WelcomeMail.amountAvorion)

    player:addMail(mail)
end

 

Now, let's change the name of the WelcomeMail.refundedValue variable to something a little more sensible. Let's say WelcomeMail.welcomeMoney. A quick find-and-replace does this all for us. Don't forget to also find-and-replace for refundedValue just by itself, since there are the secure(), restore() and getValues() functions that use these names instead of WelcomeMail.refundedValue.

 

That's all cool and all, but we haven't actually set any of the values for the attachments yet. Let's write a new function:

function WelcomeMail.generateWelcomeMailAttachments(attachMoney, attachIron, attachTitanium, attachNaonite, attachTrinium, attachXanion, attachOgonite, attachAvorion)
    WelcomeMail.welcomeMoney = attachMoney or 0
    
    WelcomeMail.amountIron = attachIron or 0
    WelcomeMail.amountTitanium = attachTitanium or 0
    WelcomeMail.amountNaonite = attachNaonite or 0
    WelcomeMail.amountTrinium = attachTrinium or 0
    WelcomeMail.amountXanion = attachXanion or 0
    WelcomeMail.amountOgonite = attachOgonite or 0
    WelcomeMail.amountAvorion = attachAvorion or 0
end

Our function has 8 arguments that can be passed to it. However, because we define inside the function that the local variables are either

  • the value given by the argument; or
  • 0,

we could theoretically call our function using fewer than 8 arguments, and it would work (just keep in mind you'll have to call them in-order, so if you want to give 0 Iron but 100 Titanium, you'll have to call WelcomeMail.generateWelcomeMailAttachments(0, 0, 100)). This isn't strictly necessary, since we only call this function once, and we know we're giving it all the values explicitly (see our rewrite of initialize(), below), but it's good coding practise to create failsafes nonetheless.

 

And don't forget to change the initialize() function, where we'll set these values:

-- this function gets called on creation of the entity the script is attached to, on client and server
function WelcomeMail.initialize()
    if onServer() then
        print("Welcome Mail initialised!")
    end

    if onClient() then
	invokeServerFunction("setTranslatedMailText",
				 "Some Assets to Help You Get Rolling"%_t,      --message header
				 WelcomeMail.generateWelcomeMailText(Player().index),
				 "Shrooblord /* The owner of this galaxy. Wait, what? */"%_t)       --sender
        invokeServerFunction("generateWelcomeMailAttachments",
                                50,  --money
                                100,  --iron
                                200,  --titanium
                                300,  --naonite
                                400,  --trinium
                                500,  --xanion
                                600,  --ogonite
                                700)  --avorion
        invokeServerFunction("onPlayerLogIn", Player().index)
    end
end

 

You can change the values of all the attachments yourself, of course, but I just did it this way so it's clear for testing purposes which values corresponded to which materials (so basically: that we can test and see that it works correctly).

 

All we need to do at this point is change the body of the mail:

-- following are mail texts sent to the player
function WelcomeMail.generateInsuranceMailText()
    local entity = Entity()
    local receiver = Faction()

    local insurance_loss_payment = [[Dear ${player},

We received notice of the destruction of your craft '${craft}'. Very unfortunate!
As you are insured at our company you shall receive enclosed the sum insured with us as a loss payment.
The contract for your craft '${craft}' is now fulfilled. We hope we can be of future service to you.

Best wishes,
Ship Insurance Intergalactical
]]%_t

    return insurance_loss_payment % {player = receiver.name, craft = entity.name}
end

Remember we changed the function name to WelcomeMail.generateWelcomeMailText()? Let's not forget to include that change here.

 

Furthermore, we don't need to include the Faction or Entity calls, as we're addressing the player directly and don't need any information about the ship they're currently flying. Instead, we only want to know their player name. We can retrieve this if we know the player's index, which we are going to pass to it as an argument. So let's change this block of code into:

-- following are mail texts sent to the player
function WelcomeMail.generateWelcomeMailText(playerIndex)
    local receiver = Player(playerIndex)
    
    local welcome_text = [[Welcome, ${player},

We see you are new here. Good to have you on board!
Included in this e-mail is a bit of a start-up capital for you to work with.
We hope this will help you get going more easily, and give you a little bit
of a leg-up in this dangerous Galaxy... good luck out there!
    
May our paths once cross. Until then!

Best wishes,


Your Galactic Overlords
]]%_t

    return welcome_text % {player = receiver.name}
end

 

Naturally, you can change it into whatever you want. This is just an idea and mostly a placeholder for whatever it is you want to put in there.

 

Remember to also change the WelcomeMail.generateWelcomeMailText() call in initialize() into WelcomeMail.generateWelcomeMailText(Player().index), or you will have a very unhappy game.

 

This is the final piece of code to run through:

function WelcomeMail.getUpdateInterval()
    local minutes = 5
return minutes * 60
end

function WelcomeMail.getValues()
    if onClient() then return end

    return {
        welcomeMoney = WelcomeMail.welcomeMoney,
        amountIron = WelcomeMail.amountIron,
        amountTitanium = WelcomeMail.amountTitanium,
        amountNaonite = WelcomeMail.amountNaonite,
        amountTrinium = WelcomeMail.amountTrinium,
        amountXanion = WelcomeMail.amountXanion,
        amountOgonite = WelcomeMail.amountOgonite,
        amountAvorion = WelcomeMail.amountAvorion
    }
end

We won't need the getUpdateInterval() function because we only intend to execute this function once, on player log in. We will terminate() after we're done, so the script will end (and remove itself from the player, too).

I added our new variables to the getValues() function. To be honest, I'm not quite sure when this function is called, but I kept it in-place just in case the game needs access to these values for some reason.

 

Ah, but wait! We have forgotten one crucial part of this process! We do not check yet whether the player has logged in for the first time, or whether they are returning to the server. Remember that block of code we prepared way at the start? Time to integrate it into our script now.

 

Let's go back to our WelcomeMail.onPlayerLogIn(playerIndex) function definition and put our check in place:

-- when the player logs in, this function is called
function WelcomeMail.onPlayerLogIn(playerIndex)
    if onServer() then 
        local player = Player(playerIndex)
        local server = Server()
        local playerLogIns =  server:getValue("player_"..tostring(player.name).."_logIns")
        
        if playerLogIns == 0 or playerLogIns == nil then
            playerLogIns = 1
            server:setValue("player_"..tostring(player.name).."_logIns", 1)
        else
            playerLogIns = playerLogIns + 1
            server:setValue("player_"..tostring(player.name).."_logIns", playerLogIns)
        end
        
        if playerLogIns == 1 then

            local mail = Mail()
            mail.header = mailHeader
            mail.text = mailText
            mail.sender = mailSender
            mail.money = WelcomeMail.welcomeMoney

            mail:setResources(WelcomeMail.amountIron, WelcomeMail.amountTitanium, WelcomeMail.amountNaonite, WelcomeMail.amountTrinium, WelcomeMail.amountXanion, WelcomeMail.amountOgonite, WelcomeMail.amountAvorion)

            player:addMail(mail)
        else
            print("WelcomeMail: Number of times player "..tostring(player.name).." has logged in: "..tostring(playerLogIns).." times; not generating welcome mail.")
        end
    end
    --regardless of whether re run on server or client, we will end script execution as soon as we're done sending or not sending the mail
    terminate()
end

Our last effort of labours: we now only execute aaaalll the functionality in this script if the player logs in, and at that time their total number of log-ins is 1. In other words, they've just joined the server for the first time, so they get a welcome e-mail! Finally, we terminate() the script whether they got an e-mail or not, because we only want to run this script once: on player log-in.

 

The if onServer() then check is probably redundant, but it can't hurt to make sure.

 

----

 

All-in-all, our full script is:

 

 

TO BE CONTINUED...

Link to comment
Share on other sites

PART III (FINAL)

 

package.path = package.path .. ";data/scripts/lib/?.lua"
require ("galaxy")
require ("utility")
require ("stringutility")

-- Don't remove or alter the following comment, it tells the game the namespace this script lives in. If you remove it, the script will break.
-- namespace WelcomeMail
WelcomeMail = {}

WelcomeMail.welcomeMoney = 0

WelcomeMail.amountIron = 0
WelcomeMail.amountTitanium = 0
WelcomeMail.amountNaonite = 0
WelcomeMail.amountTrinium = 0
WelcomeMail.amountXanion = 0
WelcomeMail.amountOgonite = 0
WelcomeMail.amountAvorion = 0

local mailText = ""
local mailHeader = ""
local mailSender = ""

function WelcomeMail.restore(values)
    WelcomeMail.welcomeMoney = values.welcomeMoney or 0
    
    WelcomeMail.amountIron = values.amountIron or 0
    WelcomeMail.amountTitanium = values.amountTitanium or 0
    WelcomeMail.amountNaonite = values.amountNaonite or 0
    WelcomeMail.amountTrinium = values.amountTrinium or 0
    WelcomeMail.amountXanion = values.amountXanion or 0
    WelcomeMail.amountOgonite = values.amountOgonite or 0
    WelcomeMail.amountAvorion = values.amountAvorion or 0
    
    mailText = values.mailText or ""
    mailHeader = values.mailHeader or ""
    mailSender = values.mailSender or ""
end

function WelcomeMail.secure()
    return {
        welcomeMoney = WelcomeMail.welcomeMoney,
        amountIron = WelcomeMail.amountIron,
        amountTitanium = WelcomeMail.amountTitanium,
        amountNaonite = WelcomeMail.amountNaonite,
        amountTrinium = WelcomeMail.amountTrinium,
        amountXanion = WelcomeMail.amountXanion,
        amountOgonite = WelcomeMail.amountOgonite,
        amountAvorion = WelcomeMail.amountAvorion
    }
end

-- this function gets called on creation of the entity the script is attached to, on client and server
function WelcomeMail.initialize()
    if onServer() then
        print("Welcome Mail initialised!")
    end

    if onClient() then
	invokeServerFunction("setTranslatedMailText",
				 "Some Assets to Help You Get Rolling"%_t,      --message header
				 WelcomeMail.generateWelcomeMailText(Player().index),
				 "Shrooblord /* The owner of this galaxy. Wait, what? */"%_t)       --sender
        invokeServerFunction("generateWelcomeMailAttachments",
                                50,  --money
                                100,  --iron
                                200,  --titanium
                                300,  --naonite
                                400,  --trinium
                                500,  --xanion
                                600,  --ogonite
                                700)  --avorion
        invokeServerFunction("onPlayerLogIn", Player().index)
    end
end

function WelcomeMail.setTranslatedMailText(header, text, sender)
    mailHeader = header
    mailText = text
    mailSender = sender
end

-- when the player logs in, this function is called
function WelcomeMail.onPlayerLogIn(playerIndex)
    if onServer() then 
        local player = Player(playerIndex)
        local server = Server()
        local playerLogIns =  server:getValue("player_"..tostring(player.name).."_logIns")
        
        if playerLogIns == 0 or playerLogIns == nil then
            playerLogIns = 1
            server:setValue("player_"..tostring(player.name).."_logIns", 1)
        else
            playerLogIns = playerLogIns + 1
            server:setValue("player_"..tostring(player.name).."_logIns", playerLogIns)
        end
        
        if playerLogIns == 1 then

            local mail = Mail()
            mail.header = mailHeader
            mail.text = mailText
            mail.sender = mailSender
            mail.money = WelcomeMail.welcomeMoney

            mail:setResources(WelcomeMail.amountIron, WelcomeMail.amountTitanium, WelcomeMail.amountNaonite, WelcomeMail.amountTrinium, WelcomeMail.amountXanion, WelcomeMail.amountOgonite, WelcomeMail.amountAvorion)

            player:addMail(mail)
        else
            print("WelcomeMail: Number of times player "..tostring(player.name).." has logged in: "..tostring(playerLogIns).." times; not generating welcome mail.")
        end
    end
    --regardless of whether re run on server or client, we will end script execution as soon as we're done sending or not sending the mail
    terminate()
end

function WelcomeMail.generateWelcomeMailAttachments(attachMoney, attachIron, attachTitanium, attachNaonite, attachTrinium, attachXanion, attachOgonite, attachAvorion)
    WelcomeMail.welcomeMoney = attachMoney or 0
    
    WelcomeMail.amountIron = attachIron or 0
    WelcomeMail.amountTitanium = attachTitanium or 0
    WelcomeMail.amountNaonite = attachNaonite or 0
    WelcomeMail.amountTrinium = attachTrinium or 0
    WelcomeMail.amountXanion = attachXanion or 0
    WelcomeMail.amountOgonite = attachOgonite or 0
    WelcomeMail.amountAvorion = attachAvorion or 0
end

-- following are mail texts sent to the player
function WelcomeMail.generateWelcomeMailText(playerIndex)
    local receiver = Player(playerIndex)
    
    local welcome_text = [[Welcome, ${player},

We see you are new here. Good to have you on board!
Included in this e-mail is a bit of a start-up capital for you to work with.
We hope this will help you get going more easily, and give you a little bit
of a leg-up in this dangerous Galaxy... good luck out there!
    
May our paths once cross. Until then!

Best wishes,


Your Galactic Overlords
]]%_t

    return welcome_text % {player = receiver.name}
end

function WelcomeMail.getValues()
    if onClient() then return end

    return {
        welcomeMoney = WelcomeMail.welcomeMoney,
        amountIron = WelcomeMail.amountIron,
        amountTitanium = WelcomeMail.amountTitanium,
        amountNaonite = WelcomeMail.amountNaonite,
        amountTrinium = WelcomeMail.amountTrinium,
        amountXanion = WelcomeMail.amountXanion,
        amountOgonite = WelcomeMail.amountOgonite,
        amountAvorion = WelcomeMail.amountAvorion
    }
end

return WelcomeMail

 

 

So there you have it. We have generated a welcome mail.

 

----

 

INSTALLATION INSTRUTIONS

TL; DR version:

 

 

See this forum post.

 

 

 

Finally, we register our script to be run by the server by editing the vanilla file Avorion/data/scripts/server/server.lua to include this script whenever it runs:

player:addScriptOnce("mods/WelcomeMail/scripts/player/welcomeMail.lua")                                   --WelcomeMail

We insert this code inside the onPlayerLogIn call. If you had a vanilla server.lua file, that function will now look like this:

function onPlayerLogIn(playerIndex)
    local player = Player(playerIndex)
    Server():broadcastChatMessage("Server", 0, "Player %s joined the galaxy"%_t, player.name)

    player:addScriptOnce("headhunter.lua")
    player:addScriptOnce("eventscheduler.lua")
    player:addScriptOnce("story/spawnswoks.lua")
    player:addScriptOnce("story/spawnai.lua")
    player:addScriptOnce("story/spawnguardian.lua")
    player:addScriptOnce("story/spawnadventurer.lua")
    player:addScriptOnce("mods/WelcomeMail/scripts/player/welcomeMail.lua")                                   --WelcomeMail
    
    matchResources(player)
end

 

Sadly, we cannot make mods yet without editing even a little bit of vanilla code: c'est la vie.

 

----

 

Nice. But let's test this in action before we say "we did it!!" prematurely. I'm going to generate a new galaxy, and log in. Do we receive a mail?

Kspu1m4.png

 

Hell, yeah, we do!

 

What happens when we log in a second time? No e-mail, and Console prints the number of times we've logged in (2 in this case)!

 

And we don't get an e-mail when we log into a galaxy we've already been playing in for a while. Excellent! Even when we log into a Galaxy within which we were piloting an Alliance ship, everything works as expected. Great!

 

----

 

All of that done, we have built ourselves your welcome mail mod! Let's install it on both our server and our client, and we're good to go. :)

 

You can find the mod in this forum post.

 

NOTE: A side effect of having to install this mod on both client and server means that new Galaxies created in singleplayer will also trigger the Welcome Mail. There's probably a check we can install to make sure this doesn't happen, but that was a little beyond the scope of this tutorial.

 

 

P.S.

There's an entry in the Documentation about mails. Look at Avorion/Documentation/Mail.html if you're curious where I got some of this info. In general, the Documentation is a great place to get started whenever you're curious about (adding) some functionality in Avorion.

Link to comment
Share on other sites

WOW man I cant believe you helped me this much.  Thank you so much.  Its funny you used the insurance.lua because that's exactly what I was looking at and was gonna add what I thought were a simple few lines (wrong) to onplayercreate part of server as that should only happen once per character.  This has opened a whole new world for me! Thanks again.  I guess I should have looked also into the documentation as I don't quite know all the syntax yet.

Link to comment
Share on other sites

Your best bet at learning new code is to use a couple of different resources:

  • The Documentation has a great expansive list of all the functions and properties that are available to you. I recommend a text editor that allows you to search these documents, as finding your way around using the navigation buttons alone may not be as intuitive as you hope (and also, sometimes you simply don't know in what part of the Docs a specific function call would be housed).
     
     
  • Study the vanilla code for behaviour that closely matches what you want to implement. If your idea is novel, there's a good chance you can still base part of it on how vanilla handles a couple of things. I found especially the Sector generation and Entity interaction commands to be quite informative on the whole structure of how most of Avorion operates. The Entity Debugger does a lot of wacky stuff that isn't scripted anywhere else, and it too is a good source for learning some neat tricks.
     
     
  • Look at how some modders have applied new code to existing functionality, or found ways to integrate completely new code into the game. Some of the methodology they use is rather elegant and inspiring, and I for one can still take a good couple of notes from their work.
     
     
  • Experiment. A lot. Adding a simple script and using print(...) commands is a great help to debug what is going on, since we don't have an actual debugger to use. You'll find, though, that even though this is true, you'll rarely actually crash the game. Often you'll find an error is explained in much detail, with a full stack traceback, in the Console (open by pressing the quote key (' and ")). I've only crashed my game once, and I'm still not sure how, when I did. xD
     
    Another great asset are functions like printTable(...), tostring(...) and the /run ... command in-game. I've also attached the Entity Debugger to my own ship by default by adding Config.Add("data/scripts/lib/entitydbg.lua") to my Ship Script Loader Config, which is an immense help.

 

Good luck out there, and have fun modding!

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
×
×
  • Create New...