Lua Scripting Tutorial for Star Wars EAW / FOC

Mehr
27 Mär 2015 02:31 #72000 von Imperial

Beginner Lua Scripting Tutorial for Star Wars Empire at War / Forces of Corruption



-// Introduction
[/color]

Hey there,

Although there already is one tutorial on the Internet which adresses this topic I thought, I could provide a different and deeper view into the (special) and weird way EAW is handling lua.

Why would you want to learn lua coding for Empire at War?

If you are one of the few guys who enjoy tinkering around on .xml files and already experienced first modifications on the game assets you might want to do "more" things.

Although the .xml engine offers a lot of simple parameters you could manipulate, I think the lua part of this hybrid game engine is the more powerful one.

And probably the biggest benefit; you don't have to always restart the damn game for testing changes you applied to the lua script.

It will offer you additional options to get the engine doing almost what ever you want it to.

Additionally it is a powerful language, often used for games and quite easy to learn.
LUA doesn't need to be compiled, which is a great advantage.

This is a tutorial for beginners so you don't need to know anything about coding/scripting at all, but it would be quite helpfull if you already had some experiences in any other programing language, especially C languages. But a tad of basic technical understanding for the way computers work is indeed required.

I never planned to make this tutorial as long as half of a damn book, so I'm sorry for this wall of text. I rather explain a bit too much then leaving open questions. :lol:


-// Step 1: Preparing Editor
[/color]

The first thing you will need is a text editor or a IDE (Integrated Development Environment) for Lua editing.
Any text editor with lua interpreter will do it, some freeware suggestions:

- PSPad Editor is very suited for .xml editing and easy to use, that's why I am using it, Download:
www.pspad.com/en/download.php

- How to set up PSPad for Lua:

Select Settings menu > Highlighter settings.
Select Specification tab.
On the left list, select one of the "<not assigned>" tags and on the right site scroll down and choose Lua from the list > ok.

- You might want to select the "Lua" tag on the left we just added and edit the "Colors" tag. Here you can set for example comments to be displayed in green and strings in purple, so highlighter colors have the same meaning like for Xml.


Integrated Development Environments you could use instead:
- Code::Blocks
- Eclipse




-// Step 2: Learning the stock lua language
[/color]

First of all you need to learn the basic syntax of lua and the way it actually functions.

Now I wont cover this part completely myself because.. well there are a bunch of really good tutorials on youtube and on the Internet out there.

All you need to do is finding a good tutorial playlist on youtube, so go ahead and search the web for a good lua tutorial, sit down with a bottle of coffee and watch these tutorial footages.
Please create a simple .txt or .doc file and note everything into it that seems to be important information about LUA.

But, you indeed will need to invest about 2 weeks for the learning process!

If you speak german, here is a short tutorial series (the one I used for my studies):
www.youtube.com/playlist?list=PL4a0LdhdT...gHv431MQ_lH2C4pTxCKX


-/ However, I will provide: Some basic Notes and explanations about LUA:
[/color]

Sings
Each language has its own semantic and operators, often similar to other languages. This is the meaning in LUA:

- Minus
+ Plus
* Multiplication
/ Division

< smaller then
> bigger then (aviously)
= Setts a variable to the value that follows after =
~ This sign means NOT, but you can use this also as text operator, simply writing "not"
== Used to compare if two values are the same
~= Used to compare if two values are NOT the same
<= smaller or equal to
>= bigger or equal to

or = or
and = and (aviosly)

. Calls a function, you need to have one variable or object in front of the. dot and the function you want to call after the .dot, so variable.function() = variable call function()

.. Two dots are concatenating strings.
1..2 = 12
... Means "and so on"

; Semicolons can be written after each function like in C++, but they don't have to, only if you are used to it and like to close each line with them.
"" Two of these signs will cause Lua to interprete everyting "inside" of them as a Text String!
_ DON'T use empty space between two words of a variable, because Lua interprets empty space as next variable/parameter/command, use _ instead "between_string_words"

-- = two - signs initiate ONE line of comments, which means everything in this line is a comment and wont be interpreted as code!

--[[ = Opens a comment that goes
through
as many lines as needed
]] = closes the comment started by --[[

Would cause script crashes if you forget to close a comment. Or if you start another comment inside of a --comment using --

- nil = zero = If a variable has no value, nil will automatically be assigned to that variable.
- return = callback = return function.


- Functions ()
Lua works with functions, each function ends with brackets().
The brackets can contain multiple values for the parameters.
FunctionName(parameter_1, parameter_2)

-/ Variables
Every programming language needs variables, to dynamically change values and to store values and text strings.

Actually all programs and algorythms, generally we can distinguish all information in 2 parts: Data and Interpretation of Data. Because you can store Data on Stones, Books, in your Head, on CDs, HDs, in the Air using Radio waves or Wifi, RNA or almost anything else. This data wont do anything without a algorythm that interprets what to do with it. So we store our Data in variables, and our scripts are processing this data to do what ever we order it.

You can use different types of variables:

- integer = int = numbers
- boolean = bool = 1 or 2 = true or false
- "string" = Text Strings
- float
- table = list = we will talk later about these


Lua handles these variables quite automatically, so mostly you don't need to define any variable for a certain type, except for the "strings".

- Local Variables
Lua distinguishes between "local" and "global" variables. For example the local value "test" in function A is only accessible for function A and in function B "test" is a other variable that might have the same name but can have a different value.
You can declare any variable (or you can also define functions) as local using this syntax:

local VariableName = 22

Here I just defined the variable VariableName to be local and assigned the value 22 to it. Once any function uses this variable it will insert 22 into its place. But a other function wont be able to call this variable, and in many games it will cause a script crash if a script refers to a unknown variable!

- _G = Global Variables
They are accessible from every function.

- By the way, this is how you initiate 2 variables at the same time if they have the same value:

variable_01, variable_02 = value


- Initiation
Mostly, important and often used global variables need to be initiated at the very top of the script.
I initially set them often to 0, nil or false. Then within the script they change their values.


- Tables = Arrays = Lists
{} These kind of brackets usually are starting and closing tables, which means they are a kind of list varible!
A list with a index sequence. You can think about it as it were a shopping list, a simple list with values.

[] These kind of brackets are used to refer to the index of a table/list.

Example for a Table, this is how Lua sees a table:

Table_List_01 =
{
[1] = Tomatos,
[2] = Milk,
[3] = Beer,
[4] = Chips,
[5] = Vodka,

}

Now if I want to buy only 1 item from my shopping list, I would write it into a new variable, and this is how I would refer to it:

I_will_Buy = Table_List_01[3]

What I just did is; I assigned the value nr.3 (= Beer) from the table Table_List_01 into the new variable I_will_Buy.
Now when my script runs and it calls "I_will_Buy", it will replace this variable by Table_List_01[3], which is Beer ^^

You would probably write a table this way:

Table_List_02 = {value_1, "value_2", value_3, "value_4"}


Do you notice anything?
value_2 and value_4 are "Text_Strings" because of the "signs"

-/ The table.insert function:

table.insert (TableName, VariableToAdd)

Example:

table.insert (Table_List_01, "Tee")


This line inserts another variable "Tee" into the table "Table_List_01", our shopping list from above.
Tee is a string and will be added into the next free index slot, which is [6] because we already have 5 items in the list.


table.remove(TableName, VariableToRemove) = does the opposite

table.remove(TableName, [2])



[/color]
-// Step 3: Reading "A guide to campaign scripting"

[/color]

The other tutorial regarding this topic I meant in the introduction was written by the user .Pox ob Moddb.
It describes in a very short and clear way almost everything you need to know about Lua and Story Scripting in EAW/FOC:

www.moddb.com/games/star-wars-empire-at-...o-campaign-scripting

Simply read this guide 1 or 2 times to get into the stuff.
Dont worry if you don't understand everything, that's ok.

Its good if you have a Idea how the .xml engine is working and what file is initiated where. Because, well this game engine requires all .xml files of one kind to be registered in a certain .xml root file.

In the first 2 Chapters .Pox explains the xml story engine, and from Chapter 3 on the lua (story scripting) stuff.
I will explain you the details later, first you need a basic overview.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
27 Mär 2015 02:39 #72001 von Imperial

-// Step 4: The actual Tutorial
[/color]

If you wish to understand more about the .xml engine, you can view Part 10 - Part 16 of my Modding Tutorial Play list on youtube:

www.youtube.com/playlist?list=PLild8kZ8-...aGqLWuJdGFSSXlrv3B_9

For the understanding of how ground Unit turrets are supposed to be rigged, we have nardels awesome tutorial:


- From the Top to the Bottom
The very first lesson you need to understand is, that every .xml and every .lua script and every executable all together runs always from the top to the bottom. In quite the same way you read a news paper, the computer reads and interprets the code.. but 10.000x faster.
This is why it might appear to someone like the computer was executing everything at the same time, but actually the computer often runs through such scripts within a single second.


Its important to know this to have the code running in your imagination, so you can kind of follow the route of the code through the script (because it is often jumping up to a function then back down and so on.. ) but within these functions it runs.. from the top to the bottom.

- if statements:

they need to start with "if" and to have a "then" after the condition, and to have a "end". Otherwise it causes a syntax error which crashes the script!

Here is a example for a correct if statement:

variable_01 = 5

-- If the variable_01 we just initiated above is not false, we set variable_02:
if variable_01 ~= false then
variable_02 = WhatEver
end

Example for a correct if-elseif statement:

if condition_1 then
do something
elseif condition_2 then
do something else
elseif condition_3 then
do something else
else
do this if none of the conditions above was fullfilled
end


I usually leave 3 empty spaces in each new layer of if statements, so you can see clearer which statement starts/ends where. Like in this example:

if condition_1 then
do something

if condition_1 then
do something
end
end


So above we have an if statement inside of a other if statement.

if condition_1 and condition_2
or condition_3 then

do something
end


And here you can simply use a "and" operator instead of 2 separate if statements!
You can chain as many "and" and "or" Conditions as you like.
Important: there should be only ONE "then" at the end of the chain.

Actually, in 90% of all script crashes I check the last if, elseif, else statements I worked on in the last time and find a mistake there. You know it was probably caused by an if statement or for loop syntax error if the game don't seem to load the script at all.

Mostly forgetting to end a statement using "end", or a "end" operator too much. Often if and in the same statement another "if" instead of "elseif". Or simply wrote If with capital i instead of using a small one.

Please have another look at chapter 3 of .Poxs "Guide to campaign scripting":

www.moddb.com/games/star-wars-empire-at-...o-campaign-scripting

Here he described every statement:
if, for, while, repeat

I know they look complicated, but after you got some practise they will be very easy to you.
Each of them is very useful in a certain situation.
You have to know at least how to use the following statement:

if condition_1 then
do something
elseif condition_2 then
do something else
else
do this if none of the conditions above was fullfilled
end



-// 5: Understanding Folder structure and initiation
[/color]

As mentioned in the introduction, this game handles Lua in a special way. It has its own functions and runs them not in the same way as stock lua code does.
First I'm going to teach you about the file structure of the scripts:

The two directories of the vanilla game for scripts are:

.\LucasArts\Star Wars Empire at War\GameData\Data\SCRIPTS
.\LucasArts\Star Wars Empire at War Forces of Corruption\Data\SCRIPTS


But they are compiled and you'll need to install the map editor for Eaw or Foc in order for the vanilla scripts to pop up (in a readable state) in the .\Mods\Source\Data\Scripts folder of your game.. I recommend to have a look at all scripts of the vanilla game there, so you can see how they work.
(Thank you Locutus for this additional info.)

Each Mod can create a own instance of this Directory in:
.\LucasArts\Star Wars Empire at War Forces of Corruption\Mods\ModName\Data\Scripts\GameObject
And the game overwrites files with the same name (so scripts as well) of the Mods directory over Vanilla files.

In these 3 Directories there are many subdirectorys. But the only important ones are:

.\Data\Scripts\Ai = Ai relevant stuff
.\Data\Scripts\Library = We need to load stuff from here because of dependencies


.\Data\Scripts\GameObject =
VERY IMPORTANT: each Spaceship/Spacestation/Groundunit runs its costum script from here

.\Data\Scripts\Story =
Very Important: Here we have the Story.txt files which contains references to text strings in the .\Data\Text\Text.dat file. We need these references to be displayed for scripted events.

And this Directory also contains the story.lua scripts of the GCs. This is how you can initiate it:

-// 6: Story Scripts
[/color]
.\LucasArts\Star Wars Empire at War Forces of Corruption\Mods\ModName\Data\Xml\CampaignFiles.xml Is the root file for Story GCs.

Each GC_Name.xml file has to be initiated there inside of <File>GC_Name.xml</File>


Pox already explained at chapter 2.1 that in your GC_Name.xml, in the tag of the active fraction (each fraction got a own story part):

<Rebel_Story_Name></Rebel_Story_Name>
<Empire_Story_Name></Empire_Story_Name>
<Underworld_Story_Name></Underworld_Story_Name>

[/color]

There you need the name of a story_plot.xml file initiated, which connects to Story.xml and Story.lua scripts.

In your story_plot.xml you need to insert the name (without .lua) of a valid story.lua script. This story.lua script needs to be placed in .\Data\Scripts\Story:

<Story_Mode_Plots>

<Lua_Script>story</Lua_Script>
<Active_Plot>story.xml</Active_Plot>

</Story_Mode_Plots>


But the .xml file needs its .xml suffix

This is the only way you can get Lua STORYscripts running on GALACTICAL layer.


To get a story.lua script running on PLANETARY layer, its very similar:

make a story_plot.xml with

<Lua_Script>story</Lua_Script>
<Active_Plot>story.xml</Active_Plot>


As above. (Of course the name has to be different to the other one)
Then start the planetary space battle using a Link_Tactical event as described by Pox. You need to write the name of the story_plot.xml into <Reward_Param7> of the Link_Tactical event or it wont work.

I know this whole initiating and initiating the shit out of all scripts is complicated and dumb.. I hate it as well, but this is how this underdeveloped engine works.


-// 7: Game Object Scripts
[/color]

Luckily initiating a script inside of .\Data\Scripts\GameObject is quite easy: Simply add the Tag

<Lua_Script>ScriptName</Lua_Script>


To any Space Building/Unit or Ground Buiding/Unit or MiscObject, or Projectile or what ever Xml Object.
Again: It wont work if you write a .lua suffix.

And if you write

<Lua_Script>ScriptName_1</Lua_Script>
<Lua_Script>ScriptName_2</Lua_Script>


For inside of the same unit, ScriptName_2 will be taken and ScriptName_1 will be ignored because the scripts RUNS from the TOP to the BOTTOM and the Lua_Script is probably just a variable which changes to the newer variable.


-// 08. Writing a Script
[/color]

-/ Getting Dependencies
At the beginning of each script you need to load basic lua script Libraries your script will depend on. But don't worry, usually all you need are these two:

require("PGStateMachine")
require("PGStoryMode")


- PGStateMachine
Is needed for ALL scripts inside of the .\Data\Scripts\GameObject directory.
As the name says: it probably controls the States. I will tell you later about the states.

- PGStoryMode
Is needed for ALL scripts inside of the .\Data\Scripts\Story directory.
It is required for some functions, like spawning units using the "SpawnList" function.

Although you often only need 1 of them for GameObject or for Story content, I recommend requiring both at the beginning of each new script. Sometimes I myself wondered why SpawnList wont spawn, then I remember.. ups forgot to require PGStoryMode.

----

- function Definitions()
This function is always the first one.
And it is only supposed for setup of global variables.

For EAW, you don't need to write "global" in front of global variables.
And you must not write local in front of any variable inside of function Definitions, or it will crash the script and the game!!

But it is always good to initiate local variables inside of any other function then this one.
This is how it would look in Story scripts:

function Definitions()

StoryModeEvents = {
Universal_Story_Start = Universal_Story_Start,
Event_01 = State_01,
Event_02 = State_02
}


variable_01 = false
variable_02 = "Unit_Name"
variable_03 = blabla


end


IMPORTANT; There is a difference between the story scripts in .\Data\Scripts\Story and the Object scripts in .\Data\Scripts\GameObject:

For storys scripts you are supposed to use StoryModeEvents as above, which listens for events in the story.xml file to fire after their activation. You should insert all new states in the list of this function.

For object scripts you need to initiate each new state using a seperate Define_State line!

And this is how it would look in GameObject scripts:

function Definitions()

-- Every State has to be initiated here or it wont work
Define_State("State_Init", State_Init)
Define_State("State_Inactive", State_Inactive)
Define_State("State_Active", State_Active)

variable_01 = false
variable_02 = "Unit_Name"
variable_03 = blabla


end



The next thing that comes after requiring the needed dependencies and the Definitions function is a state (usually state_init).

Then you can chain as many states you like under them, as long you don't forget to initiate them with Define_State() in the Definitions function.

Note:
You must not write a -- comment into a --comment or it crashes the script

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
27 Mär 2015 02:41 #72002 von Imperial

-//09. States
[/color]

Usually you would simply make a new function and run this function, but Star Wars Empire at War rather uses so called States then functions.

What is a State?
Its a function that can be executed once (OnEnter), (OnExit) or repeating on each refresh (OnUpdate).

The state runs from the top to the bottom and then you can start the next state and leave the current one.

States have to be closed with "end", exactly like normal functions. But both States and normal Functions must NOT have a "local" in front or they cause script crash.

(You can have now another look at chapter 3.3 of Poxs tutorial.)

-/ GameObject Initiation
CAUTION: Be aware that there is a difference between GameObject scripts and Story scripts:

EACH state for lua scripts in the .\Data\Scripts\GameObject Directory requires to be initiated this way within the Definitions Function:

Define_State("State_Init", State_Init)
Define_State("State_01", State_01)
Define_State("State_02", State_02)


And the script needs to have at least 1 state, the State_Init state, which is usually always the first one. It initiates the script.
Never write "Find_All_Objects_Of_Type()" into the Definitions function, write it into State_Init or the other states instead, because Definitions function is for variable setup only. And "Find_First_Object()" better avoid as well.

-/ Story Initiation
EACH state for lua scripts in the .\Data\Scripts\Story directory requires to be initiated this way in the Definitions Function:

StoryModeEvents = { Universal_Story_Start = Universal_Story_Start,
Event_01 = State_01,
Event_02 = State_02

}


For Story scripts each State will use the "if message == OnEnter then" or "if message == OnUpdate then". It will listen for this event in the story.xml (written into the same story_plot.xml as the lua script) to trigger (in this case it listens for Universal_Story_Start and Event_01, Event_02). The events have always different names, so make sure it listens for events in the story.xm file.
Remember that Lua and Xml files were linked together by the story_plots.xml file?

ServiceRate = 0.05

The Variable ServiceRate defines how fast the "OnUpdate" states will refresh, 0.1 means 1 second and 0.05 is half a second..
If you don't write this variable, the game will use a stock value of 0.05 to refresh twice a second

So you can set how fast the game will respond to changes and refresh. Be carefull, values over 0.03 could drain performance because of hundreds of refreshes.

Examples:

function State_Init(message)
if message == OnEnter then

-- Your Code Block

Set_Next_State("State_01")
end
end


State_Init is the simplest kind of state; it runs once because of the "OnEnter", executes the Code Block and then it runs the "Set_Next_State()" function to switch to State_Init.
Here we have two "end" because one closes the State and the second one closes the if statement.

function State_01(message)
if message == OnUpdate then

-- Your Code Block

if condition then
Set_Next_State("State_02")
end
end
end


State_01 is repeating because of OnUpdate. Which means it runs the code block, then it checks if the condition at the bottom was fulfilled, if not it will update again and run its body again from the top to the bottom, until the condition is fullfilled and the "Set_Next_State()" activates to go to the next state.

Remember the first lesson that everything runs from top to bottom, this is important so you understand that a state runs, then it sets another state or refreshes OnUpdate and re-runs multiple times a second.

function State_02(message)
if message == OnEnter then
-- Once Only Code Block


elseif message == OnUpdate then
-- Repeated Code Block

end
end


State_02 is a mix of State_Init and State_01. It runs the whole Code and both Code Blocks once, then it only keeps the "Repeated Code Block" repeating (in the interval specified by ServiceRate) until anything breaks this loop (like maybe the if statement in State_01).


-// 10. Functions
[/color]

Functions are a lot simpler then states, they are also called "Threads".
Example:

FunctionName()

-- Execute Code Block

end


You can always call a function using

FunctionName_Thread = Create_Thread("FunctionName")


And you can run a Function from inside a thread/function.
I always put the Functions at the very bottom of the script.


-// 11. Star Wars EAW Functions
[/color]

The Admin "Anakin" in the everythingeaw forum concluded all functions he found from the vanilla game into a nice function list:

www.everythingeaw.com/forum/showthread.php?t=27

(12.2016; anythingeaw.com seems to be offline, no idea if it will recover)
Bookmark this site, or copy this list somewhere to your HD into a .txt document. This is basically the code you are going to build your scripts upon.

They are quite simple to use, simply copy/paste a function into your script and insert the EXPECTED kind of values (as he described) into the function() brackets.

Some of the functions at the bottom are rather useless because they're made for a certain situation, but most of them are always very useful and the name of each function describes its purpose pretty self explanatory.

Anakin forgot to add these few functions to his list:

GlobalValue.Get(PlayerSpecificName(player, value_name))

Play_Music("abc") -- abc is the .xml instance of a .mp3 file, inside of MusicEvents.xml
.Get_Health()
.Make_Enemy(player_object)
.Attach_Particle_Effect("XXX")


- The Particle_Name XXX of the last one has to be a particle instance, you can find many of them in Particles.xml. This way you can attach any kind of particle effect to a Unit.


I've browsed for you through the Forces of Corruption Particles.xml and lilsted useful particles here:

"Home_One_Target_Particles" = red targeting
"Weaken_Enemy_Particle_Effect" = permanent green particle
"Corrupt_Systems_Effect" = permanent green orb
"canderous_lure" = permanent pink localisation particle
"Lure_Ability_Particle" = pink Interdictor particle
"Emperor_Force_Convert_Particles" = Green Empire symbol

"Shield_Penetration_Effect_Particle" = Permanent portalgun effect
"Weaken_Enemy_Detonation_Effect" = green wave
"Force_Instant_Heal_Particles" = blue wave
"Self_Destruct_Effect" = pink wave

"Force_Whirlwind_Particles" = short red wave
"FOW_Ping_Blast_Particles" = short blue wave
"Replenish_Wingmen_Particle_Effect" = short ultraviolet wave
"Sensor_Jamming_Effect" = short bigger ultraviolet wave
"Blast_Charging_Effect" = pink dust
"Ewok_Suicide_Bomb_Explosion" = nice little explosion
"Huge_Explosion_Land" = another nice explosion
"Spmat_Damage_Land" = little green explosion
"p_orbital_impact" = heavy Napalm explosion
"p_orbital_impact_ion" = Ion explosion
"Piet_Powerup_Particle_Effect" = red particles
"Rescue_Effect" = blue beaming away effect
"p_kraytdestoyer_detonate_special" = Planet destruction
"Shield_Flare_Effect"
"Ysalamiri_Force_Drain_Effect" = short black circle



Here is the list of Lua Commands from the everythingeaw forums:

Anakin in the everythingeaw forums schrieb: FOC: if Object.Get_Rate_Of_Damage_Taken() > 20.0 then



interesting Count-Getter:
fleet.Get_Contained_Object_Count() -> please note, that fleets have to be assembled first
fleet.Contains_Hero() -> works for all fleets not in hyperspace
fleet.Contains_Object_Type(

SpaceForce.Get_Force_Count()
FreeStore.Get_Object_Count()
MainForce.Get_Unit_Table() -> only in KI section

********************************************
*** Findings from the LUA script files ***
****************************************

StoryGlobale:
State-Funktions parameter P0, Werte: OnEnter, OnUpdate, OnExit
- which is when?

!!! function Story_Mode_Service()
- it seems as if this function is executed repeatedly, to check specific criteria

ScriptExit()
- terminates the script
StoryModeEvents = {Blabla_00 = State_Blabla_00}
- assigns StoryEvents to script function
GetCurrentTime()
- returns current time (system time?, ingame time?, I guess the last, because it looks like it is used with seconds)
Find_All_Objects_Of_Type("XXX") - XXX object name
- returns an array with all found objects
Find_First_Object("XXX") - XXX name of a GameObjects
- returns the first found object
Find_Player("XXX") - XXX for Empire, Rebel, Pirates ...
- returns the according GameObject (PlayerObject)
FindPlanet("XXX") - XXX Planetenname
- returns the according GameObject (PlanetObject)
TestValid(X) - GameObject
-
Add_Planet_Highlight(XXX, "0") - XXX PlanetObject
-
Remove_Planet_Highlight("0")
-
Get_Story_Plot("XXX") - XXX Story_bla.xml
-
Story_Event("XXX") - XXX Eventname
- triggers StoryEvent
Find_Hint("XXX", "YYY") - XXX object from the tactical map, YYY - hint
- to find certain locations on the tactical map, objects with same type can be distinguished with hints?
Find_All_Objects_With_Hint("XXX") - XXX hint
-
Add_Radar_Blip(XXX, "YYY") - XXX object, YYY new hint?
-
Rotate_Camera_By(180, 30) - X angular degree, Y time in seconds
- rotates camera around targeted point
Letter_Box_In(0)
-
Letter_Box_Out(0.5);
-
Suspend_AI(X) - X 0 or 1
- puts KI off or on
Lock_Controls(X) - X 0 or 1
- puts controls off or on
Create_Thread("XXX") - XXX function name
-
Create_Thread("XXX", Y) - XXX function name, Y parameter
-
Sleep(X) - X seconds
- suspends the thread for the given time
GlobalValue.Set("XXX", Y) - XXX name of variable, Y value
- not the same variables as in XML !?
- XXX values: Allow_AI_Controlled_Fog_Reveal

Hide_Object(X, Y) - X object, Y 0 or 1
Hide_Sub_Object(X, Y, "ZZZ") - X object, Y 0 or 1, ZZZ SubObject name (also .alo possible)
-
Do_End_Cinematic_Cleanup()
-
Register_Timer(X, Y) - X function, Y integer delay time
- function is executed after given time
Register_Death_Event(X, Y) - X object, Y function
- function to be executed after the death of the object?
Find_Nearest(X, Y, Z) - X object, Y PlayerObject owner, Z true/false ? mostly true
-
Find_Nearest(MainForce, "Structure", PlayerObject, true)
Find_Nearest(Target, "Prop_Good_Ground_Area")
Find_Nearest(Object,"TYP-STRING")

Assemble_Fleet(X) - X return value of SpawnList
- returns FleetObject
Find_Path(X, Y, Z) - X PlayerObject, Y PlanetObject(start), Z PlanetObject(target)
- returns list of PlanetObjects ([0]start, [1]next planet, ect.)
Find_Object_Type("XXX") - XXX object name
- returns type of object
ReinforceList(X, Y, Z, true, true) - X list of UnitObjectNames, Y object (SpawnPosition), Z PlayerObject
table.getn(X) - X array aka table aka list
- retuns number of objects in given list
FogOfWar.Reveal(X, Y, 200000, 200000) - X PlayerObject, Y PositionObject,
- returns fogid
GameRandom.GetFloat()
- returns a random float value between 0.0 und 1.0
GameRandom(X,Y) X integer, Y integer
- returns a random integer value between X and Y (both inclusive)

PlayerObject:
.Enable_Advisor_Hints("XXX", Y) - XXX for space, ground, Y false or true
-
.Get_ID()
- returns ID of PlayerObject
.Get_Enemy()
.Get_Type()
.Select_Object(X) - X object
.Enable_As_Actor()
.Retreat()
.Get_Name()
.Get_Faction_Name()
.Get_Tech_Level()
.Is_Human()
.Give_Random_Sliceable_Tech()
.Give_Money(X) - X credits

PlanetObject:
.Get_Owner()
- returns PlayerObject (applicable for most objects)
.Get_Is_Planet_AI_Usable()
.Get_Game_Object()
.Get_Type()
.Get_Final_Blow_Player()

UnitObject:
.Despawn()
- removes the object from game
.Get_Planet_Location()
I tried to detect planets in Spacebattles, but both .Get_Parent_Object() and .Get_Planet_Location() seem to run without result: The value is always nil in space mode.
- returns PlanetObject
.Move_To(XXX) - XXX object
- (also applicable for PlayerObject)
.Get_Position()
- returns Position(Object)
.Enable_Behavior(X, Y) - X number of behavior, Y false or true

.Is_Under_Effects_Of_Ability("XXX") - XXX AbilityName
- returns true or false
.Activate_Ability()
.Activate_Ability("XXX", Y) - XXX AbilityName, Y false or true
-
.Activate_Ability("XXX", Y) - XXX AbilityName, Y object
-
.Reset_Ability_Counter()
-
.Set_Single_Ability_Autofire("XXX", Y) - XXX AbilityName, Y false or true
.Is_Ability_Active("XXX") - XXX AbilityName
.Is_Ability_Ready("XXX") - XXX AbilityName
.Has_Ability("XXX") - XXX AbilityName

.Get_Hull()
- returns x.x -> 1.0 = 100%?
.Take_Damage(X) - X damage points
-
.Take_Damage(X, "YYY") - X damage points, Y hardpoint
.Get_Shield()
- returns x.x -> 1.0 = 100%?
.Are_Engines_Online()
- retruns true or false
.Override_Max_Speed(X.X) X velocity?
- new value for max velocity?
.Guard_Target(XXX) - XXX PositionsObject

.Suspend_Locomotor(X) - X false or true
- movability on or off?
.Cinematic_Hyperspace_In(X) - X integer time
-
.Prevent_AI_Usage(X) - X false or true
-
.Hide(X) - X false or true
-
.Play_Animation("cinematic", true, 2)
-
.Change_Owner(XXX) - XXX PlayerObject (new owner)
-
.In_End_Cinematic(X) - X false or true
- shall object be displaed in Cinematic?
.Teleport(X) - X (Positions)Object
-
.Face_Immediate(X) - X (Positions)Object
- (also for PlayerObject)
.Prevent_Opportunity_Fire(X) - X false or true
- fire at will?
.Make_Invulnerable(X) - X false or true
-
.Stop()
.Set_Check_Contested_Space(X) - X false or true
.Get_Parent_Object()
- for fleets this is a PlanetObject, for ground forces it's the transport?
.Attack_Target(X) - X Object
.Lock_Current_Orders()

StoryPlotObject:
.Get_Event("XXX") - XXX name of event in plot
- returns EventObject
.Activate()
-
Wait_On_Flag("ACCUMULATE_CREDITS_NOTIFICATION_00", nil)

.Suspend()
-
.Reset()
- event can fire again

EventObject:
.Set_Reward_Parameter(X,Y) - X number of parameter (beginning with 0), Y GameObject

GameObject:
Get_Game_Object()
- ? see R0.Get_Game_Object() (where R0 should be a PlanetObject)


Warnung: Spoiler! [ Zum Anzeigen klicken ]



IMPORTANT:
Now that you got the EAW Functions, you will need some context how to use them to influence the game using lua.
For this purpose you may want to have a look at ultimate eaw's repository database:
ultimate-empire-at-war.com/svn/trunk/Data/Scripts/

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
27 Mär 2015 02:51 #72003 von Imperial

-// 12. Writing a Object Script
[/color]

Now we are going to teach you the main purpose of this tutorial, to show how to actually connect
all of these functions of the game into a sinful way together.

So first thing we are going to do is creating a new .lua file.
Call it ObjectScript_*Name_It_What_Ever* and drop it into the .\Data\Scripts\ObjectScript directory of your Mod or the vanilla game.
Finally paste this into it:

Warnung: Spoiler! [ Zum Anzeigen klicken ]


The question is, how are we gonna change and manipulate objects on the maps?

Well, there are 2 Layers in Star Wars EAW:
1. Galactic Conquest Layer in Space
2. Planetary Layer


For the lua scripts, both are actually quite the same.. a container with so called Objects stored in it.

On GC Layer you can spawn more Units, destroy and despawn them, maybe moving them somewhere. So there isn't much to script for.
Most things will happen on planetary layer, in space or ground battles!

However, on both layers the first thing you want to do is getting objects to work with.
You cant simply point at them and say "I want this one", this is why you have to tell the script to look for the right unit you mean.

Now, please start the Game and get the unit you added the <Lua_Script> tag in the step above, then please start a space/ground fight with that unit.
And save the game BEFORE it enters the map. As soon it enters the map it should spawn a highlighter arrow over it.

If you see the pointer arrow this is good, it means our script is running correctly! Now press the Windows key to minimize the game.
Otherwise you did something wrong and have to try it again.


Please make a copy of your lua scipt in your GameObjects Directory, for the case we waste anything in it.
It can stay with the name "ObjectScript_Name - Copy.lua" and "ObjectScript_Name - Copy(2).lua" and 3 and so on.
As soon you are entirely done with your script you can delete all of these security backup copys.

Additionally these backups are very helpful for error detection:

If you use PSPad you can open the new version of your script, and then go to Tools > TextDiff:Compare > With chosen file then choose the older backup of your script on your Harddisc. PS Pad will highlight any change colorful so you easily can see the last changes you did to your script since the last backup (which worked). One of the highted lines might cause a crash.

Many other text editors have this comparsion function, you just have to search for it.
Text Comparsion is also usefull to apply your newest function (and all changes you added with this function) to older scripts. I did this quite often each time I learned a new trick.



Now we are going to define the name of the desired unit in a variable, please do this mostly in the Definitions state:

function Definitions()
Define_State("State_Init", State_Init)

-- Name Strings of .xml instances have always to be in "signs"
Desired_Unit = "Unit_Name"

-- Defining the player fraction objects
Player_Rebel = Find_Player("Rebel")
Player_Empire = Find_Player("Empire")

-- Don't write this if your script is for classic EAW:
Player_Underworld = Find_Player("Underworld")

end

We sometimes need to find the Player object because some functions require it that way.
But mostly its better to use instead:

Object.Get_Owner()

Because this gets the owner of our Object. You could also get owner of enemy units, which makes it universally and easy to use.

Then please write this into the body of your state_init function:

My_Unit = Find_First_Object(Desired_Unit)

Now this will be a global variable, which means you can access it from any state.
If you already know you need this only in this single state, please add "local" in front of it,
so it stays local and requires less resources:

local My_Unit = Find_First_Object(Desired_Unit)


This is (in my mind) the best way to initiate units, because if you wish to change this unit later,
all you have to do is search for it in Definitions function and rename "Unit_Name" to what ever!

Another way to do this is directly inserting the string into the Find_First_Object function:

local My_Unit = Find_First_Object("Unit_Name")


But dont forget that Find_First_Object requires whether a "String" or a Variable which points to a "String"!

Note: You usually don't need to find the .xml unit with the <Lua_Script> tag, because you always refer to this
Unit as "Object" in ObjectScript(s). This is the Object which executes the script, so you always can call it as Object. ^^

You only need to find all the other units you want to manipulate.

Now we have 2 units we could manipulate: the Object and My_Unit.
Lets say we want to first highlight our Object which executes the Lua script, then the other Unit we just found:

-- Highlighting Object for 4 Seconds, then un-highlighting again:
Object.Highlight(true)
Sleep (4.0)
Object.Highlight(false)

My_Unit.Highlight(true)

Save the code we just inserted and go back into the game. It will say you changed something to the .lua script and if you like to load these changes.
Please click on "cancel", because the game will automatically load the edited script as soon we reload the map!

Load the save game BEFORE your Object enters the map.
Now the changes we just made should take effect.
If first the Object has a highlighter, then the highlighter goes to the other unit, everything works as it should and you can minimize the game again.
Please make another backup copy of the Script in your GameObject directory, and learn to do this each time you did greater changes to your script and if it workes.

If anything wont work you always can restore from a older version and try again or try something else.


You can insert the following variable into your definitions function:

Spawned_Unit = false

This variable will be a switch, we use this to prevent the script from crashing if a unit could exist on the field
but sometimes wont spawn or was already destroyed. If lua tries to call a unknown variable this causes a script crash!
To prevent that you can do in your State body:

if Spawned_Unit ~= false then
-- Spawned_Unit.Do_Anything
-- After you're done you can set the variable back to false for the next check:
Spawned_Unit = false
end


If statements fire only if the condition is true, otherwise they will completely ignore their body, which is useful here!
I usually use these kind of switches quite often. You could also use "if TestValid(UnitName) then" instead in most of the cases.

By the way in Lua you (can if you like but) dont have to write == true for boolean variables, like for example:

Spawned_Unit = true


You can write:

if Spawned_Unit == true then


You also can write:

if Spawned_Unit then

Because the engine will assume that you mean == true
This is quite handy. But it aviously works for true only and wont work for == fase if states.


The Sleep function waits for x amount of seconds:

-- Sleeping because..
Sleep (0.6)

-- Sleeping because .. blabla
Sleep (1.0)

It would also work:
Sleep (1)

I strongly recommend that you add a comment over each sleep function with the reason why it is being used.
Because if anyone else sees your script or you see it a year later, you will have no idea WHY the script sleeps
exactly in this timing and for this long.


Known Bugs and Issues:
[/color]

CAUTION: If multiple units on the map use the sleep function exactly in the same second,
the sleep function can cause all other units except for the first one to crash their lua script or to crash the game!!
Especially when combined with "OnUpdate" states.

So if you know that all units of a type will use a lua script, and it suddenly crashes and it will only work for 1 unit,
It can be caused by one of their sleep functions, then you could -- comment these sleep functions out to deactivate and test them one by one.

I maybe found a solution for this issue; you could place the Sleep commands into a thread and start that thread to execute Sleep from inside the thread instead of from inside of the state. This can help because I guess threads are better suited for simultanous execution.

The other thing that causes script crashes if more then 1 unit use this function at the same time is

.Play_SFX_Event("Xml_Sound_Name")

This also crashes the script if you try to play a second sound as long the first song is still being played.

Because the 2 sounds seem to collide which crashes the script.

So be careful when you use .Play_SFX_Event and Sleep, they can cause script crashes!!


It sometimes takes hours to find out what causes a bug because sometimes more factors together are the bug, but the code snippets themselfes are correct code!!

Like for Example:

Object.Change_Owner(Find_Player("Neutral"))

The change owner command can crash the script if it is placed under or over a wrong command.
Further I placed it with caution into my script, but it still managed to crash the whole game after loading (in a very misterious way).

Sometimes Register_Death_Event functions or register functions in general can cause script or gamecrashes after loading/while saving, because they continously check for a event, which drains memory. In combination with a expensive for loop or a long if statement, or while loading it can crash the script or the game.

For more info please have a look into my Anti bugs thread here:
www.stargate-eaw.de/index.php/de/forum/d...i-bugs-thread#p73446

At the beginning its about .xml debugging, but there I listed also some lua issues.


Calling Functions
[/color]

Things you could do to a Object:

-- Hiding a whole Object:
Hide_Object(Object, 1)
-- Unhiding a Object:
Hide_Object(Object, 0)

-- Hiding partial meshes of the Object model:
Hide_Sub_Object(Object, 1, "Mesh_Name")
-- Un-hiding partial meshes:
Hide_Sub_Object(Object, 0, "Mesh_Name")

Point_Camera_At(Object)


.Override_Max_Speed(int) -- You have to look into the original .xml for speed. Fighters have about 5.0

-- Replace "Idle" with name of Animation, don't name it like "Idle_00", works without numbers in the name.
-- The second parameter repeads this animation in a loop if set to true
.Play_Animation("Idle", false, 0)

.Teleport(target_obj) -- Use this to instantly get a unit to a other location
.Face_Immediate(target_obj)
.Teleport_And_Face(target_obj) -- This is a combination of the 2 above

.Attack(target_obj)
.Move_To(target_obj)
.Attack_Move(target_obj)
.Stop()
.Guard_Target(target_obj)
.Suspend_Locomotor() -- true or false

.Take_Damage(amount) -- or .Take_Damage(amount, "Name_of_any_Hardpoint")
.Despawn()

.Make_Invulnerable() -- true or false
.Prevent_All_Fire() -- true or false
.Set_Selectable() -- true or false
.Reset_Ability_Counter() -- true or false
.Activate_Ability("Ability_Name", true)
.Cancel_Ability("Ability_Name")
.Change_Owner("Player_String_Name")

.Make_Ally(player_object) -- has to be used for both players
.Make_Enemy(player_object) -- has to be used for both players

player_object.Select_Object(Object) -- .Set_Selectable() has to be true
player_object.Give_Money(amount)

-- Suspending generally the whole AI
Suspend_AI(1) -- 1 or 0


The Dot . Calls a function, you need to have one variable or object in front of the. dot and the function you want to call after the .dot, so
variable.function() = variable calling function()

Here again the Getters, Anakin listed in his post:
To use one of them, simply put a Object in front of it (NO String!!)

.Get_Type()
.Get_Distance()
.Get_Owner()
.Get_Shield()
.Get_Hull()
.Get_Health()
.Get_Combat_Rating()
.Get_Enemy()
.Get_Name()
.Get_Position()
.Get_All_Planets()

.Get()
.Get_Affiliated_Indigenous_Type()
.Get_AI_Power_Vs_Unit()
.Get_Avoidance_Chance()
.Get_Base_Level()
.Get_Bone_Position()
.Get_Build_Cost()
.Get_Build_Pad_Contents()
.Get_Contained_Object_Count()
.Get_Credits()
.Get_Current_ID()
.Get_Difficulty()
.Get_Event()
.Get_Faction_Name()
.Get_Final_Blow_Player()
.Get_Float()
.Get_Force_Count()
.Get_Game_Object()
.Get_Game_Scoring_Type()
.Get_Hint()
.Get_ID()
.Get_Is_Planet_AI_Usable()
.Get_Max_Range()
.Get_Min_Range()
.Get_Next_Starbase_Type()
.Get_Object_Count()
.Get_Parent_Object()
.Get_Planet_Location()
.Get_Reserved_Build_Pads()
.Get_Score_Cost_Credits()
.Get_Self_Threat_Max()
.Get_Self_Threat_Sum()
.Get_Stage()
.Get_Tactical_Build_Cost()
.Get_Tech_Level()
.Get_Time_Till_Dead()
- returns an object-type, but only used for parent-object, not working for others?

.Get_Parent_Object()
.Get_Type_Of_Unit()
.Get_Unit_Table()



Name Tests
[/color]

Now lets do our first if statement, please delete the Highlight stuff we just made and paste this instead:

if Object.Get_Type() ~= Find_Object_Type(Desired_Unit) then
Object.Highlight(true)
end

Important to know: Type of a Object is often its XML Unit Name! So if many functions refer to type, it actually means unit name.

First we get the type of the Object (and store it into a variable), then we compare this variable with the string Desired_Unit
ONLY if our Object and the String "Unit_Name" are NOT the same type, the object will be highlighted.
Otherwise nothing will happen.

Be aware: Desired_Unit is a variable, which is set to "Unit_Name" so Find_Object_Type actually expects Find_Object_Type("Unit_Name") and not My_Unit which is a Object and no String!
So dont ever confuse string and object with eachother! Some functions expect strings while other expect objects.

Please return to the game and again "cancel", reload our save game to reload the map and check if it worked.

We could turn the meaning of this statement to the oppsite, simply replace ~= (is NOT) with == (is the same as)

if Object.Get_Type() == Find_Object_Type(Desired_Unit) then
Object.Highlight(true)
end

Of course you can replace later the Object.Highlight(true) with your own code blocks.

But you would need to define another unit with the name of the .xml unit of our object, this time it isnt suppsed to highlight if you test ingame.
Because our Object and the other unit are probably of different types, so no match, that's why it wouldn't highlight.

As I just said: strings and objects are different things and functions expect the right kind.
I will show you another possibility to check units and compare their type:

-- Only if the Object is of the same Type (mxl name) as My_Unit
if Object.Get_Type() == My_Unit.Get_Type() then
Object.Highlight(true)
end

This variant checks the name of both the Object and My_Unit and if they are of the same type the Object will highlight.
You could also do

if Object.Get_Type() ~= My_Unit.Get_Type() then

Which will highlight if they are NOT of the same type.

Other thing you could do is:

My_Unit = Find_First_Object("Unit_Name")

if TestValid(My_Unit) then
Object.Highlight(true)
end


This simply tests if the Object (NO string) we want to find exists on the map.
So if there is a unit My_Unit, it will be highlighted.

This is a alternate way how you could get the string:

if TestValid(Find_First_Object("Unit_Name")) then
Object.Highlight(true)
end


This is a shortcut to the way above; instead of first defining My_Unit we simply insert the Find_First_Object function into the TestValid() function, which returns a Object we need for TestValid().

The second way is codewise faster to execute but the first way is easier to read by humans. It also depends on the situation how you write it. However, the result is the same!


There is one golden rule I want to teach you: Small steps, NEVER do great leaps!
(Atleast as long you don't know 100% for sure that all is going to work)

I wont tell you again to minimize the game using Windows key (or strg, alt and entf),
And to save backups in the GameObject or Story Directory once in a while. You have to learn to do this automatically by your own!
Because this is what you are doing all the time while coding.


Another cool function is GameRandom; Its a randomizer that randomly gives you a number within the range of its parameters

GameRandom(min, max)
Example:

GameRandom(1, 6)

Will return a random number between 1 and 6, to play Russian roulette ^^
You can utilize this for lots of functions to simulate dicitions.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
27 Mär 2015 03:03 #72004 von Imperial

Tables and for loops
[/color]

Find_First_Object() is handy if you know there is only 1 object of the name that you wish on the field.
Especially for unique units and space stations or map markers.

But if you have 10 or 30 objects of a kind, the game will simply "Find_First_" object of this type and
it will probably be the wrong one.

So how do you tell the game to choose the right one? This is a bit tricky, but don't worry I'll show you how.


Finding multiple objects of same type:
[/color]

Please copy this on the top of your state_init body:

All_My_Units = Find_All_Objects_Of_Type("Unit_Name")

Then replace "Unit_Name" with the name of a valid .xml unit.
Find_All_Objects_Of_Type creates a table and inserts all units it can find of this type, than it returns this table as variable.
Remember my short lesson about tables from above?

Table_List_01 =
{
[1] = Tomatos,
[2] = Milk,
[3] = Beer,
[4] = Chips,
[5] = Vodka,

}


index_number
Are the numbers in the [ ] Brackets. They are a endless sequence if needed.
Any new variable will be stored into the next free index number.

unit_variable
Is a simple variable, the for loop will cycle through the whole sequence, and will assign the content to this variable.

Example how this works:

Cycle 1:
index_number = [1]
unit_variable = Tomatos

Cycle 2:
index_number = [2]
unit_variable = Milk

The for loop will do this FOR ALL content in the table. This is how for loops work here.

Then please insert this into the body of your state:

-- for each index and index variable in the table "All_My_Units" do
for index_number,unit_variable in pairs(All_My_Units) do
-- If exist and the unit_variable is has the fraction as our Object
if TestValid(unit_variable) and unit_variable.Get_Owner() == Object.Get_Owner() then

-- Highlighting, replace this by your code block
unit_variable.Highlight(true)
end
end


This is how you get all space ships into a table:

All_Space_Units = Find_All_Objects_Of_Type("Transport | Fighter | Bomber | Corvette | Frigate | Capital")


You can use this for statement to filter all units of your fraction out:

-- for each Unit in the table "All_Space_Units" do
for each,Unit in pairs(All_Space_Units) do
-- If exist and has the fraction as our Object
if TestValid(Unit) and Unit.Get_Owner() == Object.Get_Owner() then

-- Highlighting, replace this by your code block
Unit.Highlight(true)
end
end



You can replace the == for ~= in this line to get all non player units and enemies instead:

if TestValid(Unit) and Unit.Get_Owner() ~= Object.Get_Owner() then



Another way to get all units of a fraction is:

All_Enemy_Objects = Find_All_Objects_Of_Type(Find_Player("Empire"))

You can replace "Empire" with Rebel, Underworld or whatever fraction.
But be careful with the "Neutral" fraction, because all Planets, space objects
and some ground objects are of type Neutral and would get into that list.

But a better way is:

-- Getting all units of the user
local All_Own_Units = Find_All_Objects_Of_Type(Object.Get_Owner())

This uses .Get_Owner() to get the owner of any unit (Replace Object with enemy units if you want to get enemy fraction).

Note:
These for loops operate with Objects ONLY, it wouldn't work with a table that contains String names!
Use the one below for String names.


Finding multiple objects of DIFFERENT type:
[/color]

Please copy this table into your Definitions function:

Valid_Units_List = {"Unit_Name_1", "Unit_Name_2", "Unit_Name_3", "Unit_Name_4"}

Then replace my variable "Unit_Name_?" with units of your Mod or the vanilla game and copy this into the body of your state:

-- Initiating a empty table, found units will be inserted here
All_Found_Units = {}

-- For all String_Names in the Valid_Units_List Table
for each,String_Name in pairs(Valid_Units_List) do
-- Find all units of this type into another table variable
local Found_Units = Find_All_Objects_Of_Type(String_Name)

-- Again for all units, this time not the strings but the actual units
for each,Unit in pairs(Found_Units) do
-- If exist and the Unit has the player fraction
if TestValid(Unit) and Unit.Get_Owner() == Object.Get_Owner() then
-- Then we will insert it into the table "All_Found_Units"
table.insert(All_Found_Units, Unit)
end
end
end



Note:
Most coders use "for k, Name in pairs .." and other single letters.
I don't like this because if you view your script 1 year later, you have no idea what the letter k stands for.
Better use variables that exactly describes what it is good for.

You can simply always use "for each,Unit in pairs .."
You are allowed to use exactly the same variables in every for loop because they use local variables for their cycles.

I strongly recommend to differentiate between String_Names and Units, as I did above.
Its very easy to confuse yourself this way and later you don't know yourself what is required here, strings or objects..

I also recommend the word Unit, because it is quite universal and you can use it for both, Spaceships and Ground troops.


-- This has the opposite effect of table.insert, to remove a unit from a table:

table.remove(Table_Name, index_position) -- Usually index_position would be "each" for me


-- Again for all units, this time not the strings but the actual units
for each,Unit in pairs(All_Found_Units) do
-- If exist and the Unit has the type we are searching for (we use this to filter out anything other then the type we are looking for)
if TestValid(Unit) and Unit.Get_Type() == Find_Object_Type("Unit_Name") then
-- Then we will remove it from the table "All_Found_Units"
table.remove(All_Found_Units, each)
end
end


Note: Here you need to insert the index_number, wont work with the name.
But the .Get_Type() == Find_Object_Type() part will filter the desired units out and remove them.


You can also count all units in a table, and randomly choose one of them:

-- Counting the Units in "All_Found_Units" with the "getn" = get number command

local Unit_Count = table.getn(All_Found_Units)

local Random_Number_1 = GameRandom(1,Unit_Count)

-- Assigning random Unit according to the Random_Number_1 variable from the lines above
local Random_Unit = All_Found_Units[Random_Number_1];



Now I am going to show you how to make a own proximity function, which triggers as soon any other spaceship
gets close to this unit.

You can paste the first table in your Definitions Function:

-- Lets say we want to exclude these 2 units so they wont trigger our proximity:

Exclusion_List = {"Unit_Name_1", "Unit_Name_2"}


Then paste everything from here into a "OnUpdate" state:

All_Space_Units = Find_All_Objects_Of_Type("Transport | Fighter | Bomber | Corvette | Frigate | Capital")

-- For all space units we found
for each,Unit in pairs(All_Space_Units) do
-- If exist
if TestValid(Unit)
-- And it is none of the Units in the Exclusion List table
and Unit.Get_Type() ~= Find_Object_Type(Exclusion_List[1])
and Unit.Get_Type() ~= Find_Object_Type(Exclusion_List[2]) then

-- Getting distance between Unit and our Object
local Distance_to_Object = Unit.Get_Distance(Object)

-- If a Unit gets as close as 350 to our unit then.. (350 is a good value)
if Distance_to_Object < 351 then
-- Just highlighting to see it works, replace by your code then
Unit.Highlight(true)
end

end
end


You could limit this function to fire only for own or enemy ships, adding

"and Unit.Get_Owner() == Object.Get_Owner()" or "and Unit.Get_Owner() ~= Object.Get_Owner()"

What this function will do is executing the code and highlight the unit as soon it is nearing our Object.
This is often better then the Register_prox function because Register_prox is sometimes misfiring from the
other side of the map.

The essential part of the function above is
-- Getting distance between Unit and our Object

Distance_to_Object = Unit.Get_Distance(Object)


Which returns a value of the distance between 2 objects.
Combined with a if statement this is a powerful tool in many situations!


Registering Death and Proximity
[/color]

You can register Death of a Unit, so as soon this Unit got destroyed it will trigger the thread.
But if the object itself gets destroyed then there is no longer any object on the map which executes the function and it wont work because the script stops being executed with the Object. Thats why Register_Death works best when its executed from the story script, or by the object script of any other Object, that you know will be on the same map.

I recommend pasting "Register_Death_Event" and "Register_Prox" into state_init or other OnEnter states, because I don't think its good to register this
multiple times a second.
Sidenote: If you register death to a second unit instance of the same name, the register death event will only trigger for 1 of them.

Register_Death_Event("Unit_Name", Thread_Name_1)


--============= Threads =============
-- A thread is a simple function that can be called by any state and as often as needed:
function Thread_Name_1 ()

-- Insert your Code here

end


Register prox notices if the defined unit comes as close as "ability_range" to the object,
if true it will run "Thread_Name_2":


local ability_range = 200

-- Object, function, range, player_filter) the 4th parameter is optional

Register_Prox(Object, Thread_Name_2, ability_range)


--============= Threads =============
-- A thread is a simple function that can be called by any state and as often as needed:
function Thread_Name_2 ()

-- Insert your Code here

end



Other useful things
[/color]

-- Spawn marker of the position on the mini map for 1 sec:

Add_Radar_Blip(Object, "Marker_on_Radar")

First parameter is any object to highlight on the minimap,
second one is any name you want to give the marker so later it can be removed (so this time the string represents NO xml instance).

Remove_Radar_Blip("Marker_on_Radar")


-- Switching background Music theme

Play_Music("abc")

Note: "abc" is the name of a .xml instance of a sound, usually located in . \Xml\MusicEvents.xml ( It might work with normal sound .xml instances either).

The Music themes themselfes are usually .mp3 files located in .\Data\Audio\Music.
You can use this command in a story script, or call it through a object script. It will immediantly switch the background music ^^

-- Playing Sound Effects

Object.Play_SFX_Event("Sound_Effect")


The sounds themselfes are .wav files, located in .\Data\Audio, however the engine expects "Sound_Effect" to be a .xml instance that points to that wav file.

Use Sound effects with caution, as less as possible because they cause bad script crashes! (see above)


Size Dependencies
[/color]

if Object.Is_Category("Capital") then
-- do something
elseif Object.Is_Category("Frigate") then
-- do something else
else -- do this if unit is Corvette or smaller
end


This statement will check the <CategoryMask> tag of your xml unit and execute one of the block, depending on the Unit category.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
27 Mär 2015 03:11 #72005 von Imperial

-// 13. Analysis of the Ability_Template script
[/color]

I uploaded 5 versions of my ability templates in a .zip archive for you here:

www.mediafire.com/download/e8ajecxmyhmg4gv/Obj

Please download these files and view them in your lua editor.

ObjectScript_Simple_Template.lua = A simple template for smaller scipts, to add a few lines of code into it.

ObjectScript_Ability_1_Template_Text.lua = The same as the one below, but with more text descriptions. Useful for first steps, later you will prefer the one without comments.
ObjectScript_Ability_1_Template.lua = You can use this template to run a Ability script, as soon the dummy ability was activated.

ObjectScript_Ability_2_Template_Text.lua = With description text, allows using 2 active abilities.
ObjectScript_Ability_2_Template.lua = Same as above with less text.

You are allowed to use them for writing your mod scripts, distributing them and to do anything else you wand to them.

You will need to keep 1 copy of them in your GameObject directory and each time you write a new script,
simply copy and rename the template then you can edit it, which is a lot faster then having to rewrite each time the whole code.

In the future more ability scripts will be released in TPC, you might want to check them then to see how things have been done.
By the way you can also assign passive abilities to the Units, which activates without requiring
activation by the player.


Lets have a closer look into the details of "ObjectScript_Ability_2_Template_Text.lua",
please open this file in your lua editor:

--[[
COPY THE FOLLOWING ABILITY into the <Unit_Abilities_Data> tag of your unit.xml:

<!-- You can replace Alternate_Name_Text, Alternate_Description_Text, and Alternate_Icon_Name with costum content. -->

<!-- ===== Recharge and Expiration set to 1000000 because of usage limits ===== -->
<Unit_Ability>
<Type>WEAKEN_ENEMY</Type>
<Expiration_Seconds>1000000</Expiration_Seconds>
<Recharge_Seconds>1000000</Recharge_Seconds>
<Owner_Particle_Bone_Name>BEAMING_ARRAY</Owner_Particle_Bone_Name>
<Effective_Radius>40000</Effective_Radius>
<Spawned_Object_Type>Hyperspace_Jump_Target</Spawned_Object_Type>
<Bomb_Countdown_Seconds>-1</Bomb_Countdown_Seconds>
<Alternate_Name_Text></Alternate_Name_Text>
<Alternate_Description_Text></Alternate_Description_Text>
<Alternate_Icon_Name>I_SA_FORCE_CONFUSE.TGA</Alternate_Icon_Name>
<SFXEvent_Target_Ability></SFXEvent_Target_Ability>
<SFXEvent_GUI_Unit_Ability_Activated></SFXEvent_GUI_Unit_Ability_Activated>
<SFXEvent_GUI_Unit_Ability_Deactivated/>
<!-- Area effect decal size depends on "Antills_Weaken_Enemy_Effect" -->
<Area_Effect_Decal_Distance> 20 </Area_Effect_Decal_Distance> <!--was 800-->
<Layer_Z_Adjust>0.0</Layer_Z_Adjust>
</Unit_Ability>

<Unit_Ability>
<Type>HARMONIC_BOMB</Type>
<Expiration_Seconds>1000000</Expiration_Seconds>
<Recharge_Seconds>1000000</Recharge_Seconds>
<Alternate_Name_Text></Alternate_Name_Text>
<Alternate_Description_Text></Alternate_Description_Text>
<Alternate_Icon_Name>I_SA_FORCE_CONFUSE.TGA</Alternate_Icon_Name>
<SFXEvent_GUI_Unit_Ability_Activated></SFXEvent_GUI_Unit_Ability_Activated>
</Unit_Ability>

<!-- ===== And copy this tag, which contains the name of this script aswell to the space unit (WITHOUT .lua suffix): ===== -->

<Lua_Script>ObjectScript_Ability_Name</Lua_Script>

]]

At the top this script provides info about the required dummy abilities.
A "dummy" ability is a ability that (in best case) has no other side effect and does actually nothing else then
giving the signal to this script, that this ability was been started.

I like to use "WEAKEN_ENEMY" as dummy ability because it provides you a target pointer and can spawn any object to the position you point at.
Then you can use a dummy that de-spawns (by its own lua script) after a few seconds and has a invisible model.

Later you can get the object position of this dummy (which is the whole point of it) and do things on this position like sending units to it,
Spawning other things, selecting enemy units and so on..

And I like "HARMONIC_BOMB" because it usually spawnes a single object at the position of the unit itself.
But without the <Spawned_Object_Type> tag it wont do anything, that's why its a good dummy.

Another good dummy ability is "POWER_TO_WEAPONS" without any game modifier tags.

ability_used_counter_1 = 0
maximal_usage_limit_1 = 3

Another special thing, invented by me is the maximal usage limit.
You can use for each ability a "ability_used_counter" with a "maximal_usage_limit" variable,
to detect how many times a ability fired, then as soon ability_used_counter reached the value of
maximal_usage_limit the ability stops working.

This is important for overpowered abilities, to limit them a bit. But you can simply set maximal_usage_limit to 200
so the usage is quite unlimited.

Time_Counter_1 = 0

And I use time counters sometimes to let the script wait for a while.
Benefit of a time counter compared to the Sleep (x) command is, that the script wont stop its execution for
the amount of waiting seconds, instead it runs through the script and is able to refresh and run other needed code, while it still keeps track of how much time is over, then it triggers in the right moment.

Now how are we actually going to handle the abilities ?
We could simply dump every function and all code blocks into a single state.
But that wouldn't look very good, and it would be harder to get this working this way.

So my rule about that is "divide and rule"
We split the whole effect of your ability into a few single states, each state is called at a certain time.

But "State_Inactive" is our CENTRAL state. This is the position where the script always returns, even though different abilities leads the script into different states, but at the end of all these states a Set_Next_State("NameOfState") command brings us always back to the "State_Inactive" state and fulfills a circulation.

You don't have to name the state "State_Inactive", feel free to call all states how ever you like.
This name convention of states is a thing that works for me, you can of course rename them if you feel convenient enough.
But be aware that even a single wrong spelled state name will cause your script to not work as supposed.

if message == OnUpdate then Sleep (1.0)

You sometimes add Sleep (1.0) or Sleep(GameRandom(1,2)) after "if message == OnUpdate then"
What is it good for ?

Well the default service rate is 2x a second, but if you want the script to wait 1 additional second, it is sinful to add this for each refresh.

And, often abilities crash the script in the transition from active to inactive state because parts of the ability code
are still in execution but you returned to the "inactive_state".
To prevent script crashing in this situation we put this 1 second of sleeping between the 2 states as buffer for the transition.

-- This time counter calls once every 20 seconds the function thread at the bottom of this script
if Time_Counter_1 == 20 then
-- Resetting counter for the next 20 seconds
Time_Counter_1 = 0
-- Calling the thread at the end of this script
Starting_Thread_Name_Thread = Create_Thread("Thread_Name")
else
Time_Counter_1 = Time_Counter_1 + 1
end


This is the time counter I already mentioned above. It runs the thread from the bottom of the script once
every 20 seconds because of the "if message == OnUpdate then Sleep (1.0)" on the top.
The function "Thread_Name" is currently empty, you can insert your code there.

By the way this is how you run another function from within a state.
The state stops its execution when this function activates, it finishes the other function and
finally returns back and finishes the rest of the state:

Starting_Thread_Name_Thread = Create_Thread("Thread_Name")

Replace Name_Thread both times with the name of any thread/function you wish to start.
You can always call threads without any time counter, in each situation.

I showed you the time counter only because this is a way to install a regular check, which
constantly runs a function, but not all the time just once in a while every 20 secs.

--================== AI Auto Activation for Ability 1 ====================
-- If the Owner of this Unit is not Human (=AI) and a Enemy is nearby
if not Object.Get_Owner().Is_Human() and Object.Has_Attack_Target()
-- And if its Ability isn't currently running
and Object.Has_Ability(ability_1) and Object.Is_Ability_Ready(ability_1) then
Object.Activate_Ability(ability_1,true)
end


This controls the auto firing for the AI.
Pretty self explanatory. You also learn here how to activate a ability via script.
But the object is required to have this ability working correct in its .xml and dont forget to set the "name" of this ability into the variable ability_1

--================== AI Auto Activation for Ability 1 ====================
-- If the Owner of this Unit is not Human (=AI) and a Enemy is nearby
if not Object.Get_Owner().Is_Human() and Object.Has_Attack_Target()
-- And if its Ability isn't currently running
and Object.Has_Ability(ability_1) and Object.Is_Ability_Ready(ability_1) then
-- Using the state of active ability:
Set_Next_State("State_Ability_1_A")
end


A other thing you could do instead is letting the AI jump simply into the first state of the ability.

--================== Ability 1 ====================
-- Wont work unless the unit has the ability and it is currently acitive!
if Object.Has_Ability(ability_1) and not Object.Is_Ability_Ready(ability_1)
-- To prevent unwanted activation in Nebulas
and not Object.Is_In_Nebula() then

-- As long the first variable is still smaller or equal to the second variable the effect thread starts.
if ability_used_counter_1 < maximal_usage_limit_1 then
-- Using the state of active ability:
Set_Next_State("State_Ability_1_A")
end

end

This is the trigger, which causes the script to jump into another state, into "State_Ability_1_A".
"State_Ability_1_A" contains the actual code of this ability.
The "State_Inactive" is simply a state that continuously checks if any of the abilities was activated
and then goes to the code of that sate. This is why it wouldn't work with "if message == OnEnter" instead of "OnUpdate"

So be careful to not confuse the two.
There are situations for OnEnter, to run once only.
And there are states and certain situations for OnUpdate states.

Well you can also use both in a single state (Im not doing that because for me personal it is clearer and easier this way)

--================== Ability 2 ====================
if Object.Has_Ability(ability_2) and not Object.Is_Ability_Ready(ability_2)
and not Object.Is_In_Nebula() then

-- As long the first variable is still smaller or equal to the second variable the effect thread starts.
if ability_used_counter_2 < maximal_usage_limit_2 then
-- Using the state of active ability:
Set_Next_State("State_Ability_2_A")
end

end

Same as ability_1, only difference is this fires if the second dummy ability, Harmonic Bomb. You can set what ever dummy ability you want: simply change the value of ability_2.
Warning: Ability_1 is NOT the same as ability_1, the capital letter would cause the script to think these are 2 different variables!

Object.Highlight(true)

This and the Give Money command is only so you can see whether the script is functioning right.
Simply replace them by your actual code.

Also ObjectName.Highlight will look weird for OnUpdate states because this constantly spawns new arrows over your unit ..

Spawning a Unit via Lua script:
for Spawnlist the unit has to be a list inside of table {} brackets
SpawnList spawns all "Strings", in this list near the current Object. Parameter Syntax:
{"Unitname_String"}, Position_Object, Fraction_Object, allow_ai_usage (true or false), delete_after_scenario (true or false)
The last parameter "delete_after_scenario" don't seem to do anything, at least in my scenario I couldn't keep the spawned units after winning planetary battle.
SpawnList requires require("PGStoryMode") to be at the top of the script, or it wont work!

-- You need a valid unit in the variable "Unit_For_Spawn" above, or after un-commenting the next line will crash script

Spawned_Unit_1 = SpawnList({Unit_For_Spawn}, Object, Object.Get_Owner(), true, true)

'

Parameter Syntax: "Unitname_String", Position Object, Player Object
Create_Generic_Object CANT spawn Squadrons, use SpawnList instead.
Contrary to SpawnList it spawns the new unit (only 1 unit) right into the center of position of the unit which uses the ability.

You need a valid unit in the variable "Unit_For_Spawn" above, or after un-commenting the next line will crash script.

Spawned_Unit_2 = Create_Generic_Object(Unit_For_Spawn, Object, Object.Get_Owner())

You can call other Functions for these 2 Units we just spawned using their name: "Spawned_Unit_1.Function()" Sometimes the name wont assign to the Object you just created (am not sure why) In these cases you can find them using find_first_object or a for loop.


There is also a third function for spawning a single unit:
Syntax:
Spawn_Unit("unit_type", position_obj, faction)

Example:

Spawned_Unit_3 = Spawn_Unit(Unit_For_Spawn, Object, Object.Get_Owner())

Note: Spawn_Unit is a bit buggy compared to SpawnList and CreateGenericObject.
It also has sometimes trouble to assign to Spawned_Unit_3 like in this example, which causes later
a script crash because the script wouldn't find Spawned_Unit_3 and treat it as nonexistent variable.
This is why I recommend using the other 2. Be aware that Spawn_Unit expects a "String" or a variable that
points to a string (contrary to SpawnList which expects strings in {"tables"}).


Other possibility to spawn units is manually via hyperspace menu. To insert any unit into this menu use:
Add_Reinforcement("String_Name", Owner_Obj)

Example:
-- This will add 1x this unit to the hyperspace list of the units you can call as reinforcement

Add_Reinforcement(Unit_For_Spawn, Object.Get_Owner())


You can also directly let them come via hyperspace using:
ReinforceList(type_list, Object_Or_PositonMarker , faction, allow_ai_usage, delete_after_scenario)

for parameters see SpawnList
ReinforceList requires as well require("PGStoryMode") to be at the top of the script, or it wont work!


-- Uses the ability variable from above you can use the lines under this instead if your ability don't has duration 1000000

Sleep (ability_1_duration + 1)


I often do this at the end of a function that runs OnEnter, because if the ability is still in active state
the "and not Object.Is_Ability_Ready(ability_1)" in "State_Inactive" would constantly fire to run this code
multiple times.

Which we want to avoid for abilities that are supposed to run once only.
So the solution is we are going to sleep as long as the ability endures + 1 second.
This is why you have to insert the time duration of the ability in your unit.xml into
the variable "ability_1_duration" in the Definitions function at the top.

If "ability_1_duration" has not the same value as in the .xml this can cause the script to crash and game crash, so use with caution..

-- As soon the Ability was finished, we will quit this state
-- Comment out the line above and un-comment this line to swap deactivation method

elseif Object.Is_Ability_Ready(ability_1) then


This is an alternate way to achieve the same effect without the "ability_1_duration" value.
You always want to have it at the very end of your ability state.
It is more accurate because it shuts this state off as soon the ability is usable again (=over).

But you have to write this correct. It is an if-elseif statement and if you write it into the wrong line --> script wont start
The correct written code (which goes over the whole ability state) is:

-- Will only work as long the ability stays active
if not Object.Is_Ability_Ready(ability_1) then

-- Codeblock.. do something

-- As soon the Ability was finished, we will quit this state
elseif Object.Is_Ability_Ready(ability_1) then

Set_Next_State("State_Ability_1_B")
end

In the case of my template I divided the ability into 2 states:
"State_Ability_1_A" and "State_Ability_1_B"

In the B part, I simply added the other thing which is supposed to be at the very end of the script:

-- Increasing ability count by 1 (relevant for maximal usement)

ability_used_counter_1 = ability_used_counter_1 + 1


You know this little trick:

i = i + 1

Which increases the value i by 1 each time the code is executed.
The time counter does this as well. You do this if you want to count up or -1 to count down.

This way the script remembers how often the ability was used, which is important for the maximal usage.

-- Resetting only if usage points are left
if ability_used_counter_1 < maximal_usage_limit_1 then
-- Without this resetting the permanent ability, it would infinitely stay in active state so cant be reactivated.
Object.Reset_Ability_Counter()
end

In this case our Ability has <Recharge_Seconds>1000000</Recharge_Seconds> and Expiration seconds to 1000000. Which will take for ever. So the ability wont recover, unless it was reseted by Object.Reset_Ability_Counter()

Which only will happen if the "maximal_usage_limit_1" wasn't reached.
The whole point of this is to limit the usage of this ability, if it is a overpowered one.

Set <Expiration_Seconds> and <Recharge_Seconds> to about 40 and maximal_usage_limit to 300 if you want the ability
to function in the regular way infinitely.


More basic functions: Sending Units via Script
[/color]

-- Paste this function to the bottom of your Script:

function Moving_To_Fleet ()

-- Initiating a table (global variable because it is supposed to be used in the states above):
Main_Fleet = {}

-- Resetting the Big units
local All_Big_Units = {}

-- Re-Searching for the fleet
All_Big_Units = Find_All_Objects_Of_Type("Transport | Corvette | Frigate | Capital")

-- We wont use "All_Units" because we don't want them to go to fighters
for each,Unit in pairs(All_Big_Units) do
-- If exist and the Owner of the Unit is the same as the Object Owner (Human)
if TestValid(Unit) and Unit.Get_Owner() == Object.Get_Owner() then
-- Then we will insert it into the table of our "Main_Fleet"
table.insert(Main_Fleet, Unit)
end
end

-- Getting unit count of the Main_Fleet table
local Units_in_Fleet = table.getn(Main_Fleet)
-- Choosing a random number, where Units_in_Fleet is the maximal number to use
Random_Number_2 = GameRandom(1,Units_in_Fleet)

end


Then run this in the body of your State:

-- Calling the thread, to move the spawned ship to a random unit in the fleet:
Moving_To_Fleet_Thread = Create_Thread("Moving_To_Fleet")

-- Waiting a bit to prevent timing issues
Sleep (1.0)

-- Selecting a single Unit
Object.Get_Owner().Select_Object(My_Unit)

-- Moving it to a random ship in the Main Fleet (I left both to be global variables so they're accessible)
My_Unit.Move_To(Main_Fleetcolor=#ff6600]Random_Number_2[/color)




-- In order to send Squadrons you need to send each fighter separately using a for loop.
The for loop uses the Moving_To_Fleet_Thread from above, paste the following into your state:

-- Calling the thread, to move the spawned ship to a random unit in the fleet:
Moving_To_Fleet_Thread = Create_Thread("Moving_To_Fleet")

local My_Squadron = Find_First_Object("Squadron_Name")

-- For all units in Spawned_Squadron do
for each,Unit in pairs(My_Squadron) do
-- If exist move each unit to the position of a random unit unit in our Main Fleet
if TestValid(Unit) then
-- The purpose of this and the function before is to send the squad to main fleet.
Unit.Move_To(Main_Fleet[Random_Number_2])
end
end



-- Paste this to bottom of your Script:

function Attack_Nearest_Enemy ()

-- First getting Owner of the Object and then his enemy player
local enemy_player = Object.Get_Owner().Get_Enemy()

-- (Re)Searching for all ships
local Bigger_Units = Find_All_Objects_Of_Type("Transport | Corvette | Frigate | Capital")

-- For all bigger Units, do
for each, Unit in pairs(Bigger_Units) do
if TestValid(Unit) then
-- Finding the nearest spaceship unit of the enemy player
Closest_Enemy = Find_Nearest(Unit, enemy_player, true)
end
end

end


-- Run this in the body of your State:

-- Calling the thread at the end of this script, to move our unit to a random unit in the opponent fleet:

Attack_Nearest_Enemy_Thread = Create_Thread("Attack_Nearest_Enemy")

-- Waiting until the unit is ready to move away
Sleep (1.0)

-- Sending the Object to attack this unit from the thread
Object.Attack_Move(Closest_Enemy)



If you need more examples of how to use the functions, I found these very useful lua scripts in order to figure out how to get a lua script working. For story scripts here:
ultimate-empire-at-war.com/svn/trunk/Data/Scripts/Story/

For GameObject and Ability scripts here:
ultimate-empire-at-war.com/svn/trunk/Data/Scripts/GameObject/


-// You made it. End of Tutorial!!
[/color]

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

  • Locutus
  • Locutuss Avatar
  • Offline
  • Administrator
  • Administrator
  • [SGMG] Project Co-Lead, Lead Coder, 3D-Artist
Mehr
27 Mär 2015 13:27 #72006 von Locutus
Wow, looks as if someone got too much free time:p
Very extensive tutorial, I'm sure will be useful for a bunch of people, great work!

I just skimmed trough the tut, couldn't let go of a few thoughts:

Imperial schrieb: LUA doesn't need to be compiled, which is a great advantage.

Of course it will be compiled. You just don't need to do it manually:p

Imperial schrieb: . Calls a function, you need to have one variable or object in front of the. dot and the function you want to call after the .dot, so  variable.function() = variable call function()

And what about
function TalkToMe()
   alist = {word1 = "hello", word2 = "how", word3 = "are", word4 = "you"}
   return alist.word1
end
:p

Imperial schrieb: .. Two dots are counting variables. If two..dots are between 2 variables lua will interpret them like a , sign.
1..2 = 12

Two dots aren't counting anything; it's just the expression for concatenating strings.
In this case the numbers 1 and 2 will be implicitly converted into a string.

Imperial schrieb: The two directories of the vanilla game for scripts are:

.\LucasArts\Star Wars Empire at War\GameData\Data\SCRIPTS
.\LucasArts\Star Wars Empire at War Forces of Corruption\Data\SCRIPTS

You should probably mention how to get those scripts. Since using the meg extractor isn't very helpful because most of the scripts are compiled you'll need to install the map editor in order for the vanilla scripts to pop up in your mods/source folder.

Imperial schrieb: What is a State?
Its a function that can be executed once (OnEnter) or repeating on each refresh (OnUpdate).

Oooor: OnExit. But agreed, one barely uses that.

Imperial schrieb: Never write "Find_All_Objects_Of_Type()" into the Definitions function, write it into State_Init or the other states instead, because Definitions function is for variable setup only. And "Find_First_Object()" better avoid as well.

Better: Avoid anything which refers to a map object because the map is not fully loaded at this point.

Imperial schrieb: The Variable ServiceRate defines how fast the "OnUpdate" states will refresh, 0.1 means 1 second and 0.05 is half a second..

Sure about this? I always thought that it's faster.

Imperial schrieb: Functions are a lot simpler then states, they are also called "Threads".
Example:
FunctionName()

   -- Execute Code Block

endYou can always call a function using
FunctionName_Thread = Create_Thread("FunctionName")And you can run a Function from inside a thread/function.

A Thread is a special kind of function which can run parallel to other Threads or States.
You can still create regular functions such as
function State_PrintText()
local text = "r2d"
SomeFunction(text .. "2")
end

function SomeFunction(text)
if text == "r2d2" then
--do sth.
end
end

Imperial schrieb: Here is the list from the everythingeaw forums:

Want me to scroll forever? Spoiler this! :p

Imperial schrieb: As I just sayed:

Just because it is driving me crazy to see this wrong over and over again: The past tense of "say" is "said" :p
Also, its "their" not "theyr" ;)

Imperial schrieb: Most coders use "for k, Name in pairs .." and other single letters.
I don't like this because if you view your script 1 year later, you have no idea what the letter k stands for.
Better use variables that exactly describes what it is good for.

It is very obvious or you misunderstand how that works;)
Example:
alist = {"Locutus", "is", "awesome"}
for i,name in pairs (alist) do
print("The " .. i .. ". element of the list is " .. name)
end

This will print:
The 1. element of the list is Locutus
The 2. element of the list is is
The 3. element of the list is awesome

So "i" is the iterator and "name" the value assigned to it.
You can name them whatever you want because well ... it is just darn obvious^^ (My opinion at least)


Feel free to delete this comment after reading if you want to keep this tutorial clean, I just thought it couldn't hurt to give a little input.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
27 Mär 2015 19:17 - 27 Mär 2015 19:22 #72008 von Imperial

Wow, looks as if someone got too much free time:p

Yeah, I had this already for about 3 months on my todo list and always evaded it by now because I knew it will be pretty much. I was in a productive mood so it took "only" 2 days from the first line until everything was online. At the end of the day my fingers literally burned :lol:

Locutus schrieb: Feel free to delete this comment after reading if you want to keep this tutorial clean, I just thought it couldn't hurt to give a little input.

No, this tutorial is free for discussion. First of all thank you for taking the time to read this.
And every critics and chances for improvement are welcome here.

Of course it will be compiled. You just don't need to do it manually:p

Im so grateful for that. Its soo painful to always have to compile before you can test.

Just because it is driving me crazy to see this wrong over and over again: The past tense of "say" is "said" :p
Also, its "their" not "theyr"

Wtf I ran yesterday the whole tutorial through
www.onlinecorrection.com/

Both mistakes were once only here. Sorry I know I have some trouble distinguishing ie and y words, and with double letters. Will try to do something about it. Corrected them in the tutorial.

Locutus schrieb:

Imperial schrieb: The Variable ServiceRate defines how fast the "OnUpdate" states will refresh, 0.1 means 1 second and 0.05 is half a second..

Sure about this? I always thought that it's faster.

At least in one of my tests I had 0.05 and the Icon of a unit teleported at a certain speed, then I deleted the ServiceRate variable from the definitions but ingame nothing seemd to change.. ergo the default rate is whether 0.05 or it somehow stored my last value of ServiceRate somehow ingame (which is unlikely).

But it can always be setted to 0.03 or faster ^^

It is very obvious or you misunderstand how that works;)
You can name them whatever you want because well ... it is just darn obvious^^ (My opinion at least)

Yeah from the point of view of a experienced scripter it is damn obvious, but when I was about to do my first script I had serious trouble to get a single table based for statement working because I had no idea what all these damn single letters stand for.

Sometimes i ist used for i=i +1 othertimes it is for i, j in pairs () or for hell knows what else.

My point was the coder could transform this into kind of human language:
for each, WhatEver in pairs

Not only for this situation but generally.

Because of all other things you suggested, Locutus; already changed them in the tutorial.
I will still have a final read through in the night to polish the last details.


Edit:

By the way, I'd like to have this tutorial linked on our Modd page in the tutorials section, like this
www.moddb.com/mods/stargate-universe-destinys-journey/tutorials
Letzte Änderung: 27 Mär 2015 19:22 von Imperial.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
07 Aug 2015 17:13 #74166 von Imperial
Lets talk about dummy abilities. They are any ability you use in order to trigger your lua script by any of your units (that executes your lua script through the <Lua_Script> tag).

Usually you'd assign in the Definitions function of your ability script a dummy ability, like this:
ability_1 = "Name"
ability_2 = "Name"

Thats the easy way ^^
This is a more complicated way I like to share with you, it pays off to use this if you do more ability scripts.

Well I write lot of ability scripts and dont feel like always checking to have the right ability written in this tag, sometimes I change the abilities in the unit.xml and then I'd also need to change it in the lua script.
So I wrote these lines to autodetect which unit.xml uses which abilities, and autoassign them.
To use auto assignement simply make sure the 1st ability of your unit is the first one in the Abilities table and copy/paste these lines at the very beginning of your State_Init state:

--======= Auto Assigning Abilities, you'll never have to bother with this again! =======
-- Note: ALLWAYS put the left ability of your unit at the beginning in 1st position of this list, or the abilities will be twisted and it wont work!
Abilities = {"WEAKEN_ENEMY", "HARMONIC_BOMB", "CLUSTER_BOMB", "POWER_TO_WEAPONS", "TURBO", "STEALTH", "DEFEND", "INVULNERABILITY",
"HUNT", "SPOILER_LOCK", "SELF_DESTRUCT", "BLAST", "BARRAGE", "ROCKET_ATTACK", "DEPLOY_SQUAD", "ENERGY_WEAPON",
"SHIELD_FLARE", "FULL_SALVO", "LUCKY_SHOT", "ION_CANNON_SHOT", "SUPER_LASER", "CONCENTRATE_FIRE", "CAPTURE_VEHICLE", "EJECT_VEHICLE_THIEF", "FOW_REVEAL_PING",
"SENSOR_JAMMING", "CORRUPT_SYSTEMS", "LEECH_SHIELDS", "DRAIN_LIFE", "LURE", "FORCE_LIGHTNING", "MISSILE_SHIELD", "AREA_EFFECT_STUN", "SPRINT"
}

-- We cycle through all abilities in this list and assign the latest one that matches a ability of the Ship to ability_2
for each,String_Name in pairs(Abilities) do
-- If this unit has one of these abilities, it will be assigned as primary ability:
if Object.Has_Ability(String_Name) then
ability_2 = String_Name
end
end


ability_1 = false

-- Then we cycle again through the list and assign the other matching ability, which must NOT be ability_2:
for each,String_Name in pairs(Abilities) do
-- If this unit has one of these abilities, it will be assigned as primary ability:
if Object.Has_Ability(String_Name) and ability_2 ~= String_Name then
ability_1 = String_Name
end
end

-- If the unit has only 1 ability, ability 1 wont be assigned, so we'll take ability_2:
if ability_1 == false then
ability_1 = ability_2
end

-- Instead of these 2 loops above you can use the regular way: simply assign 2 abilities: ability_1 = "Name_1" and ability_2 = "Name_2"
-- ability_1 = "Name"
-- ability_2 = "Name"


Ah, if you use other variables then ability_1 or ability_2 (caution its case sensitive) you indeed need to adjust them.

By the way I pasted all types of spaceunit ability I was able to find in the Abilities list.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Mehr
20 Aug 2015 19:02 #74321 von Imperial
Because of the .Play_Animation command:

-- Replace "Idle" with name of Animation, don't name it like "Idle_00", works without numbers in the name.
-- The second parameter repeads this animation in a loop if set to true

.Play_Animation("Idle", false, 0)


Forgot to mention, that if you make a own animation, it MUST have one of the original _Suffix.ala of the game, or the game wont play your animation. Actually you can browse the .\Data\Art\Models of the Vanilla game for more animation names.
Here are the most common of them:

Idle
Movestart
Attack
Attackidle
Move
Walkmove

Build
Land
Take_off

Deploy
Undeploy

Celebrate
Crouchattack
Crouchidle
Crouchmove

You'll probably need the idle, move and attack animation for their original purposes, so you can always take build, land and similar for your own animations. It doesent matter which one you take, and you dont need to write the names CAPITAL or with NAME_00 numbers. It only matters that you take a name of a existing animation.

Bitte Anmelden oder Registrieren um der Konversation beizutreten.

Ladezeit der Seite: 1.153 Sekunden