Ir para conteúdo

Rusherzin

Membro
  • Registro em

  • Última visita

Histórico de Curtidas

  1. Você Tentou
    Rusherzin recebeu reputação de Gabrielxxxxx em Setando -1 na storage quando desloga e dando erro na distro   
    Boa noite @Gabrielxxxxx, essa função advanceStage é chamada algumas vezes num intervalo de tempo e ela tenta setar a storage no player, se ele estiver deslogado vai dar esse erro, pois o servidor não encontra o player. É só adicionar um check nela, assim:

     
    -- Função para avançar para o próximo estágio após o término do atual local function advanceStage() -- checa se o player/creature existe if not isCreature(cid) then return false end if stage < #waittimes then stage = stage + 1 setPlayerStorageValue(cid, storage, stage) -- Programar o próximo avanço de estágio addEvent(advanceStage, waittimes[stage] * 1000) else -- Resetar a storage quando todos os estágios terminarem setPlayerStorageValue(cid, storage, -1) end end  
  2. Curtir
    Rusherzin deu reputação a marcoshps11 em Configurando Docker no windows, adeus VM   
    Neste tutorial, irei ensinar configurar o Docker no windows e rodar seu otserver
     
     
    - Diferença entre Docker x Máquina virtual:
      Docker e Máquinas Virtuais (VMs) ambas são fundamentais usadas para isolar recursos em um sistema de computação, mas funcionam de maneiras muito diferentes.
     
      VMs são ambientes de computação completos, incluindo um sistema operacional (SO) completo, que simulam o hardware de um computador físico. Isso significa que cada VM é um sistema autônomo e independente.   Já o Docker, usa a tecnologia de contêineres. Os contêineres compartilham o mesmo sistema operacional do host, mas mantêm seu próprio espaço de processo, arquivos e rede. Isso significa que eles são muito mais leves do que as VMs, pois não precisam de um SO completo, e podem ser iniciados e parados muito mais rapidamente.
    - Vantagens em utilizar o Docker
     O Docker oferece várias vantagens significativas:
    Eficiência de recursos: Como os contêineres Docker compartilham o SO do host, eles são mais leves e usam menos recursos do que as VMs. Portabilidade: O Docker permite que você "construa uma vez, execute em qualquer lugar". Uma vez criado um contêiner Docker, ele pode ser executado em qualquer sistema que suporte o Docker, independentemente das configurações subjacentes. Isolamento: Cada contêiner Docker é isolado do resto do sistema, o que ajuda a evitar conflitos entre diferentes aplicações e versões de software. Integração e entrega contínua: O Docker se integra bem com ferramentas de CI/CD (Integração Contínua / Entrega Contínua), permitindo a automatização de testes e implantação de aplicações. Escalabilidade e Orquestração: O Docker facilita a escalabilidade de aplicações, permitindo que múltiplos contêineres sejam implantados facilmente. Além disso, com ferramentas como Docker Swarm e Kubernetes, a orquestração de contêineres se torna eficiente e gerenciável  
     
    Colocando a mão na massa
     
    Este tutorial será dividido nas seguintes etapas:
    Preparação do Windows Instalando o Docker Instalando imagens mariadb e phpmyadmin Realizando a build da imagem TFS  
    1º PASSO:  Preparando o Windows com o subsistema Linux.
     
    AVISO: Essa Etapa será necessária somente caso ainda não possua o subsistema Linux ativado em seu windows
     
    Os comandos a seguir, serão todos utilizando o POWERSHELL do windows, na barra de pesquisa digite POWERSHELL e execute como administrador.
     

     
    Com o POWERSHELL aberto, execute os seguintes comandos:
     
    Habilitando subsistema Linux:
      dism.exe /online /enable-feature /featurename:Microsoft-Windows-Subsystem-Linux /all /norestart  
     
    Habilitando recurso de máquina virtual: dism.exe /online /enable-feature /featurename:VirtualMachinePlatform /all /norestart  
    Baixe e instale Kernel do Linux:  
    https://wslstorestorage.blob.core.windows.net/wslblob/wsl_update_x64.msi
     
    Após essa etapa, será necessário reiniciar o computador para atualização, após reiniciar, abra novamente o PowerShell
     
    Definindo o WSL 2 como padrão: wsl --set-default-version 2  
     
    Após esses passos, basta entrar na Microsoft Store e instalar o sistema Ubuntu:


     
    Após a instalação, abra o Ubuntu, como será a primeira vez, irá solicitar para que digite um nome de usuário e uma senha.

     
     
     
    Etapa 1 concluída.
     
    Fonte: https://learn.microsoft.com/pt-br/windows/wsl/install-manual
     
    2º PASSO:  Instalando Docker.
     
    este é um passo simples, apenas entre no site oficial do Docker, baixe e instale o Docker Desktop

    https://www.docker.com/
     

     
     
    Etapa 2 concluída.
     
     
    3º PASSO:  Instalando imagens MariaDB e phpMyAdmin
     
     Assim como no passo 1, execute novamente o PowerShell como administrador e siga os seguintes passos:
     
    Primeiro vamos baixar as imagens necessárias, execute os seguintes comandos: docker pull mariadb  
    docker pull phpmyadmin/phpmyadmin  
     
    Agora iremos criar uma "Rede" para que o docker se comunique entre os containers que iremos utilizar. Execute o seguinte comando:  
    docker network create mynetwork  
      mynetwork é o nome que defini neste caso, você pode utilizar o nome que achar melhor, porém lembre-se desse nome que iremos utilizar nos próximos passos.
     
    Agora iremos iniciar essas imagens, utilizando os seguintes comandos:  
    docker run --name mariadb --network=mynetwork -e MYSQL_ROOT_PASSWORD=your_password -d mariadb  
    your_password = altere pela senha que deseja utilizar no mysql
    mynetwork = é o nome que criamos no passo anterior
     
    docker run --name myadmin --network=mynetwork --link mariadb:db -d -p 8080:80 phpmyadmin/phpmyadmin  
    Lembrando novamente, mynetwork é o nome criado anteriormente
     
    Após esses passos, será possível ver seu container rodando no Docker Desktop:

     
    Etapa 3 concluída.
     
    4º PASSO:  Gerando Imagem do TFS e configurações necessárias:

     
    Baixe a TFS, nesse exemplo será o repositório oficial https://github.com/otland/forgottenserver Extraia os arquivos após baixar com o container mariadb e phpmyadmin rodando, acesse http://localhost:8080/  com o login root, e a senha que criou no passo 3, crie uma nova base de dados e importe o arquivo schema.sql do servidor edite o arquivo config.lua.dist
     -- MySQL
    mysqlHost = "mariadb"
    mysqlUser = "root"
    mysqlPass = "SUA SENHA CRIADO NO PASSO 3"
    mysqlDatabase = "NOME DA DATABASE"
    mysqlPort = 3306
    mysqlSock = ""

    mariadb é o nome do container que estamos rodando o mysql  
     
    ** Caso queira utilizar todo potencial do seu computador na hora de buildar a TFS, no arquivo Dockerfile, edite a linha 18:
    RUN cmake --preset default && cmake --build --config RelWithDebInfo --preset default para
     
    RUN cmake --preset default && cmake --build --config RelWithDebInfo --preset default -- -j 16  
    onde 16 é o numero de processadores lógicos que deseja ser utilizados, não é necessário utilizar 100% porém quanto mais melhor, porém caso utilize mais do que está disponível poderá travar,  então utilize a quantidade mostrada pra menos.
    no gerenciador de tarefas aba desempenho, é possível visualizar a quantidade de núcleos e processadores lógicos do seu CPU


     
    Após isso, entre no Power Shell e execute o comando:  
    docker build -t server .  
     onde "server" é o nome da imagem que será gerada
     
    Após isso, é só iniciar o servidor com o seguinte comando  
    docker run --name server --network=mynetwork -p 7171:7171 -p 7172:7172 -d server  
    lembrando que mynetwork é o nome que geramos no passo 3, e o ultimo parametro é nome da imagem que geramos anteriormente.
     
    Você poderá ver seu sevidor ronando no Docker Desktop:
     

     
    caso queira parar algum servidor ou iniciar novamente, é só clicar no botão de ação:
     

     
    Caso precise fazer alguma alteração no server, basta utilizar os comandos de build e run citados anteriormente.
     
     
     
     
    Lembrando que isso é um tutorial básico para rodar o servidor na máquina local, qualquer dúvida ou sugestão, deixe comentário.
     
  3. Gostei
    Rusherzin recebeu reputação de PokeKingdoms em (Resolvido)Como Remover Protection Zone RME   
    Sim, selecione a opção que adiciona a PZone e segure ctrl ao mesmo tempo e passe nos tiles onde tem PZone.
  4. Gostei
    Rusherzin recebeu reputação de Ghaz em getTileInfo   
    @Jeanzeraa Adiciona essa função abaixo na sua Lib. Use-a com os seguintes parâmetros:
    pos = posição onde vai checar
    creature = true, se é para retornar falso caso tenha alguma criatura na posição
    proj = se true, checa se o item bloqueia pathfind, se false, checa se o item bloqueia projéteis
    pz = se true, retorna falso cajo a posição seja uma protection zone
     
    function isWalkable(pos, creature, proj, pz)-- by Nord if getTileThingByPos({x = pos.x, y = pos.y, z = pos.z, stackpos = 0}).itemid == 0 then return false end if getTopCreature(pos).uid > 0 and creature then return false end if getTileInfo(pos).protection and pz then return false, true end local n = not proj and 3 or 2 for i = 0, 255 do pos.stackpos = i local tile = getTileThingByPos(pos) if tile.itemid ~= 0 and not isCreature(tile.uid) then if hasProperty(tile.uid, n) or hasProperty(tile.uid, 7) then return false end end end return true end  
  5. Gostei
    Rusherzin recebeu reputação de ILex WilL em Ajuda com Character.php   
    @Cleiton Felipi, não testei, mas deve ser assim:
     
     
  6. Obrigado
    Rusherzin recebeu reputação de vasco4 em (Resolvido)Ajuda com tabela sql players_items   
    Abre teu SQLite pressione Alt + E e cole isso:
    CREATE TABLE IF NOT EXISTS `player_items` ( `player_id` int(11) NOT NULL DEFAULT '0', `pid` int(11) NOT NULL DEFAULT '0', `sid` int(11) NOT NULL DEFAULT '0', `itemtype` int(11) NOT NULL DEFAULT '0', `count` int(11) NOT NULL DEFAULT '0', `attributes` blob NOT NULL ); E pressione F9

     
  7. Gostei
    Rusherzin recebeu reputação de wallaceg15 em (Resolvido)Ajuda com tabela sql players_items   
    Abre teu SQLite pressione Alt + E e cole isso:
    CREATE TABLE IF NOT EXISTS `player_items` ( `player_id` int(11) NOT NULL DEFAULT '0', `pid` int(11) NOT NULL DEFAULT '0', `sid` int(11) NOT NULL DEFAULT '0', `itemtype` int(11) NOT NULL DEFAULT '0', `count` int(11) NOT NULL DEFAULT '0', `attributes` blob NOT NULL ); E pressione F9

     
  8. Gostei
    Rusherzin recebeu reputação de Vodkart em (Resolvido)Como Remover Protection Zone RME   
    Sim, selecione a opção que adiciona a PZone e segure ctrl ao mesmo tempo e passe nos tiles onde tem PZone.
  9. Gostei
    Rusherzin recebeu reputação de Ackerzin em Compactar script!   
    Na verdade o teu script original pega uma posição randômica entre as 3 da lista, por isso perguntei. Mas tu pode fazer assim:
     
    local positions = { [999] = { {x = 1000, y = 1000, z = 7} }, [1000] = { {x = 1000, y = 999, z = 7} }, [1001] = { {x = 1001, y = 1001, z = 7} } } function onStepIn(cid, item, position, lastPosition, fromPosition, toPosition, actor) local pos = positions[item.actionid] if not pos then return true end doTeleportThing(cid, pos[math.random(1, #pos)]) return true end  
  10. Gostei
    Rusherzin recebeu reputação de XGaduX em Compactar script!   
    Na verdade o teu script original pega uma posição randômica entre as 3 da lista, por isso perguntei. Mas tu pode fazer assim:
     
    local positions = { [999] = { {x = 1000, y = 1000, z = 7} }, [1000] = { {x = 1000, y = 999, z = 7} }, [1001] = { {x = 1001, y = 1001, z = 7} } } function onStepIn(cid, item, position, lastPosition, fromPosition, toPosition, actor) local pos = positions[item.actionid] if not pos then return true end doTeleportThing(cid, pos[math.random(1, #pos)]) return true end  
  11. Curtir
    Rusherzin recebeu reputação de maiconmnt em Erro Creature script Gonka   
    Tu tens duas opções, ou cria um NPC com o nome [Server Information], ou tu remove a seguinte linha:
     
    doCreatureSay(getCreatureByName("[Server Information]"), "[DOUBLE POINTS]: Receba pontos em dobro para qualquer valores! Promoção por tempo LIMITADO!.", TALKTYPE_PRIVATE, false, cid)  
  12. Gostei
    Rusherzin recebeu reputação de premii em [ERRO] Shop Globalevents   
    Tentou com db.executeQuery? Se não der certo, tenta botar db.getResult, não é o mais indicado a se fazer, mas acredito que vá editar o banco de dados anyway.
  13. Obrigado
    Rusherzin recebeu reputação de LeoTK em (Resolvido)Tile que só pode passa algumas vocaçao.   
    Não testei..
     
    local vocations = {1,2,3,4,5,6,7,8} -- Vocs local entermsg = "Welcome" -- Msg se puder passar local failmsg = "Sorry, but you aren\'t the right vocation" -- Msg se não tiver voc certa function onStepIn(cid, item, position, lastPosition, fromPosition, toPosition, actor) if isPlayer(cid) then if isInArray(vocations,getPlayerVocation(cid)) then doPlayerSendTextMessage(cid,25,entermsg) doSendAnimatedText(getCreaturePosition(cid), "WELCOME!", TEXTCOLOR_RED) doSendMagicEffect(getCreaturePosition(cid), CONST_ME_FIREWORK_RED) else doTeleportThing(cid,fromPosition) doSendMagicEffect(getCreaturePosition(cid), CONST_ME_TELEPORT) doPlayerSendTextMessage(cid,25,failmsg) end return true end end movements.xml
     
    <movevent type="StepIn" actionid="xxx" event="script" value="voctile.lua"/>
  14. Gostei
    Rusherzin recebeu reputação de assisdjj em (Resolvido)Como Remover Protection Zone RME   
    Sim, selecione a opção que adiciona a PZone e segure ctrl ao mesmo tempo e passe nos tiles onde tem PZone.
  15. Curtir
    Rusherzin recebeu reputação de Cricket em [Resolvido] TILE POR STORAGE   
    Queria um script q só players com o storage 11138 conseguissem passar sobre ele!
    Já procurei em vários lugares e nada!!
    Por favor postem o script e a tag ..
  16. Curtir
    Rusherzin recebeu reputação de nyedsonnyel em (Resolvido)Como Remover Protection Zone RME   
    Sim, selecione a opção que adiciona a PZone e segure ctrl ao mesmo tempo e passe nos tiles onde tem PZone.
  17. Obrigado
    Rusherzin recebeu reputação de Bobby Psicotico em (Resolvido)Como Remover Protection Zone RME   
    Sim, selecione a opção que adiciona a PZone e segure ctrl ao mesmo tempo e passe nos tiles onde tem PZone.
  18. Gostei
    Rusherzin recebeu reputação de Cleiton Felipi em Ajuda com Character.php   
    @Cleiton Felipi, não testei, mas deve ser assim:
     
     
  19. Curtir
    Rusherzin recebeu reputação de ITALOx em Comando "Spy" Players   
    Olá pessoal do TK, venho aqui trazer um script que não fui eu quem o criou, somente coloquei-o em português (e daí?).. Achei em um fórum e no final do tópico estarei disponibilizando o nome do criador.
     
    Utilidade do Script:
     
    Bom, o script serve para você com um character com acesso de GOD (configurável) possa ver quais itens um certo player está utilizando no momento. PS: Inclusive os itens presentes dentro da backpack do Player.
     
    Como funciona:
     
    Você digita a talkaction /spy seguida do nome do player a ser "espionado".
    Por Exemplo: /spy Rusherzin
    /spy Tibia King
     
    Testado em:
     
    Versão 8.54
     
    Imagem:



     
    Agora vamos ao script:
    Vá em data/talkactions/scripts/ copie e cole um arquivo .lua qualquer, renomeie para spy e cole isso dentro:



     
    E adicione essa tag no talkactions.xml:



     
     
     
    Créditos:  Azi
  20. Curtir
    Rusherzin recebeu reputação de Ackerzin em [AJUDA] Update do servidor sem resetar a DB   
    Executa isso na tua db
     
    CREATE TABLE IF NOT EXISTS `players` ( `id` int(11) NOT NULL AUTO_INCREMENT, `name` varchar(255) NOT NULL, `world_id` tinyint(4) unsigned NOT NULL DEFAULT '0', `group_id` int(11) NOT NULL DEFAULT '1', `account_id` int(11) NOT NULL DEFAULT '0', `level` int(11) NOT NULL DEFAULT '1', `vocation` int(11) NOT NULL DEFAULT '0', `health` int(11) NOT NULL DEFAULT '150', `healthmax` int(11) NOT NULL DEFAULT '150', `experience` bigint(20) unsigned NOT NULL DEFAULT '0', `lookbody` int(11) NOT NULL DEFAULT '0', `lookfeet` int(11) NOT NULL DEFAULT '0', `lookhead` int(11) NOT NULL DEFAULT '0', `looklegs` int(11) NOT NULL DEFAULT '0', `looktype` int(11) NOT NULL DEFAULT '136', `lookaddons` int(11) NOT NULL DEFAULT '0', `lookmount` int(11) NOT NULL DEFAULT '0', `maglevel` int(11) NOT NULL DEFAULT '0', `mana` int(11) NOT NULL DEFAULT '0', `manamax` int(11) NOT NULL DEFAULT '0', `manaspent` bigint(20) unsigned NOT NULL DEFAULT '0', `soul` int(10) unsigned NOT NULL DEFAULT '0', `town_id` int(11) NOT NULL DEFAULT '0', `posx` int(11) NOT NULL DEFAULT '0', `posy` int(11) NOT NULL DEFAULT '0', `posz` int(11) NOT NULL DEFAULT '0', `conditions` blob NOT NULL, `cap` int(11) NOT NULL DEFAULT '0', `sex` int(11) NOT NULL DEFAULT '0', `lastlogin` bigint(20) unsigned NOT NULL DEFAULT '0', `lastip` int(10) unsigned NOT NULL DEFAULT '0', `save` tinyint(1) NOT NULL DEFAULT '1', `skull` tinyint(1) unsigned NOT NULL DEFAULT '0', `skulltime` int(11) NOT NULL DEFAULT '0', `rank_id` int(11) NOT NULL DEFAULT '0', `guildnick` varchar(255) NOT NULL DEFAULT '', `lastlogout` bigint(20) unsigned NOT NULL DEFAULT '0', `blessings` tinyint(2) NOT NULL DEFAULT '0', `pvp_blessing` tinyint(1) NOT NULL DEFAULT '0', `balance` bigint(20) unsigned NOT NULL DEFAULT '0', `stamina` bigint(20) unsigned NOT NULL DEFAULT '151200000' COMMENT 'stored in miliseconds', `direction` int(11) NOT NULL DEFAULT '2', `loss_experience` int(11) NOT NULL DEFAULT '100', `loss_mana` int(11) NOT NULL DEFAULT '100', `loss_skills` int(11) NOT NULL DEFAULT '100', `loss_containers` int(11) NOT NULL DEFAULT '100', `loss_items` int(11) NOT NULL DEFAULT '100', `premend` int(11) NOT NULL DEFAULT '0' COMMENT 'NOT IN USE BY THE SERVER', `online` tinyint(1) NOT NULL DEFAULT '0', `marriage` int(10) unsigned NOT NULL DEFAULT '0', `promotion` int(11) NOT NULL DEFAULT '0', `deleted` int(11) NOT NULL DEFAULT '0', `description` varchar(255) NOT NULL DEFAULT '', `ip` varchar(17) NOT NULL DEFAULT '0', `offlinetraining_time` smallint(5) unsigned NOT NULL DEFAULT '43200', `offlinetraining_skill` int(11) NOT NULL DEFAULT '-1', `cast` tinyint(4) NOT NULL DEFAULT '0', `castDescription` varchar(255) NOT NULL DEFAULT '', `castViewers` int(11) NOT NULL DEFAULT '0', `created` int(11) NOT NULL DEFAULT '0', `nick_verify` int(11) NOT NULL DEFAULT '0', `old_name` varchar(255) NOT NULL DEFAULT '', `hide_char` int(11) NOT NULL DEFAULT '0', `worldtransfer` int(11) NOT NULL DEFAULT '0', `comment` text NOT NULL, PRIMARY KEY (`id`), UNIQUE KEY `name` (`name`,`deleted`), KEY `account_id` (`account_id`), KEY `group_id` (`group_id`), KEY `online` (`online`), KEY `deleted` (`deleted`) ) ENGINE=InnoDB DEFAULT CHARSET=latin1 AUTO_INCREMENT=3237 ;
  21. Gostei
    Rusherzin recebeu reputação de Lincoln123123 em (Resolvido)Como Remover Protection Zone RME   
    Sim, selecione a opção que adiciona a PZone e segure ctrl ao mesmo tempo e passe nos tiles onde tem PZone.
  22. Gostei
    Rusherzin recebeu reputação de Andersen96 em (Resolvido)[Modern ACC] PHP Verificar mais de uma value   
    @Brunds A ideia é a seguinte, pega o valor da storage do jogador uma vez só, fora do foreach. Para cada valor na lista, tu só checa se a storage é maior ou igual. Tenta assim:
    if(count($characterPageQuests) != 0) { echo "<table class=\"bar\" width='100%'>"; echo "<tr><td width='90%'><b>Nome Das Graduações</b></td><td><b>Status</b></td></tr>"; $SQL = POT::getInstance()->getDBHandle(); $quest = $SQL->query("SELECT `value` FROM `player_storage` WHERE `player_id` = ".$player->getId()." AND `key` =89745")->fetch(); foreach($characterPageQuests as $value) { $status = ($quest['value']) >= $value['value'] ? "true" : "false"; echo "<tr><td width='90%'>".$value['name']."</td><td><center><img src='../../../public/images/$status.gif'></center></td></tr>"; } echo "</table>"; } ?>
     
  23. Gostei
    Rusherzin recebeu reputação de Mathias Kenfi em (Resolvido)TILE QUE MUDA DE COR!   
    Nossa, eu copiei o código do post do cara como base e nem notei, tenta assim:
     
    function onStepIn(cid, item, position, fromPosition) if not isPlayer(cid) then return true end doTransformItem(getThingFromPos(position).uid, 966) return true end function onStepOut(cid, item, position, fromPosition) if not isPlayer(cid) then return true end doTransformItem(getThingFromPos(position).uid, 965) return true end  
  24. Obrigado
    Rusherzin recebeu reputação de LeoTK em (Resolvido)[Modern ACC] PHP Verificar mais de uma value   
    @Brunds A ideia é a seguinte, pega o valor da storage do jogador uma vez só, fora do foreach. Para cada valor na lista, tu só checa se a storage é maior ou igual. Tenta assim:
    if(count($characterPageQuests) != 0) { echo "<table class=\"bar\" width='100%'>"; echo "<tr><td width='90%'><b>Nome Das Graduações</b></td><td><b>Status</b></td></tr>"; $SQL = POT::getInstance()->getDBHandle(); $quest = $SQL->query("SELECT `value` FROM `player_storage` WHERE `player_id` = ".$player->getId()." AND `key` =89745")->fetch(); foreach($characterPageQuests as $value) { $status = ($quest['value']) >= $value['value'] ? "true" : "false"; echo "<tr><td width='90%'>".$value['name']."</td><td><center><img src='../../../public/images/$status.gif'></center></td></tr>"; } echo "</table>"; } ?>
     
  25. Gostei
    Rusherzin recebeu reputação de kiinho em Tipo do item   
    @kiinho Usa a função getItemInfo e pega o valor de wieldPosition, getItemInfo(itemid).wieldPosition. Isso vai retornar um dos seguintes valores:
     
    CONST_SLOT_FIRST = 1 CONST_SLOT_HEAD = CONST_SLOT_FIRST CONST_SLOT_NECKLACE = 2 CONST_SLOT_BACKPACK = 3 CONST_SLOT_ARMOR = 4 CONST_SLOT_RIGHT = 5 CONST_SLOT_LEFT = 6 CONST_SLOT_LEGS = 7 CONST_SLOT_FEET = 8 CONST_SLOT_RING = 9 CONST_SLOT_AMMO = 10 CONST_SLOT_LAST = CONST_SLOT_AMMO  

Informação Importante

Confirmação de Termo