Pensando sobre o Skript

Pessoas que me conhecem faz algum tempinho sabem que eu usava Skript para quase tudo no meu servidor, realmente tentava passar do limite do Minecraft para conseguir criar um servidor ótimo e diferente, que ninguém jamais viu antes... Infelizmente isto fez que eu passasse do limite que o Skript poderia oferecer, fazendo que ele se "suicidasse" (ou, pelo ou menos o que eu achava na época que isto aconteceu).

Isto me "forçou" a aprender Java para refazer todos os meus scripts em Java, tirei todos os meus scripts do ar com o motivo de "para mim o Skript estava lagando demais o servidor, e eu não quero que ninguém use o Skript (ou pelo ou menos os meus scripts) para que não aconteça o mesmo problema com você" que depois de um tempo foi resumido para apenas "Skript é um lixo" que aí fez as pessoas começarem a achar que eu só estava falando que Skript é um lixo porque eu aprendi Java...

Neste post eu resolvi falar a minha opinião sobre Skript, já que durante todos esses anos a minha opinião mudou e, infelizmente, muitas pessoas começaram a pegar o que eu tinha falado anos atrás e começou a distorcer o que eu tinha falado, vou desmentir algumas coisas que eu tinha falado no passado que eu descobri que eu mesmo estava errado e tentar, por uma vez por todas, acabar com a onda de "Skripter nem é gente" (mesmo que eu nem use mais Skript).

Porque eu comecei a mexer com Skript?

Lá em 2013 no meu servidor de "survival with real life friends", eu estava mexendo na pesquisa do Bukkit vendo os novos plugins que estavam sendo enviados e atualizados para que eu pudesse colocar alguns plugins no meu servidor para eu e os meus amigos se divertirem, um deles era esse tal de Skript, que, na época, estava na versão 2.0.0, baixei e resolvi brincar um pouco, afinal, seria engraçado fazer algumas coisas "diferentes" no servidor.

command /mrpowergamerbr:
        trigger:
                message "<light green>---MrPowerGamerBR Server---"
                message "<light red>Server de Minecraft 1.6.2"
                message "<light red>Feito por MrPowerGamerBR <light blue>Hampsa"

command /arigil:
        trigger:
                message "<light red>Chinelo, Pisante, Guerreiro do Ceu"
                message "<light red>Administrador, Dono do Baconzitos, Primeiro a ter um sinalizador"
                message "<light red>Ele e o Arigil"

command /darber:
        trigger:
                message "<light red>Samba, Samba, Samba!"

command /zhenga:
        trigger:
                message "<light red>Sai dai, seu baiano!"

command /lpariahl:
        trigger:
                message "<light red>eae leo"

command /moneyupgrade:
        trigger:
                message "<light red>Bom Dia, Bom Dia, Bom Dia. Veja os fatos que sao noticias do brasil e do mundo, agora, no Jornal do Lumen"

command /spawn:
        trigger:
                execute console command "/tppos %player% -182 64 175"

Comandos simples, coisas mais para a gente rir e se divertir mesmo, e, é claro, um comando de /spawn para facilitar a nossa vida quando a gente estava explorando o mundo survival e nós precisavamos voltar para o spawn rapidamente.

Tempos passaram e todo mundo perdeu interesse com Minecraft (eu culpo a nossa viagem de formatura do 9º ano, já que pelo visto depois da viagem todo mundo virou "adulto" e não pode mais jogar Minecraft... só para depois de anos no ensino médio ficarem "kk eae leo bora jogar 🅱inecraft?" 🙄)... exceto eu, é claro.

Devido a alguns destinos da vida, eu conheci uma pessoa chamada Doge_, já que nós estavamos sem nada para fazer (e tinhamos acabado de sair da Staff de um cara que resetava o servidor e trocava o estilo do servidor todo o dia) nós resolvemos criar um servidor chamado "DogePower" no começo de 2014.

Na época, a última versão do Skript era a 2.0.2, e eu resolvi usar BASTANTE ela, primeiro para coisas simples, como "coloque a sua conta do Skype para que todos possam saber qual é o seu Skype para poder conversar!", automizar uma Mina Recheada que apenas bastava eu digitar /minarecheada e ela automaticamente era criada e outras coisinhas especiais, mas nada de muito grande...

Chega 1/4 de 2014 e um dos donos resolveu trocar o nome do servidor para "SparkPower"... vamos dizer que depois desta época eu realmente comecei a forçar o limite do Skript, com VÁRIAS coisas, como sistema de chat, sistema de casamento, sistema de clans e entre outras coisas...

Só para ter uma ideia, o meu sistema de chat tinha suporte a hover (ou seja, passar o mouse por cima do chat e aparecia algo), click events e várias outras coisas, tudo feito usando Skript (e /tellraw)

on load:
    add "%script%" to {SparkPlugins.Lista::*}
    
on unload:
    remove "%script%" from {SparkPlugins.Lista::*}

on chat:
    cancel event
    if {SparkJail.Preso.%player%} is true:
        send "&cDesculpe, mas presos não falam ;)"
        stop
    message contains "\!":
        set {_message} to message
        replace all "\! " and "\!" with "" in {_message}
        if {SparkClans3.Player.%player%.Clan} is not set:
            send "&cVocê não tem nenhum Clan para mandar uma mensagem!"
            stop
        if {_message} is "":
            send "&cVocê não pode mandar uma mensagem vazia!"
            stop
        loop all players:
            if {SparkClans3.Player.%loop-player%.Clan} is {SparkClans3.Player.%player%.Clan}:
                if {SparkClans3.Clan.%{SparkClans3.Player.%player%.Clan}%.Leader} is player's name:
                    send "&b%player%&6: &7%{_message}%" to loop-player
                    stop
                else if player has permission "authme.vip":
                    send "&e%player%&6: &7%{_message}%" to loop-player
                    stop
                else:
                    send "&3%player%&6: &7%{_message}%" to loop-player
                    stop
    if {SparkMarry.%player%.MarryChat} is true:
        cancel event
        send "%{SparkMarry.%player%.Marry}%"
        set {_player} to "%{SparkMarry.%player%.Marry}%" parsed as offline player
        send "&8[&a%player%&8] &b%message%"
        send "&8[&a%player%&8] &b%message%" to {_player}
        stop
    add 1 to {SparkChat.%player%.ChatMessages}
    if {SparkClans3.Player.%player%.Clan} is set:
        set {_clantag} to "%colored {SparkClans3.Clan.%{SparkClans3.Player.%player%.Clan}%.ColoredPrefix}%&8."
        set {_clanformat} to "%colored {SparkClans3.Clan.%{SparkClans3.Player.%player%.Clan}%.ColoredPrefix}%&8/&f%colored {SparkClans3.Clan.%{SparkClans3.Player.%player%.Clan}%.Name}%"
    else:
        set {_clantag} to ""
        set {_clanformat} to "&cNenhum ;-;"
    if {SparkMarry.%player%.Marry} is set:
        set {_marrytag} to "&4♥ "
        set {_marryformat} to "&c%{SparkMarry.%player%.Marry}%"
    else:
        set {_marrytag} to ""
        set {_marryformat} to "&cNinguém ;-;"
    if {richest.player} is player's name:
        set {_ostenta} to "&8[&20st3nt4&8] "
    else:
        set {_ostenta} to ""
    if {SparkCorrida.Winner} is player's name:
        set {_corridatag} to "&8[&bCorredor&8] "
    else:
        set {_corridatag} to ""
    if {SparkCorrida.OldWinner} is player's name:
        set {_corridatag} to "&8[&bExCorredor&8] "
    if {SparkCorrida.SuperWinner} is player's name:
        set {_corridatag} to "&8[&bCorredor&6+&8] "
    set {_timeonline} to difference between now and {SparkChat.%player%.LoginSession}
    player has permission "SparkChat.Colors":
        set {_message} to colored message
    else:
        set {_message} to message
    replace all "'" with "`" in {_message}
    loop all players:
        message contains "%loop-player%"
        replace all "%loop-player%" with "&a%loop-player's display name%&f" in {_message}
    loop all players:
        make console execute command "tellraw %loop-player% {'text':'', 'extra':[{'text':'%colored player's prefix%%colored {_corridatag}%%colored {_ostenta}%%colored {_marrytag}%%colored {_clantag}%&f%player's display name%%colored player's suffix%', 'color':'green', 'hoverEvent':{'action':'show_item', 'value':'{id:1,Damage:0,Count:1,tag:{display:{Name:\&6%player%\, Lore:[\&bClan&6; %{_clanformat}%\, \&aCasado com&6; %{_marryformat}%\, \&aTempo Online&6; %{_timeonline}%\, \&aMensagens Enviadas&6; %{SparkChat.%player%.ChatMessages}%\, \&3Clique aqui para mandar uma msg para &6%player's display name%&3.\]}}}'}, clickEvent:{action:suggest_command,value:'%player% '}}, {'text':'&6: &f%{_message}%'}]}"
        
command /crafttest:
    trigger:
        send "%player's health%"
        if player's health is 10:
            send "&4♥♥♥♥♥♥♥♥♥♥"
        
on join:
	set {SparkChat.%player%.LoginSession} to now

Infelizmente o servidor fechou em Agosto de 2014 por motivos de "apenas um dono recebia o dinheiro do servidor e eu não recebia nada, mesmo que eu fazia quase tudo no servidor" (e também porque eu estava indo mal na escola e eu achava que sair da staff de lá ia resolver alguma coisa... no final do bimestre fiquei de recuperação do mesmo jeito 😝)...

Após o servidor ter fechado eu comecei a "espalhar" os meus scripts pela internet, talvez algumas pessoas lembrem do PowerFight, PowerQuiz, PowerAntiRoubo e vários outros scripts meus, a comunidade brasileira de Skript começou a crescer devido a minha causa, que legal!

(Mas vou ser sincero, não fui o primeiro brasileiro a mandar um script, no AtomGamers já tinha um script postado no fórum bem antes de eu ter postado os meus, mas pelo visto ninguém deu bola...)

Em novembro de 2014 eu comecei a aprender Java, criei alguns plugins (sendo um deles o PowerMoedas, um plugin que eu tinha feito para mostrar que alguns plugins pagos eram mais caros que deveriam tosse TintaMoedas tosse)

Tempos passaram e em Novembro de 2014 eu ganhei uma hospedagem de graça pelo alexhackers e criei o SparklyPower, naquela época enquanto eu já sabia sobre Java eu preferia MUITO fazer scripts, afinal, eu sabia mais, era mais prático e mais simples, porque continuar a mexer em Java então?

E assim continuou o servidor, peguei a maioria dos meus scripts velhos, coloquei no servidor com alguns tweaks e fiz vários outros, incrível, né? Meu próprio servidor estava crescendo! E ainda por cima, o meu servidor era quase uns 75% feito usando apenas Skript! Pessoas falavam para as outras "Tá vendo esse servidor? Ele é ótimo, né? Sabia que ele é feito apenas com Skript?" e as pessoas ficavam surpresas, como pode criar um servidor tão bom apenas com Skript?

...até que um dia o servidor começou a ter quedas grandes de TPS, fui analisar o Timings e quem estava travando era o Skript... mas... como?

Infelizmente aí que começaram os meus problemas com o Skript... olhar o Timings não ajudava muita coisa, já que a única coisa que falava que o Skript estava executando uma task com nome de Delay$1... mas o que seria delay? O que seria delay? Seria wait X seconds ou seria o every X seconds? Como posso resolver este problema? Ninguém sabe... Parece algo inimaginável isto, eu procurava no Google pessoas que tem o mesmo problema mas nada, como eu posso descobrir qual script que está causando o problema?

Problemas, problemas, problemas

Que aí que começou os meus problemas com o Skript, como irei resolver os problemas agora?

Comecei a refazer todos os meus scripts em Java... beeeeeeem lentamente, já que meu conhecimento em Java era quase nulo, mas eu estava indo do meu jeito, substituindo lentamente cada script por uma versão dele em Java...

Eu nunca consegui "descobrir" o problema que estava causando o lag, será que era a database de variáveis (afinal, ela já era meio grande... uns 150MB na época), será que era algo que eu mesmo tinha feito ou será que era culpa minha?

Quando eu comecei a escrever este post, eu resolvi tentar pelo ou menos descobrir porque estava lagando, o que será este misterioso Delay$1? Muitas pessoas falaram para mim "É que você é idiota e fica usando um monte de wait X seconds!!!")

E eu descobri... e impressionantemente tem nada a ver com o wait X seconds, quer dizer, tem a ver, mas a culpa não é do pobre wait X seconds... vamos analisar a classe de Delay do Skript https://github.com/Njol/Skript/blob/master/src/main/java/ch/njol/skript/effects/Delay.java

Quando o wait X seconds é executado, ele faz isto

	@Override
	@Nullable
	protected TriggerItem walk(final Event e) {
		debug(e, true);
		final long start = Skript.debug() ? System.nanoTime() : 0;
		final TriggerItem next = getNext();
		if (next != null) {
			delayed.add(e);
			final Timespan d = duration.getSingle(e);
			if (d == null)
				return null;
			Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() {
				@Override
				public void run() {
					if (Skript.debug())
						Skript.info(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1000000000. + "s");
					TriggerItem.walk(next, e);
				}
			}, d.getTicks());
		}
		return null;
}

No Java, quando uma classe anônima é compilada, ela é chamada de Delay$n+1, ou seja, se você tiver três classes anônimas em uma classe chamada HelloWorld, elas irão ser chamadas de HelloWorld$1, HelloWorld$2 e HelloWorld$3... Agora você deve estar se perguntando... "mas para que essa explicação de classes anônimas???"... pois bem.

Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() {
	@Override
	public void run() {
		if (Skript.debug())
			Skript.info(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1000000000. + "s");
		
		TriggerItem.walk(next, e);
	}
}, d.getTicks());

Ainda não viu a classe anônima? Deixa eu te dar uma ajudinha então...

Bukkit.getScheduler().scheduleSyncDelayedTask(Skript.getInstance(), new Runnable() {
	@Override
	public void run() { // INÍCIO DA CLASSE ANÔNIMA
		if (Skript.debug())
			Skript.info(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1000000000. + "s");
		
		TriggerItem.walk(next, e);
	} // FIM DA CLASSE ANÔNIMA
}, d.getTicks());

Agora vamos analisar a nossa classe anônima...

if (Skript.debug())
	Skript.info(getIndentation() + "... continuing after " + (System.nanoTime() - start) / 1000000000. + "s");
		
TriggerItem.walk(next, e);

Em pseudo-código, isto seria algo como

se o modo debug está ativado:
	enviar no console informações de debug
	
avançar para a próxima linha

Como o modo debug no meu servidor estava desativado, então só pode ser o avançar para a próxima linha/TriggerItem.walk(next, e);

Ou seja, todos os códigos após o wait vão aparecer como Delay$1 no seu Timings, sendo apenas possível descobrir o real problema usando um Java Profiler como o YourKit, o que é MUITO mais complicado para o usuário comum e, é claro, na época eu não sabia como usava profilers para descobrir problemas.

Você pode falar agora que "ah, mas isso que você fez então é culpa sua, seu idiota!!!", sim, foi culpa minha... mas será que você não poderia cometer o mesmo erro também? Vamos supor este código:

command /teste:
	trigger:
		send "Olá mundo!" to player
		wait 5 seconds
		# imagine que tenha mais de 100 linhas de código depois do wait

Sabe como vai aparecer no seu Timings? Delay$1!

Agora você deve estar se perguntando: "Tá, mas como aparece uma task desse mesmo jeito no Timings v2?"

Do mesmo jeito... antes de você começar a falar "aaa então plugin tem os mesmos problemas né safado!", se você está analisando um plugin, você pelo ou menos consegue saber qual task que está provocando o tal problema, não é igual no Skript que, se você ver um Delay$1, você não consegue saber qual dos delays que está causando isto, pode ser qualquer um.

Na fork do bensku (algo que não existia na época que eu tinha este problema) para tentar ajudar a resolver o problema do Delay$1, agora cada wait possui uma entry diferente no Timings v2, ou seja, em vez de aparecer apenas Delay$1 ele irá aparecer até o nome do efeito que causou o Delay$1 ser criado, legal, né?

Outros motivos

Existem também outros motivos de eu ter parado de usar o Skript, Skript poderia ser lagado, mas ele era bem prático... mas mesmo assim ele possui alguns problemas.

Cadê minhas funções?

Enquanto isto já tinha sido implementado pelo njol no Skript 2.2, o Skript 2.2 nunca foi lançado e as funções foram apenas adicionadas em forks do Skript.

Vamos supor este código em Kotlin

override fun onBlockPlace(e: BlockPlaceEvent) {
    fazerCoisas(e.player)
}

fun fazerCoisas(player: Player) {
    player.sendMessage("§aEstou fazendo coisas!")
    player.getInventory().clear()
    player.getInventory().addItem(Material.DIAMOND_SWORD)
    player.sendMessage("§aEu fiz coisas!")
}

Simples, não? Agora vamos ver em Skript...

on block place:
    make console execute command "fazercoisas %player%" # wait, what?
    
command /fazercoisas <player>: # ???
    executed by: console
    trigger:
        send "&aEstou fazendo coisas!" to player
        clear player's inventory
        give 1 diamond sword to player
        send "&aEu fiz coisas!"

Comandos são suas funções agora... wait what?

É, se você quisesse reaproveitar o seu código, você teria que criar um comando... que é claro, ruim. Afinal, você está criando um comando apenas para reaproveitar código, que, é claro, será um comando válido no seu servidor, será um comando registrado na command map... é, não é uma boa ideia fazer isto.

Lembrando que desde o Skript 2.2 existem funções (yay!), ou seja, em vez de fazer essa gigante gambiarra de "comandos como funções", você pode fazer isto:

function myFunction(i: number) :: number:
    set {_i} to {_i} + 1
    return {_i}

Finalmente algo decente! Infelizmente para mim isto não existia na época que eu usava Skript então nunca tive a possibilidade de usar... (na época o Skript 2.2 era muito instável e não estava funcionando corretamente no meu servidor)

Limitações

Skript é bastante limitado, pelo ou menos ele sem nenhum addon.

Isto já é algo que não tem outras soluções sem ser usar addons, que ai tem outro problema ao usar addons...

Depender de várias outras coisas

Vamos supor que você tenha um servidor na 1.7.10 e você está usando o Skript, aí lançou o Minecraft 1.8.8 e você quer atualizar o seu servidor para o Minecraft 1.8.8 já que ficar em versões obsoletas não é legal...

Ai na hora que você atualiza você descobre que o Skript não funciona direito na 1.8.8 e você precisa esperar uma atualização dele... e a situação só piora se você está usando addons, já que você precisa esperar o próprio Skript atualizar para depois esperarem atualizar os seus addons... quanta enrolação.

Isto já aconteceu antes com quem usava o SkQuery na 2.0.2 e queria atualizar o Skript para a 2.1.2, o SkQuery demorou MUITO para atualizar para a 2.1.2 e, é claro, enquanto ele não atualizava...

  • Ou você ficava rodando uma versão desatualizada do Skript
  • Removia todos os scripts que usavam o SkQuery até o Skript atualizar

Performance

Sim, Skript tem uma performance pior que Java. Skript é uma linguagem interpretada, já Java é uma linguagem compilada.

Enquanto o seu servidor está rodando, o nosso amigo JIT está otimizando os códigos mais importantes do seu servidor para garantir uma performance melhor, ele consegue entender muito bem bytecode e consegue fazer muitas otimizações excelentes no seu código para deixar ele mais otimizado... Mas quando é um script ele não consegue entender muito bem o que ele pode otimizar, já que na realidade um script é apenas um monte de listas explicando o que deverá ser executado em seguida.

Isto quer dizer que o Skript é uma tartaruga comparado com um plugin? Não, isto não é verdade.

Skript pode ser mais difícil de ser otimizado mas não quer dizer que ele é uma tartaruga, o JIT ainda consegue otimizar os triggers individualmente. A única coisa que ele teria uma performance menor seria se o script fosse grande (e se usasse vários for)

Eu voltaria a usar Skript?

Provavelmente não, eu já estou confortável usando Kotlin (ou Java) + JRebel então não penso em voltar a usar Skript.

Isto não quer dizer que você também não deva usar Skript, sim, é legal saber Java e conseguir customizar o seu servidor mais a fundo sabendo programar plugins, mas se Skript serve para você, então apenas fique com ele e seja feliz!

Ou seja: Sim, Skripter é gente e, para comemorar isto, eu repostei os meus velhos scripts! (Mesmo que eu não use mais Skript e não use mais esses scripts, talvez eles ainda sejam úteis para algumas pessoas e, é claro, eles são parte da história do Skript no Brasil)