0% found this document useful (0 votes)
6 views

Scripting

pes 2019

Uploaded by

kibinov707
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
6 views

Scripting

pes 2019

Uploaded by

kibinov707
Copyright
© © All Rights Reserved
Available Formats
Download as TXT, PDF, TXT or read online on Scribd
You are on page 1/ 13

Sider 5 Lua Module Programmers Guide

====================================
For sider.dll version: 5.2.3
April 4, 2019

1. Introduction
~~~~~~~~~~~~~~~

The main idea here is to provide a safe and flexible way to


extend Sider functionality. It allows modders to alter many
aspects of game logic, and load all sorts of extra content.
Visual and gameplay tweaks are possible too.

Instead of offering a C interface, where the programmers would need


to write a DLL, i'm a taking a different approach here and trying to
design a system, where extension modules are written in Lua. A module
would typically initialize itself by registering for certain events,
and then Sider will call the module functions, when those events
occur later in the game.

Lua is by now a well-established language of choice for scripting


support in games, used all over the gaming industry. Most famous
example is probably WarCraft, but many modern games use it, including
Pro Evolution Soccer itself.

To boost the performance, Sider uses a just-in-time compiler for Lua


called LuaJIT, written by Mike Pall. LuaJIT is a truly brilliant piece
of software. It is 100% compatible with Lua 5.1, and it is super fast,
often approaching and sometimes exceeding the speed of C code.
( More information here: https://luajit.org )

After reading this guide, the next step is to study the example (and
non-example) modules, which are provided with this release of Sider.
Find them in the "modules" directory.

2. Module structure
~~~~~~~~~~~~~~~~~~~

If you are familiar with Lua and how modules are typically organized
then this will make full sense to you. If are you new to Lua, i would
strongly recommend reading "Programming in Lua" by Roberto Ierusalimschy.
2nd edition covers Lua 5.1, which is the version of the language used
by Sider. However, any other edition of the book will be just as helpful.

In any case, the module organization is pretty simple:

a) Your need to create a new table


b) Provide an "init" function in that table, where you need to
do any initialization logic for the module and register for the events
your module is interested in.
c) Return that table as the last statement

Example module:

-----------------------------
local m = {}

function m.init(ctx)
log("Hello, world!")
end

return m
-----------------------------

As you have already guessed, this module doesn't really do much. But it
is a valid module, and can be loaded by Sider. For that you need to save
it as <something>.lua file in the "modules" folder, inside sider. Let's
assume that you named it: test.lua. Then, you must also enable it in
sider.ini, like this:

lua.module = "test.lua"

**************************************************************************
**************************************************************************
VERY IMPORTANT: File encoding must be UTF-8. This is vital, if you are
using non-latin characters in the strings in the module code - for example,
in paths. If you only have latin-1 chars, then ANSI is ok too.
**************************************************************************
**************************************************************************

If you now run the game, your module will get loaded by Sider, and then
the "init" function will be called, so you should see a "Hello, world!"
message in sider.log.

If you made a mistake and your module has a syntax error, for example,
or some other problem, then you should see an error message in sider.log,
explaining where (on which line of the script) the problem occurred.

Let's now see how you can make a more useful module. First step for that
is to understand the context object (ctx).

3. Context object
~~~~~~~~~~~~~~~~~

Context object is what Sider "knows" about the current state


of the game. As more scripts/modules are created by the community
the context object will probably also change, and include more and more
information about the game.

As of 5.2.2 release, the context object has the following:

ctx.home_team - id of the home team of the current match, after


it is selected in the exhibition match, or the next
match is determined in the league mode.

ctx.away_team - id of the away team of the current match, after


it is selected in the exhibition match, or the next
match is determined in the league mode.

ctx.tournament_id - numeric id of the current tournament. See


doc/tournament.txt file for the list
of ids for all of the game's tournaments.

ctx.match_id - some sort of number, which seems to indicate where


in the season you currently are. However, the exact
meaning of this id is unclear. Maybe you can figure
it out and tell me ;-)

ctx.match_leg - set to 1 or 2, if this is 1st leg or 2nd leg of


a two-legged knock-out match.

ctx.match_info - meaning of this field varies, depending on the type


of tournament:
league: number of league matches already played
cup/play-off:
46 - first round of play-off,
47 - second round of play-off,
51 - quaterfinal,
52 - semifinal,
53 - final
You can use this together with ctx.tournament_id to
identify a final match of a particular tournament.

ctx.stadium - numeric id of the stadium that will be used (or was


last used, if the stadium for the next match hasn't
been chosen yet by the game logic)

ctx.stadium_choice - This field is set when the game is in one of the


exhibition modes, and the stadium is allowed to
be selected via the menu. Once the actual final
selection of the stadium is made (right after the
"set_stadium" event fires), the "stadium_choice"
field get removed from the context.
Special values:
253 - home stadium
254 - random stadium
255 - not chosen yet

ctx.timeofday - The values are: 0 - Day, 1 - Night. Same as with


stadium, it represents the time of day of the coming
match, or the last one, if the stadium isn't set yet
for the next match.

ctx.season - The values are: 0 - Summer, 1 - Winter.

ctx.weather - The values are: 0 - Fine, 1 - Rainy, 2 - Snowy

ctx.weather_effects - The known values are:


2 - enforce weather effects (rain/snow falling)

ctx.match_time - Duration of the match in minutes.

The context object also contains a register function, which has the
following signature:

ctx.register(event_name, handler_function)

The event_name parameter is a string, which identifies a particular


event. The handler_function should be a function in your Lua module,
which will be called when the corresponding event happens. Parameters
vary depending on the event - see the next section for details on all
supported events and their handler signatures

The idea of the context object is that in your handler functions, your
code will make decisions on what to do, using the information in the
context.

4. Supported events
~~~~~~~~~~~~~~~~~~~

- Event name: "livecpk_make_key"


Handler function: f(ctx, filename)
Return value is expected to be a string or nil.

This event occurs when the game needs to find out some information
about the file, specified by relative filename. Maybe the game needs
to create a buffer, or it needs to determine the filesize, or something
else. Your handler function is expected to return a string key that
will be used for caching of "livecpk_get_filepath". You can just
return filename as the key, without any changes, or return nil -
which will result in the same outcome. But sometimes, you need to make
a different key, because the context has some additional information
that is important.

IMPORTANT: This event can fire a lot of times for a given filename,
so try to avoid doing expensive computations in the handler function
or logging anything, because it may affect your frame rate.

- Event name: "livecpk_get_filepath"


Handler function: f(ctx, filename, key)
Return value is expected to be a string or nil.

This event is related to "livecpk_make_key".


It works like this: after the key is returned by livecpk_make_key
handler, sider needs to know which actual file (absolute path) needs
to be loaded for this key. So your handler function is expected to
return a full absolute filename. Sider will cache this absolute filename
using the key returned by livecpk_make_key, and the next time this file
is needed, the livecpk_get_filepath event will NOT fire. (This is
again done for performance reasons so that we don't unnecessarily seek
the disk).

- Event name: "livecpk_rewrite"


Handler function: f(ctx, filename)
Return value is expected to be a string or nil.

Allows the filename to be rewritten to another. This is a very


powerful, but also quite dangerous function, if you are not careful.
If you rewrite the filename to something that does not exist in
LiveCPK roots or in download/data CPKs, and none of your modules
provide the content, then the game will be unable to load the file,
which can lead to different behaviours, depending on type of file being
loaded. If it's a DDS texture, then usually nothing bad happens -
you just get a white texture. But if it is a model file - you will
get an endless loop, where loading indicator will keep spinning
forever, or the game can just crash. So, be careful,
and don't rewrite blindly ;-)

See "kitrewrite.lua" module for an example of rewrite usage: it is


loading a 2nd player kit instead of a 1st goalkeeper kit, so your
goalkeepers end up wearing 2nd kit of outfield players.

- Event name: "livecpk_read"


Handler function: f(ctx, filename, data, size, total_size, offset)
Return value: nil.

Provides a pointer to the buffer in memory ("data") that have just


been filled with data. The "size" parameter gives the number of
bytes that have just been read into that buffer. The other parameters
provide the additional context for the read:
- filename: the relative pathname of the file,
(For example, "common\pesdb\etc\Team.bin")
- total_size: total size (in bytes) expected to be read
eventually for this file
- offset: offset in file before the read happened

See "filedump.lua" and "tracer.lua" modules for example usage of


this event. This is an advanced feature, which you will need if
you wanted to examine the data that the game is about to use.
Care must be taken to not leak memory. Also, be aware that some
large files are not read all-at-once, but instead in multiple
read operations. In such situation, use "offset" and "total_size"
parameters to check if the file has been fully read, like this:

if offset + size >= total_size then


-- last read: we now got all of the data
-- do something with it
...
end

See "filedump.lua" for complete example of handling both


multi-read and single-read files.

- Event name: "set_teams"


Handler function: f(ctx, home_team, away_team)
Return value expected: nil

This event fires after both home and away teams are determined -
either during the team selection in exhibition game, or when the next
match becomes known in a league or a cup mode (League, Master League,
UCL, Europa League, etc.)
The team ids are also set as "home_team" and "away_team" fields in
the context object so that they can be used later, if needed.

- Event name: "set_match_time"


Handler function: f(ctx, minutes)
Return value expected: nil or integer

This event occurs, when the game sets the mach duration. If your handler
function returns an integer, then this value will be used as the match
time in minutes. This way you can accelerate or slow down the matches
beyound the allowed 5-30 minute range. See timeaccel.lua - for an example
of such script.

- Event name: "set_stadium_choice"


Handler function: f(ctx, stadium_id)
Return value expected: nil or stadium_id

This event fires, when the game prepares to display the stadium image
or when it is entering pre-game menus of non-exhibition modes. In addition
to the actual id of the stadium chosen, the "stadium_id" parameter may have
the following special values:
253 : "home stadium"
254 : "random stadium"

You handler function can change it, if it returns an integer value instead of nil.
This integer value can either be a stadium id, or one of the following special
values, mentioned above.

NOTE: the final stadium selection isn't actually made, until after the
"set_stadium" event. So, if you want to change the stadium, or see what
was eventually chosen as random/home stadium, then you will need to also register
for the "set_stadium" event.

- Event name: "set_stadium"


Handler function: f(ctx, options)
Return value expected: nil or number or table

This event fires, when the stadium settings are chosen for the upcoming
match. The "options" parameter is a Lua table which contains the following
keys: "stadium", "timeofday", "weather", "weather_effects", "season".
Each of these has an integer value, as the game uses:
for stadium - it is the id of the stadium,
for timeofday: 0 - means Day, 1 - means Night;
for weather: 0 - Fine (sunny), 1 - Rain, 2 - Snow;
for weather_effects: 2 - means enforce falling rain/snow, other values - unknown
for season: 0 - Summer, 1 - Winter

You handler function can either return nil, which means that other modules
can receive the event and process it. Or, the handler can return an stadium
id - an integer - to switch the stadium to another one. Be careful though:
sider doesn't check for correctness of values, so if you switch to a
non-existent stadium, the game will probably crash or go into infinite
"loading" loop. For an example usage - see stadswitch.lua module.

( For backwards compatibility, returning a table like this:


{ stadium = <stadium-id> } is also supported. However, any other keys in
that table will be ignored. )

To change weather, timeofday and season - use a different event, called:


"set_conditions", which is documented further down in this document.

- Event name: "set_conditions"


Handler function: f(ctx, options)
Return value expected: nil or table
This event fires, when the stadium settings are chosen for the upcoming
match. The "options" parameter is a Lua table which contains the following
keys: "stadium", "timeofday", "weather", "weather_effects", "season".
Each of these has an integer value, as the game uses:
for stadium - it is the id of the stadium,
for timeofday: 0 - means Day, 1 - means Night;
for weather: 0 - Fine (sunny), 1 - Rain;
for weather_effects: 2 - means enforce rain falling, other values - unknown
for season: 0 - Summer, 1 - Winter

You handler function can either return nil, which means that other modules
can receive the event and process it. Or, the handler can return a table
of options, which are either modified or not. Returning a table of options
stops further propagation of the event. You cannot change the stadium id -
for that use "set_stadium" event. But you can change any of the other
three settings: just assign them different values.
For an example usage - see stadswitch.lua module.

- Event name: "after_set_conditions"


Handler function: f(ctx)
Return value expected: nil

This event fires after "set_conditions". It doesn't allow the handler


to change anything, but it does provide the context object so that the
modules can react in whatever way they want.

-- Event name: "get_ball_name"


Handler function: f(ctx, ballname)
Return value expected: nil or string

This event fires, when the game prepares to display the ball name.
Your handler function can change it, if it returns a string instead of nil.
The string needs to be in UTF-8 encoding to correctly render non-ASCII
characters.

-- Event name: "get_stadium_name"


Handler function: f(ctx, stadium_name, stadium_id)
Return value expected: nil or string

This event fires, when the game prepares to display the stadium name.
You handler function can change it, if it returns a string instead of nil.
(The "stadium_id" parameter is provided to handler function only as additional
information - for which stadium the name is being read/modified)
The string needs to be in UTF-8 encoding to correctly render non-ASCII
characters.

-- Event name: "trophy_rewrite"


Handler function: f(ctx, tournament_id)
Return value expected: nil or number

This event fires before the game checks if trophy scenes need to be shown
before (and after) the match. This is a specialized event, and is probably
not very useful for modules other than "trophy.lua". The "trophy.lua"
uses to enforce trophy scenes from specific tournaments. This makes it
possible to have trophy celebrations for tournaments that do not have
them in the original game content. (See trophy.lua, if you are really
interested in how this works)

-- Event name: "overlay_on"


Handler function: f(ctx)
Return values expected: string, string, table

All return values can be nil, and also the handler may return three or two,
or one, or not return anything at all.
1st return value: text to display
2nd return value: full filename of the image to display
3rd return value: table with layout options

When the overlay is on, and the current Lua module is in control of the
overlay, this event fires once for each frame that is displayed by the game engine
(So, normally - 60 times per second). The returned string is what will be
displayed by the overlay. The logic that generates this string should not be
too heavy: too much processing may affect the frame rate.
See examples in modules: overdemo.lua and camera.lua

The text and image are displayed side by side: text - on the left,
image - on the right. The layout options table allows to specify some
formatting for the image. The following ones are supported:

"image_width" : sets the width of the image on screen


- values > 1 will be interpreted as desired width in pixels
- values < 1 will be interpreted as fraction of the total
screen width.
"image_height": sets the height of the image on screen
- values > 1 will be interpreted as desired height in pixels
- values < 1 will be interpreted as fraction of the total
screen height.
"image_aspect_ratio": a floating point number (width/height).
This options allows to enforce aspect ratio that is
different from the source image. This value is NOT
used if both image_width and image_height are specified.
"image_hmargin": whitespace margin in pixels to be placed to the left and to
the right of the image.
"image_vmargin": whitespace margin in pixels to be placed to the top and to
the bottom of the image.

Final dimensions of the image on screen are calculated using two of the three
options: "image_width", "image_height", "image_aspect_ratio" - in this order
of priority. If only 1 (or none of the three) are specified, then a default
width of 0.1 of total screen width is assumed, and the original aspect ratio
of the image is used to calculate the height.

Transparency of overlay images can be controlled by "overlay.image-alpha-max"


settings in sider.ini. This setting affects all modules and cannot be changed
at runtime. See readme.txt for details on how this settings works.

-- Event name: "key_down"


Handler function: f(ctx, vkey)
Return value expected: nil

When the overlay is on, and the current Lua module is in control of the
overlay, this event fires when user presses down any key on the keyboard. The
so-called "virtual key code" will be passed as the value of "vkey" parameter.
Your function can that take appropriate action, if it wants to react to
such key events. A combination of "overlay_on" and "key_down" events can
be used to build simple UIs in the overlay itself.
See example of such UI in camera.lua

-- Event name: "gamepad_input"


Handler function: f(ctx, inputs)
Return value expected: nil

When the overlay is on, and the current Lua module is in control of the
overlay, this event fires when user presses or releases a button, or moves
a stick or d-pad of the game controller. (If you have multiple controllers
attached, only one will generate these input events). The "inputs" parameter
is always a table containing at least one, but possibly more than one mapping
of: input-name --> input-value. The "input-name" is a symbolic name that
identifies the source of input: a button, stick, d-pad. See the following
table for all possible combinations:

input | input-name | input-values


--------------------+--------------+-----------------
Button 0 | A | 0,1
Button 1 | B | 0,1
Button 2 | X | 0,1
Button 3 | Y | 0,1
Button 4 | LB | 0,1
Button 5 | RB | 0,1
Button 6 | START | 0,1
Button 7 | BACK | 0,1
Button 8 | LT | 0,1
Button 9 | RT | 0,1
Button 10 (LS push) | LS | 0,1
Button 11 (RS push) | RS | 0,1
LS (left/right) | LSx | -1,0,1
LS (up/down) | LSy | -1,0,1
RS (left/right) | RSx | -1,0,1
RS (up/down) | RSy | -1,0,1
D-pad | DPAD | 0,1,9,8,10,2,6,4,5

For buttons, 0: pressed, 1: pressed.


For sticks, horizontal: -1: left, 0:middle, 1:right
vertical: -1: down, 0:middle, 1:up
For D-pad, 0: not pressed, 1:up, 2:down, 4:left, 8:right
5:up/left, 6:down/left, 10:down/right, 9:up/right

Best way to verify what names correspond to what buttons on your


controller, is to try the "inputdemo.lua" from the modules folder.
It uses overlay to display the last 20 input events - both from
keyboard and from gamepad.

If your module is registered for "key_down" event, but not for the
"gamepad_input" event, then sider will automatically map gamepad events
into keyboard events, and emit those, for such gamepad events that are
defined in the "global input mapping" configuration - in gamepad.ini.

If you do not want this automatic mapping, make sure to register for
the "gamepad_input" event in your module, and then do whatever is needed
with the inputs. (You may choose to completely ignore them too, if you
want. See example of that in camera.lua)

IMPORTANT NOTE: Some events can fire multiple times for the same "action".
That is normal, it's just how the game works internally. Make sure your
module logic can handle such situations correctly.

5. Logging
~~~~~~~~~~

Sider provides a function called "log". This can be used to print


out any information you want into the sider.log file.
You can use string.format to format your message in a way similar
to what you would do with C printf:

log(string.format("My value is: %0.5f", math.pi))

In sider.log it will appear with a module name prefix, like as:

[<modulename>.lua] My value is: 3.14159

6. Module environment
~~~~~~~~~~~~~~~~~~~~~

Each module runs in its own environment. For detailed explanation


on what an environment is - read about Lua environments in the Lua
manual online, or in Programming in Lua book. What is important
here is that a module has access to a limited set of globals:

Standard Lua:
assert, ipairs, pairs, tostring, tonumber, table,
string, math, unpack, type, error, io, os, _VERSION, _G
Sider:
log, memory, _FILE

You can also enable "ffi" and "bit" modules, which are LuaJIT
extensions. By default, they are disabled. To enable, modify your
sider.ini like this:

luajit.ext.enabled = 1

By the way, your module can "investigate" and find out what exactly
is available for it to use - this is not hard, and is left as an
exercise for the reader ;-) Or... you can cheat, and look at env.lua
module.

7. Memory library
~~~~~~~~~~~~~~~~~

The "memory" library provides a set of low-level functions that


may prove useful if you're doing some advanced modding.
For example, you need some game state info that is not available in
sider's context object and isn't communicated through events either.
Or you want to modify some bytes in memory, because you feel really
adventurous.

**********************************************************************
**********************************************************************
IMPORTANT WARNING: PLEASE USE THIS LIBRARY WITH CARE AND CAUTION,
AND IF AND ONLY IF YOU KNOW WHAT YOU'RE DOING. REALLY.

THESE ARE POWERFUL TOOLS, BUT THERE ARE ALSO DANGEROUS, BECAUSE
WRITING INTO A WRONG PLACE IN MEMORY CAN HAVE DISASTROUS CONSEQUENCES.
ALWAYS TRY TO HAVE A BACKUP COPY OF YOUR EDIT DATA AND SAVEGAME FILES.
**********************************************************************
**********************************************************************

memory.read(addr, n)

This function reads (n) bytes at memory address (addr).


Return value: string of n bytes at given memory address

memory.write(addr, str)

This function writes the string of bytes (str) at the address (addr).
Return value: nil

memory.search(str, start_addr, end_addr)

This function searches for the string of bytes (str), in the range
of memory addresses between start_addr and end_addr.
Return value: address, at which the string of bytes was found
or nil, if the string was not found.

memory.pack(format, number)

This function converts a Lua number into one of the supported binary
formats (little endian). The "format" parameter is a string that should
have one of the following values:
"f" : 32-bit float,
"d" : 64-bit double-precision float,
"i64" : 64-bit signed integer,
"u64" : 64-bit unsigned integer,
"i32" : 32-bit signed integer,
"u32" : 32-bit unsigned integer,
"i16" : 16-bit signed integer,
"u16" : 16-bit unsigned integer
Return value: string of bytes, representing the number in the format
specified by the "format" parameter

memory.unpack(format, str)

This function converts a string of bytes (str) into a Lua number, using
the format parameter to interpret the binary spec. The same values are
supported for "format" param as in memory.pack function.
Return value: a Lua number, converted from binary representation

These last two functions (memory.pack/memory.unpack) are useful, when


used together with memory.read and memory.write, when you need to read
and modify values in RAM and you know what binary formats are used for
those values. See modules/memtest.lua - as the example module that
demonstrates the usage.

memory.hex(value)

Utility function to output value in hexadecimal format.


Depending on the type of value, the output differs slightly:

local s = 'Football'
log(memory.hex(s)) --> prints "466f6f7462616c6c" in the log

local n = 12345
log(memory.hex(n)) --> prints "0x3039" in the log

memory.get_process_info()

This function queries the game process (PES2019.exe) for information


about where it is loaded in memory.
Return value: a table containing at least two keys:
"base" - base address of the main process in memory
"sections" - a table of tables, with each member table containing
at least these keys:
"name" - name of the section
"start" - starting address in memory
"finish" - first address beyond this section

memory.search_process(s)

This function searches for a string s in memory, but unlike memory.search


function, it does not take start and finish addresses. Instead, it searches
the game process sections, one at a time, until it finds the string, or
until all sections are examined.
Returns 2 values:
1) address of string s in memory, if found, or nil - otherwise
2) table with section info, in which the string was found.

8. Zlib library
~~~~~~~~~~~~~~~

This library contains a small set of utility functons to work with


compressed data. An example usage could be a combination of using a
"livecpk_read" event to get the data, as it is read by the game, and
then unpacking it, if it is compressed ("zlibbed").

zlib.compress(data)
Takes the string of data and compresses it.
Returns 2 values:
1) string with compressed data, or nil - if an error occured
2) nil, or string with error message, if something went wrong

zlib.uncompress(compressed_data, uncompressed_size)

Decompresses the give string of data. The second parameter is


optional: if you know the size of uncompressed data, then it is more
efficient to provide it. If not, then uncompress will try a big
enough buffer (3-times the size of compressed data)
Returns 2 values:
1) string with uncompressed data, or nil - if an error occured
2) nil, or string with error message, if something went wrong

zlib.pack(data)

Creates a data structure that consists of "WESYS" header, used by


Konami in PES games, followed by raw compressed data. The size of
the WESYS header is 16 bytes, and its format is as follows:
bytes 0-2: magic bytes (version): 00 01 01
bytes 3-7: "WESYS"
bytes 8-11: compressed_size
bytes 12-15: uncompressed size
Returns 2 values:
1) string containing the data structure, or nil - if an error occured
2) nil, or string with error message, if something went wrong

zlib.unpack(data)

Checks if the input is in Konami format of WESYS header, followed


by the compressed data. If so, it will try to uncompress the data,
using the size information from the header. If the data is not in
the expected format, then it is returned as is - unmodified.
Returns 2 values:
1) string containing uncompressed bytes, or original data,
if the input is not in WESYS-format, or nil, if some
error occured
2) nil, or string with error message, if an error occured.

You might also like