×

Langue

Fermer
Atelier 801
  • Forums
  • Dev Tracker
  • Connexion
    • English Français
      Português do Brasil Español
      Türkçe Polski
      Magyar Română
      العربية Skandinavisk
      Nederlands Deutsch
      Bahasa Indonesia Русский
      中文 Filipino
      Lietuvių kalba 日本語
      Suomi עברית
      Italiano Česky
      Hrvatski Slovensky
      Български Latviešu
      Estonian
  • Langue
  • Forums
  • /
  • Transformice
  • /
  • Modules
  • /
  • [Tutorial] Submodes
[Tutorial] Submodes
Bolodefchoco
« Sénateur »
1527706260000
    • Bolodefchoco#0095
    • Profil
    • Derniers messages
    • Tribu
#1
  5

Several EN friends have asked me how to make a submode system just like #grounds, so this is the thread. I'll make it the way I think the performance wins, but there are many other ways (and even easier, but with a bad performance / flexibility) (imo)


I find interesting an initial table with the module info, as its name, the author, version, etc.
Example: https://github.com/Lautenschlager-id/Grounds/blob/master/src/Module.lua

In this tutorial we'll create it, but with only index that is _NAME, that, as the name shows, is the default module name. It'll tell the script what's the module to run / running!



Code Lua

1
2
3
local module = {
_NAME = "first"
}

Now we'll create the table mode that is fundamental in the system. It'll be the main table for every single module in your script! For now, we'll just create it.

Example: https://github.com/Lautenschlager-id/Grounds/blob/master/src/Modes.lua



Code Lua

1
local mode = {}

Now we'll create a new event that is going to be triggered in every module change.

Example: https://github.com/Lautenschlager-id/Grounds/blob/master/src/ModeChanged.lua



Code Lua

1
2
3
4
5
eventOnModeChange = function()
-- Here you reset all the global info (not related to a specific mode)
-- as removing password, textareas, images...
-- Read the spoiler 1 for a help with that ^
end

1

To know what are the active textareas, popups and images in the current module, I recommend you to create a mini handler overriding some functions.

Firstly we create a global table. All the IDs of the "objects" listed above are going to be saved there.
Code Lua

1
2
3
4
5
system.objects = {
textarea = {},
popup = {},
image = {}
}

Then we make a scope to rewrite the needed functions
Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
do -- Creates a new scope, so we can't access it later
--- Images

local addImage = tfm.exec.addImage -- copies the tfm.exec.addImage function
tfm.exec.addImage = function(...) -- then we override it
local id = addImage(...)

if id then -- if nothing went wrong in the image creation
system.objects.image[id] = true -- we say that the ID is active in the module
end

return id
end

local removeImage = tfm.exec.removeImage
tfm.exec.removeImage = function(id)
removeImage(id)

-- If the image is active, then it will be removed, since image IDs are unique
system.objects.imagem[id] = nil
end

--- Textareas
local addTextArea = ui.addTextArea
ui.addTextArea = function(id, ...)
addTextArea(id, ...)

system.objects.textarea[id] = true
end
-- In this case we won't override ui.addRemoveTextArea because
-- the textarea IDs are not unique.
-- So there's not an efficient way to know if they are still active or not.

--- Popups
local addPopup = ui.addPopup
ui.addPopup = function(id, ...)
addPopup(id, ...)

system.objects.popup[id] = true
end
-- Same case of textareas
end

To use these info in eventOnModeChange, do:
Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
-- It's recommended to remove the image system if your script doesn't have images,
-- same for the textarea and popup system. Loops can get heavy.
eventOnModeChange = function()
-- We remove the active images
for id in next, system.objects.image do
tfm.exec.removeImage(id)
end

-- We remove the textareas
for id in next, system.objects.textarea do
ui.removeTextArea(id)
end

-- As it's impossible to remove a popup, we move them to the hell. (in this case, to the sky?)
for id in next, system.objects.popup do
ui.addPopup(id, "", nil, -1500, -1500)
end

-- For safety, we just recreate the table. So it'll be refreshed.
system.objects = {
textarea = {},
popup = {},
imagem = {}
}

--- Your stuff comes here
end

You can also:

  • Disable the binded keys and mouse of the players;
  • Remove color name;
  • Set the scores to 0;
  • Disable snow;
  • Reset the disable functions (AutoScore, AutoShaman, etc...)

      Code Lua

      1
      2
      3
      for k,v in next,{"AutoScore","WatchCommand","AutoNewGame","AutoShaman","AllShamanSkills","MortCommand","DebugCommand","MinimalistMode","AfkDeath","PhysicalConsumables","AutoTimeLeft"} do
      tfm.exec["disable" .. v](false)
      end

  • Reset the auto flip mode of the maps;
  • Reset the max. number of players in the room;
  • Remove the password...



Now that we've our event finished, we'll make another function to get the specified module and trigger it if the module is valid. A getter.

Example: https://github.com/Lautenschlager-id/Grounds/blob/master/src/GameMode.lua



Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- You can also make a timer to avoid mode change several
-- times in the same minute...
local getModule = function(moduleName, triggerEvent)
-- not not is a good practice to make any value a boolean, so the
-- variable will be way smoother than saving the whole module in it
-- (without not not)
local exists = not not mode[moduleName]

if exists then
module._NAME = moduleName

-- We just want to refresh the stuff when it is a mode change,
-- but not when it's the first time the script is triggered.
if triggerEvent then
eventOnModeChange()
end
end

return exists
end

Now one of the most important steps: defining the character that specifies the module name in the name of the room.

Example: https://github.com/Lautenschlager-id/Grounds/blob/master/src/RoomSettings.lua



Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-- We'll create a table to say the character and the function that handles the value post-character.
-- Read the spoiler 2 for an admin system according to the name of the room.

local roomSettings= {}
roomSettings[1] = {
-- My default is #, but i'll use & to avoid mixing it with some stuff.
char = "&", -- character that will be used to define the mode.
-- The _execute_ function will always receive a value, that is the characterVALUE_HERE
execute = function(moduleName)
if moduleName then
-- First time that it is executed, so the event won't be triggered
-- The module will be set only if it exists. Otherwise the default
-- module (initial module._NAME) will be active.
getModule(moduleName, false)
end
end
}
-- Here you'll be able to create more settings with different characters to your module.
-- Avoid: letters, numbers, characters that are not in the keyboard, +, %, $

-- Now we'll create a function to handle this table.

-- We need to check if the room is a #room or a tribe house.
-- For that, we'll check if the second byte of the name of the room is
-- different of 3 (that weirdo unicode in the tribe house names)
local isRoom = string.byte(tfm.get.room.name, 2) ~= 3

-- We'll also get the room parameters (if it is a room).
-- The pattern in the code below is defined by:
--[[
May start with *, but may not;
There must be a # after that;
Then, there must be the main module name (initial module._NAME);
So we need a number sequence (obligatory, [0:99999])
Finally, (.*), that means "everything after the number sequence". Its value
will be the _roomAttributes_.
]]
local roomAttributes = isRoom and string.match(tfm.get.room.name, "%*?#" .. module._NAME .. "%d+(.*)")

local setRoomSettings= function()
if roomAttributes then -- Verifies automatically if it is a room too
-- We'll check a "&letters_and_underscores"
-- So we'll run the _execute_ function of the & character
roomSettings[1].execute(string.match(roomAttributes, roomSettings[1].char .. "([%a_]+)"))

-- To handle more characters, read the spoiler 2
end
end

2

To add more characters in the room settings, we'll need to create more tables in roomSettings. We'll make an admin system with that.

First, make a table that will have all the admin names (not yours):

Code Lua

1
2
3
-- Notice that I use the table _system_, that is a Transformice table.
-- Use whatever you want. A variable, a table index...
system.admin = {}

Now we'll create the settings;.
Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
-- Make a function to normalize the player name
local normalizePlayerName = function(playerName)
-- All lower case. Only the first letter will be upper case.
return playerName:lower():gsub("%a", string.upper, 1)
end

roomSettings[2] = {
-- I'll use the character @, but you can use any. (avoid #)
char = "@",
execute = function(playerName)
if playerName then
-- Now we check if the nickname has more than two letters, because
-- the min. is 3 on Transformice.
if #playerName > 2 then -- >= 3
-- table[name] = true
system.admin[normalizePlayerName(playerName)] = true
end
end
end
}

And the function is done! To add it in setRoomSettings do:

Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
--- Before roomSettings[1]

-- Get the nickname + tag of the player
-- The pattern in the code below is defined by:
--[[
May start with a +, but may not;
There must be a sequence of lower case and upper case letters, numbers and underscores (_);
After that, there must be a # and 4 numbers (that is the player tag).
]]
for playerName in string.gmatch(roomAttributes, roomSettings[2].char .. "(%+?[a-zA-Z0-9_]+#%d%d%d%d)") do
roomSettings[2].execute(playerName)
end

And your admin system is done! All the players in the name of the room will be in the table system.admin.

To handle more characters (with the same pattern), you can make a loop like that:
Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
--- After roomSettings[1]

-- execute it only if needed, uh!?
if #roomSettings > 2 then -- "> 1" if your system won't have admins
-- All the characters in a string, except 1 and 2 (module and admin).
local characters = ""
for id, setting in next, roomSettings do
if id > 2 then -- "> 1" if your system won't have admins
characters = characters .. setting.char
end
end

-- Now make a loop with all of them at once
for char, value in string.gmatch(roomAttributes, "([" .. characters .. "])([^" .. characters .. "]+)") do
-- We use a numeric table because we use roomSettings[1] and [2] out of the loop.
for id, setting in next, roomSettings do
if setting.char == char then
-- %S+ means that it'll be a value without whitespaces.
-- i.e.: "va lue" will be "va"
setting.execute(string.gmatch(value, "%S+"))

-- we break the loop 2 because it found the desired character.
break
end
end
end
end

Final result with admins and other characters

Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
local setRoomSettings = function()
if roomAttributes then
for playerName in string.gmatch(roomAttributes, roomSettings[2].char .. "(%+?[a-zA-Z0-9_]+#%d%d%d%d)") do
roomSettings[2].execute(playerName)
end

roomSettings[1].execute(string.match(roomAttributes, roomSettings[1].char .. "([%a_]+)"))

if #roomSettings > 2 then
local characters = ""
for id, setting in next, roomSettings do
if id > 2 then
characters = characters .. setting.char
end
end

for char, value in string.gmatch(roomAttributes, "([" .. characters .. "])([^" .. characters .. "]+)") do
for id, setting in next, roomSettings do
if setting.char == char then
setting.execute(string.gmatch(value, "%S+"))

break
end
end
end
end
end
end



Now you can make your modules.

Example: https://github.com/Lautenschlager-id/Grounds/tree/master/src/mode



Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
-- Use functions in the format NAME = function(parameters)

-- Firstly, make the default module (initial module._NAME)
mode.first = {
time = 0,

printf = function(where, who, message)
print(string.format("[#%s] [%s] %s", where, who, message))
end,
-- In all the modules, make two functions (mandatory): init / reset

reset = function()
-- Here you reset all the local info (related to the specific mode)
-- as, reseting variables, player cash, time, etc...
-- It's the _eventOnModeChange_ of the module

mode.first.time = 10000
end,

init = function()
-- Here you put all the initial local informations (related to the module only)
-- as the disable functions (AutoScore, AutoShaman, etc...)

tfm.exec.disableAutoShaman()

-- Use mode.first.FUNCTION to access a function in the table.
-- Putting mode.first in a variable won't improve the performance.
mode.first.printf("chat", "game", "this sheeet started")

mode.first.time = 10000
end,

-- Now you can create the official events
eventLoop = function(time)
mode.first.time = time
end,
eventNewGame = function()
mode.first.printf("tribe", "game", "yaaaay come to play guys")
end
}

-- Make how many modules you want.
-- Take care, the bigger your code is, the more time it'll take to load.

Be patient, we are close! Now one of the most important and fundamental parts of the system: the initializer function.

Example: https://github.com/Lautenschlager-id/Grounds/blob/master/src/Initialize.lua



Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
-- We'll make a table with the names of all the official events
local eventNames = {"eventLoop", "eventNewGame", "eventPlayerDied", "eventPlayerGetCheese", "eventPlayerVampire", "eventPlayerWon", "eventPlayerLeft", "eventEmotePlayed", "eventKeyboard", "eventMouse", "eventPopupAnswer", "eventTextAreaCallback", "eventChatCommand", "eventChatMessage", "eventSummoningStart", "eventSummoningEnd", "eventSummoningCancel", "eventNewPlayer", "eventPlayerRespawn", "eventColorPicked"}

-- And another table that will maintain all the events of the current module.
local events = {}

-- We'll make an empty function for the nonexistent events
-- As default, we'll call it foo
foo = function() end

initialize = function(refresh)
-- We refresh the event table every time the function is called
events = {}

-- Now we'll make a loop will all the functions
for _, event in next, eventNames do

-- Puts in the event table the respective function that is in the module.
-- as events["eventLoop"] = mode.first.eventLoop
-- If it is nonexistent, the associated function will be _foo_, that doesn't do anything.
events[event] = mode[module._NAME][event] or foo
end

-- If _refresh_ is true, then we'll refresh the module
if refresh then
if mode[module._NAME].reset then
mode[module._NAME].reset()
end
end

-- Finally, we initialize the module
mode[module._NAME].init()

-- For good practices, we loop the eventNewPlayer for all the players
table.foreach(tfm.get.room.playerList, eventNewPlayer)
end

--- Now the 3 most important steps:
-- Create all the official events
for _, event in next, eventNames do
_G[event] = events[event]
end

-- Run the room settings
setRoomSettings()

-- Initialize our module for the first time
initialize(false) -- not refreshing

3

To create events that are triggered in every module, you can make another small system.

See:

Code Lua

1
2
3
4
5
6
7
8
9
10
11
12
13
local officialEvents = {}

-- A eventChatCommand that works in every module
officialEvents.eventChatCommand = function(name, command)
if command == "modules" then
local modules = ""
for moduleName in next, mode do
modules = modules .. moduleName .. "\n"
end

tfm.exec.chatMessage("The modules in this room are:\n" .. modules, name)
end
end

Then, just edit the official events initialization (above the setRoomSettings()), like that:
Code Lua

1
2
3
4
5
6
7
8
9
for _, event in next, eventNames do
_G[event] = function(...)
if officialEvents[event] then
officialEvents[event](...)
end

events[event](...)
end
end


Tada! It's done!

http://img.atelier801.com/eda4f383.gif



Script without comments


Dernière modification le 1557407940000
Massi
« Consul »
1527706320000
    • Massi#0095
    • Profil
    • Derniers messages
    • Tribu
#2
  1
Great tutorial, thank you!
  • Forums
  • /
  • Transformice
  • /
  • Modules
  • /
  • [Tutorial] Submodes
© Atelier801 2018

Equipe Conditions Générales d'Utilisation Politique de Confidentialité Contact

Version 1.27