diff --git a/app/Changelog.md b/app/Changelog.md index 26ea3bb44..ca1c0cbd5 100644 --- a/app/Changelog.md +++ b/app/Changelog.md @@ -1,3 +1,46 @@ +## v5.2.0 + +*Sun Oct 13 2024* + +### ✨ New Features + +* Add methods `u.distance`, `u.direction` (For Catnip, these can be found in Utilities category) +* Object list panel for room editor's select mode, plus hover effect on copies and tiles + +### ⚡️ General Improvements + +* Add a menu in the app's settings to change app's UI font to Open Dyslexic or Comic Relief +* Catnip: Add "set visibility" block +* Catnip: improve block names for the Place catmod +* Create missing properties and variables when copying Catnip blocks from one asset to another (and in other similar cases) +* Improve how backgrounds and scrolling textures wrap themselves. Closes #535 +* Limit the amount of names the confirmation dialog shows when deleting several assets in the asset-browser tag. +* :globe_with_meridians: Update Chinese Simplified translation (#534 and #536 by @emaoshushu) +* :globe_with_meridians: Update Brazilian Portuguese translation (made by Henrique aka hlbarone on Discord) + +### 🐛 Bug Fixes + +* Catnip: Fix walkOverScript ignoring values in commands' arguments +* Catnip: Hide templates.killRecursive block as it is an internal API +* Fix an extra copy/tile appearing while using a rectangular fill in room editor +* Fix not being able to copy multiple blocks in catnip +* Fix overflowing content exceeding window boundaries in alertify modals. Closes #537 +* Fix repeated import of same-named images (or the same image) leading to textures with identical names, which broke exports +* Fix rooms' tile editor not accepting 0 as tile layer depth +* Fix sprited counters not rendering in room editor +* Fix tabs with room editor not opening again if this room editor has an event editor open +* Fix values in room editor's entities properties panel not updating when selecting new copies or tiles +* Fix wrong initial values for rooms' camera restriction boundaries + +### 🌐 Website + +* :zap: New layout for engine's features +* :bug: Fix issues with package icon and rounded corners on main page's grid layour +* :fire: Remove old translation keys for the homepage +* :zap: :globe_with_meridians: Update wording in the homepage's introductory text for Russian locale +* :zap: Display games from static APIs in the homepage +* :zap: Improve illustration position on various screens + ## v5.1.0 *Sat Jul 20 2024* diff --git a/app/data/ct.libs/place/types.d.ts b/app/data/ct.libs/place/types.d.ts index b5453b8ef..ba6e64e2b 100644 --- a/app/data/ct.libs/place/types.d.ts +++ b/app/data/ct.libs/place/types.d.ts @@ -91,6 +91,8 @@ declare namespace place { * @param {number} x The x coordinate to check, as if `me` was placed there. * @param {number} y The y coordinate to check, as if `me` was placed there. * @param {String} [cgroup] The collision group to check against + * @catnipName place is free for copy + * @catnipName_Ru место для копии свободно */ function free(me: Copy, x: number, y: number, cgroup?: string): boolean; @@ -102,7 +104,6 @@ declare namespace place { * * @param {Copy} me The object to check collisions on * @param {String} [cgroup] The collision group to check against - * @catnipName_Ru свободно */ function free(me: Copy, cgroup?: string): boolean; @@ -116,7 +117,8 @@ declare namespace place { * @param {String} [cgroup] The collision group to check against * @returns {Copy|PIXI.Sprite|false} The collided copy or tile, or `false` * if there is no collision. - * @catnipName_Ru занято + * @catnipName occupying object for + * @catnipName_Ru занимает место для */ function occupied(me: Copy, x: number, y: number, cgroup?: string): Copy | PIXI.Sprite | false; @@ -129,12 +131,11 @@ declare namespace place { * @param {String} [cgroup] The collision group to check against * @returns {Copy|PIXI.Sprite|false} The collided copy, or `false` * if there were no collisions. - * @catnipName_Ru занято */ function occupied(me: Copy, cgroup?: string): Copy | false; /** - * @catnipName all occupying - * @catnipName_Ru все занимающие место + * @catnipName all occupying for + * @catnipName_Ru все занимающие место для */ function occupiedMultiple(me: Copy, cgroup?: string): Array | false; function occupiedMultiple(me: Copy, x: number, y: number, cgroup?: string): @@ -240,7 +241,8 @@ declare namespace place { * @param {string} template The name of the template against which copies * the distance will be measured * @catnipAsset template:template - * @catnipName_Ru ближайший + * @catnipName nearest copy to point + * @catnipName_Ru ближайшая копия к точке */ function nearest(x: number, y: number, template: string): Copy | false; @@ -251,7 +253,8 @@ declare namespace place { * @param {string} template The name of the template against which copies * the distance will be measured * @catnipAsset template:template - * @catnipName_Ru наиболее удалённый + * @catnipName furthest copy to point + * @catnipName_Ru самая дальняя копия к точке */ function furthest(x: number, y: number, template: string): Copy | false; @@ -336,6 +339,8 @@ declare namespace place { * If omitted, will trace through all the copies in the current room. * @param {boolean} [getAll] Whether to return all the intersections (true), * or return the first one. + * @catnipName trace a line + * @catnipName_Ru проброс линии */ function traceLine(line: ICtPlaceLineSegment, cgroup: string|false, getAll: true): Array; @@ -352,6 +357,8 @@ declare namespace place { * If omitted, will trace through all the copies in the current room. * @param {boolean} [getAll] Whether to return all the intersections (true), * or return the first one. + * @catnipName trace a rectangle + * @catnipName_Ru проброс прямоугольником */ function traceRect(rect: ICtPlaceRectangle, cgroup: string, getAll: true): Array; @@ -368,6 +375,8 @@ declare namespace place { * If omitted, will trace through all the copies in the current room. * @param {boolean} [getAll] Whether to return all the intersections (true), * or return the first one. + * @catnipName trace a circle + * @catnipName_Ru проброс круга */ function traceCircle(circle: ICtPlaceCircle, cgroup: string|false, getAll: true): Array; @@ -386,6 +395,8 @@ declare namespace place { * If omitted, will trace through all the copies in the current room. * @param {boolean} [getAll] Whether to return all the intersections (true), * or return the first one. + * @catnipName trace a polyline + * @catnipName_Ru проброс ломаной линии */ function tracePolyline(polyline: Array, cgroup: string|false, getAll: true): Array; @@ -402,6 +413,8 @@ declare namespace place { * If omitted, will trace through all the copies in the current room. * @param {boolean} [getAll] Whether to return all the intersections (true), * or return the first one. + * @catnipName trace a point + * @catnipName_Ru проброс точки */ function tracePoint(point: IPoint, cgroup: string|false, getAll: true): Array; diff --git a/app/data/fonts/ComicRelief.ttf b/app/data/fonts/ComicRelief.ttf new file mode 100644 index 000000000..a9bf0dde7 Binary files /dev/null and b/app/data/fonts/ComicRelief.ttf differ diff --git a/app/data/fonts/OpenDyslexic-Bold-Italic.woff2 b/app/data/fonts/OpenDyslexic-Bold-Italic.woff2 new file mode 100644 index 000000000..aa7bcdea9 Binary files /dev/null and b/app/data/fonts/OpenDyslexic-Bold-Italic.woff2 differ diff --git a/app/data/fonts/OpenDyslexic-Bold.woff2 b/app/data/fonts/OpenDyslexic-Bold.woff2 new file mode 100644 index 000000000..2f04ad119 Binary files /dev/null and b/app/data/fonts/OpenDyslexic-Bold.woff2 differ diff --git a/app/data/fonts/OpenDyslexic-Italic.woff2 b/app/data/fonts/OpenDyslexic-Italic.woff2 new file mode 100644 index 000000000..00c19082d Binary files /dev/null and b/app/data/fonts/OpenDyslexic-Italic.woff2 differ diff --git a/app/data/fonts/OpenDyslexic-Regular.woff2 b/app/data/fonts/OpenDyslexic-Regular.woff2 new file mode 100644 index 000000000..47e26d82a Binary files /dev/null and b/app/data/fonts/OpenDyslexic-Regular.woff2 differ diff --git a/app/data/i18n/Brazilian Portuguese.json b/app/data/i18n/Brazilian Portuguese.json index dbfe834d2..217ee8369 100644 --- a/app/data/i18n/Brazilian Portuguese.json +++ b/app/data/i18n/Brazilian Portuguese.json @@ -4,6 +4,10 @@ "native": "Português Brasileiro", "eng": "Brazilian Portuguese" }, + "regionalLinks": { + "discord": "https://comigo.games/discord", + "telegram": "https://t.me/ctjsen" + }, "common": { "add": "Adicionar", "apply": "Aplicar", @@ -21,6 +25,7 @@ "docsShort": "Doc", "docsLong": "Documentação", "donate": "Doar", + "download": "Baixar", "done": "Feito!", "duplicate": "Duplicado", "exit": "Sair", @@ -88,6 +93,11 @@ "comportamentos", "comportamentos" ], + "enum": [ + "enumeração", + "enumerações", + "enumerações" + ], "texture": [ "textura", "texturas", @@ -143,6 +153,7 @@ "sortByDate": "Ordenar por data", "sortByName": "Ordenar por nome", "sortByType": "Ordenar por tipo", + "createStyleFromIt": "Criar ume estilo á partir deste", "alignModes": { "left": "Esquerda", "right": "Direita", @@ -176,7 +187,8 @@ "unwrapFolder": "Desembrulhar a pasta", "confirmDeleteFolder": "Tem certeza de que deseja excluir esta pasta? Todo o seu conteúdo também será excluído.", "confirmUnwrapFolder": "Tem certeza de que deseja desembrulhar esta pasta? Todo o seu conteúdo será colocado na pasta atual.", - "exportBehavior": "Exportar este comportamento" + "exportBehavior": "Exportar este comportamento", + "exportTandem": "Exportar este emissor de partículas" }, "builtinAssetGallery": { "galleryTip": "Essa é uma galeria interna e gratuita de várias texturas e sons. Todos os assets são CC0, WTFPL ou são liberados sob termos especiais para uso no ct.js. De qualquer forma, você pode usar esses assets da forma que desejar, seja em projetos comerciais ou qualquer outro.", @@ -192,12 +204,444 @@ "visitAuthorsTwitter": "Visite a página do autor no Twitter", "tipAuthor": "Pague um cafezinho ao author pelo seu trabalho árduo :D" }, + "catnip": { + "trashZoneHint": "Solte os blocos aqui para excluí-los rapidamente", + "properties": "Propriedades", + "propertiesHint": "As propriedades são armazenadas na cópia ou sala e podem ser acessadas posteriormente, inclusive de outras cópias.", + "variables": "Variáveis", + "variablesHint": "Variáveis ​​são temporárias e existem apenas para a execução deste evento. Elas são boas para armazenar resultados de cálculos rápidos.", + "globalVariables": "Variáveis globais", + "globalVariablesHint": "Variáveis ​​globais são acessíveis de qualquer lugar no seu projeto. Elas não são salvas entre as execuções; para isso, use os blocos \"Salvar no armazenamento\" e \"Carregar do armazenamento\".", + "createNewProperty": "Nova propriedade", + "createNewVariable": "Nova variável", + "createNewGlobalVariable": "Nova variável global", + "newPropertyPrompt": "Insira o nome da nova propriedade:", + "newVariablePrompt": "Insira o nome da nova variável. O nome não deve conter espaços em branco ou letras especiais e deve começar com uma letra.", + "newGlobalVariablePrompt": "Insira o nome da nova variável global. O nome não deve conter espaços em branco ou letras especiais e deve começar com uma letra.", + "invalidVarNameError": "Nome de variável inválido. O nome não deve conter espaços em branco ou letras especiais, e deve começar com uma letra.", + "renamePropertyPrompt": "Insira o novo nome para esta propriedade:", + "renameVariablePrompt": "Insira o novo nome para esta variável:", + "renamingAcrossProject": "Substituindo nome de variável em outros ativos…", + "errorBlock": "faltando bloco da biblioteca", + "errorBlockDeleteHint": "Clique com o botão direito para excluí-lo.", + "asyncHint": "Este bloco é executado de forma assíncrona, o que significa que ele será executado mais tarde e não bloqueará o resto do script. Use áreas de bloco dentro deste para executar comandos em seu término, mas observe que as coisas podem mudar enquanto este bloco é executado: por exemplo, a cópia que executa este bloco pode ser excluída (dependendo da sua lógica de jogo) e, portanto, se tornará inutilizável quando este bloco terminar.", + "optionsAdvanced": "Avançado", + "addCustomOption": "Adicionar uma propriedade personalizada", + "changeBlockTo": "Alterar para \"$1\"", + "goToActions": "Abrir configurações de ações", + "copyDocHtml": "Copiar como HTML para documentos", + "copySelection": "Copiar blocos selecionados", + "duplicateBlock": "Duplique este bloco", + "requiredField": "Este campo é obrigatório e está faltando um valor.", + "unnamedGroup": "Grupo sem nome", + "placeholders": { + "putBlocksHere": "Coloque os blocos aqui", + "doNothing": "Faça nada" + }, + "coreLibs": { + "appearance": "Aparência", + "arrays": "Matrizes", + "backgrounds": "Planos de fundo", + "behaviors": "Comportamentos", + "camera": "Câmera", + "console": "Console", + "emitter tandems": "Emissores", + "logic": "Lógica", + "math": "Matemática", + "misc": "Miscelânea", + "movement": "Movimento", + "objects": "Objetos", + "rooms": "salas", + "settings": "Configurações", + "sounds": "Sons", + "strings": "Strings", + "templates": "Templates", + "utilities": "Utilidades", + "actions": "Ações", + "timers": "temporizadors" + }, + "blockNames": { + "kill copy": "Destrói esta cópia", + "move copy": "Move esta cópia", + "set speed": "Definir a velocidade para", + "set gravity": "Definir a gravidade para", + "set gravityDir": "Definir gravity direction to", + "set hspeed": "Definir a velocidade horizontal para", + "set vspeed": "Definir a velocidade vertical para", + "set direction": "Definir a direção para", + "get speed": "velocidade", + "get gravity": "gravidade", + "get gravityDir": "direção da gravidade", + "get hspeed": "velocidade horizontal", + "get vspeed": " velocidade vertical", + "get direction": "direção", + "y of copy": "posição y da cópia", + "x of copy": "posição x da cópia", + "x prev": "posição x anterior", + "y prev": "posição y anterior", + "get width": "largura", + "get height": "altura", + "set width": "Definir largura para", + "set height": "Definir altura para", + "set property variable": "Definir propriedade/variável", + "increment": "Incremento", + "decrement": "Decremento", + "increase": "Aumentar", + "decrease": "Diminuir", + "this write": "Escrita", + "current room write": "Escrever na sala atual", + "write property to object": "Escrever no objeto", + "this read": "leitura", + "room read": "propriedade da sala tual", + "object read": "propriedade do objeto", + "object delete": "Excluir propriedade em um objeto", + "new array": "nova matriz", + "new object": "Criar um novo objeto", + "new empty object": "novo objeto vazio", + "convert to string": "para string", + "const string": "String (um valor constante)", + "convert to number": "para número", + "convert to boolean": "para booleano", + "note": "Nota", + "plainJs": "Executar JavaScript", + "color": "cor", + "variable": "Variável", + "property": "Propriedade", + "behavior property": "Propriedade do comportamento", + "content type entries": "Entradas do tipo de conteúdo", + "if else branch": "Condição Se-senão", + "if branch": "Condição Se", + "while loop cycle": "Laço de repetição", + "repeat": "Repetir N vezes", + "for each": "Para cada elemento da matriz", + "break loop": "Parar esta repetição", + "NOT logic operator": "Operator lógico Não", + "AND logic operator": "Operator lógico E", + "OR logic operator": "Operator lógico OU", + "AND AND logic operator": "Operator lógico E duplo", + "OR OR logic operator": "Operator lógico OU duplo", + "is": "É (igual a)", + "is not": "Não é (diferente de)", + "set texture": "Definir textura para", + "set scale": "Definir escala para", + "set scale xy": "Definir escala para", + "set angle": "Definir rotação de textura para", + "set skew": "Definir inclinação para", + "skew x": "inclinar por x", + "skew y": "inclinar por y", + "set alpha": "Definir opacidade para", + "scale x": "escalar por x", + "scale y": "escalar por y", + "get angle": "rotação de textura", + "get alpha": "opacidade", + "set tint": "Definir matiz para", + "get tint": "matiz", + "play animation": "Reproduzir animação", + "stop animation": "Parar animação", + "goto frame play": "Vá para um quadro e reproduza a animação", + "goto frame stop": "Vá para um quadro e pare a animação", + "goto frame": "Vá para um quadro", + "get animation speed": "velocidade da animação", + "set animation speed": "Definir velocidade da animação para", + "this": "este", + "concatenate strings": "Concatenar strings", + "concatenate strings triple": "Concatenar strings (triplo)", + "action value": "valor da ação", + "is action pressed": "está pressionada a ação", + "is action down": "está baixa a ação", + "is action released": "foi liberada a ação", + "templates Templates copy into room": "Copiar um modelo para a sala", + "templates Templates copy": "Copiar um modelo", + "templates Templates each": "Para cada cópia", + "templates Templates with copy": "Com cópia", + "templates Templates with template": "Com todas as cópias de um template", + "templates templates exists": "as cópias do template existem", + "rooms Rooms add bg": "Adicionar plano de fundo", + "rooms Rooms clear": "Limpar a sala atual", + "rooms Rooms remove": "Remover uma sala", + "rooms Rooms switch": "Alternar para", + "rooms Rooms restart": "Reiniciar a sala atual", + "rooms Rooms append": "Adicionar uma sala", + "rooms Rooms prepend": "Adicionar uma sala", + "rooms Rooms merge": "Mesclar na sala atual", + "rooms rooms current": "sala atual", + "rooms rooms list": "listagem de salas", + "rooms rooms starting": "sala inicial", + "behaviors Behaviors add": "Adicionar um comportamento a", + "behaviors Behaviors remove": "Remover um comportamento de", + "behaviors behaviors has": "tem comportamento", + "sounds Sounds play": "Reproduzir um som", + "sounds Sounds play at": "Reproduzir um som 3D", + "sounds Sounds stop": "Parar o som", + "sounds Sounds pause": "Pausar o som", + "sounds Sounds resume": "Retomar o som", + "sounds Sounds global volume": "Definir o volume global", + "sounds Sounds fade": "Desvanecer um som", + "sounds Sounds add filter": "Adicionar um filtro a um som", + "sounds Sounds add distortion": "Adicionar um filtro de distorção", + "sounds Sounds add equalizer": "Adicionar um filtro equalizador", + "sounds Sounds add mono filter": "Adicionar um filtro mono", + "sounds Sounds add reverb": "Adicionar um filtro de reverberação", + "sounds Sounds add stereo filter": "Adicionar um filtro estéreo", + "sounds Sounds add panner filter": "Adicionar um filtro panner 3D", + "sounds Sounds add telephone": "Adicionar um filtro de telefone", + "sounds Sounds remove filter": "Remove um filtro de um som", + "sounds Sounds speed all": "Definir velocidade global do som", + "sounds sounds load": "Carregar um som", + "sounds sounds exists": "som existe", + "sounds sounds playing": "som está tocando", + "sounds sounds toggle mute all": "todos os sons silenciados", + "sounds sounds toggle pause all": "todos os sons pausados", + "styles styles get": "obter um estilo", + "backgrounds Backgrounds add": "Adicionar um fundo", + "backgrounds backgrounds list": "lista de planos de funds", + "emitter tandems Emitters fire": "Disparar um emissor no local", + "emitter tandems Emitters append": "Acrescentar um emissor", + "emitter tandems Emitters follow": "Criar um emissor e seguir", + "emitter tandems Emitters stop": "Parar e destruir o emissor", + "emitter tandems Emitters pause": "Pausar o emissor", + "emitter tandems Emitters resume": "Retomar o emissor", + "emitter tandems Emitters clear": "Limpar as partículas do emissor", + "utilities U reshape nine patch": "Remodelar um painel de nove fatias", + "utilities u time": "tempo", + "utilities u time ui": "tempo UI", + "utilities u get environment": "ambiente", + "utilities u get os": "OS atual", + "utilities u ldx": "x comprimento de um vetor", + "utilities u ldy": "y comprimento de um vetor", + "utilities u direction": "direção", + "utilities u distance": "distância entre", + "utilities u pdc": "distância de 2 pontos", + "utilities u deg to rad": "graus para radianos", + "utilities u rad to deg": "radianos para graus", + "utilities u rotate": "rotacionar um vetor", + "utilities u rotate rad": "rotacionar um vetor em radianos", + "utilities u delta dir": "diferença de direção", + "utilities u clamp": "fixar", + "utilities u lerp": "interpolar", + "utilities u unlerp": "desinterpolar", + "utilities u map": "remapear um valor", + "utilities u get rect shape": "obter forma retangular", + "utilities u prect": "é um ponto em um retângulo", + "utilities u pcircle": "é um ponto em um círculo", + "utilities u ui to css coord": "converter ui coord para pixels css", + "utilities u game to css coord": "converter game coord para pixels css", + "utilities u ui to css scalar": "converter escalar de ui para comprimento css", + "utilities u game to css scalar": "converter comprimento do jogo para css scalar", + "utilities u game to ui coord": "converter coordenada do jogo para pixels ui", + "utilities u ui to game coord": "converter coordenada ui para pixels do jogo", + "utilities U wait": "Atraso, em milissegundos", + "utilities U wait ui": "Atraso da UI, em milissegundos", + "utilities u numbered string": "string numerada", + "utilities u get string number": "obter número da string", + "settings settings high density": "dpi alto", + "settings settings target fps": "fps alvo", + "settings settings view mode": "modo de visualização", + "settings settings fullscreen": "tela cheia", + "settings settings pixelart": "modo de pixelart", + "settings settings prevent default": "evitar eventos do navegador", + "set x": "Definir x para", + "set y": "Definir y para", + "get x": "posição x", + "get y": "posição y", + "follow this": "Seguir esta cópia", + "follow": "Seguir", + "set zoom": "Definir zoom para", + "get zoom": "zoom", + "set targetX": "Definir alvo x da câmera para", + "set targetY": "Definir alvo y da câmera para", + "set shiftX": "Definir deslocamento horizontal da câmera", + "set shiftY": "Definir deslocamento vertical da câmera", + "set drift": "Definir deriva da câmera", + "set rotation": "Definir rotação", + "set followX": "Habilitar seguir por x", + "set followY": "Habilitar seguir por y", + "set borderX": "Definir bordas horizontais para seguir", + "set borderY": "Definir bordas verticais para seguir", + "set shake": "Definir potência de sacudir a tela para", + "set shakeDecay": "Definir velocidade de decaimento de sacudir a tela para", + "set shakeFrequency": "Definir frequência de sacudir a tela para", + "set shakeX": "Definir multiplicador de sacudir horizontal para", + "set shakeY": "Definir multiplicador de sacudir vertical para", + "set shakeMax": "Definir potência máxima de sacudir para", + "set minX": "Definir limite esquerdo para", + "set maxX": "Definir limite direito para", + "set minY": "Definir limite superior para", + "set maxY": "Definir limite direito para", + "get targetX": "target x", + "get targetY": "target y", + "get computedX": "x atual", + "get computedY": "y atual", + "get shiftX": "deslocamento horizontal", + "get shiftY": "deslocamento vertical", + "get drift": "potência do desvio", + "get left": "lado esquerdo", + "get right": "lado direito", + "get top": "lado superior", + "get bottom": "lado inferior", + "get rotation": "rotação", + "get followX": "seguir horizontalmente", + "get followY": "seguir verticalmente", + "get borderX": "seguir bordas horizontais", + "get borderY": "seguir bordas verticais", + "get shake": "poder de tremor da tela", + "get shakeDecay": "velocidade de decaimento do tremor da tela", + "get shakeFrequency": "frequência de tremor da tela", + "get shakeX": "tremor horizontal multiplicador", + "get shakeY": "multiplicador de vibração vertical", + "get shakeMax": "potência máxima de vibração", + "get minX": "limite esquerdo", + "get maxX": "limite direito", + "get minY": "limite superior", + "get maxY": "limite inferior", + "console log": "Registrar no console", + "console warn": "Enviar um aviso para o console", + "console error": "Enviar um erro para o console", + "set depth": "Definir profundidade para", + "get depth": "depth", + "script options": "Opções de script", + "run script": "Executar um script", + "hasSubstring": "tem substring", + "substringPosition": "posição da substring", + "stringLength": "comprimento da string", + "replace substring": "substituir substring", + "replace all substrings": "substituir todas as substrings", + "trim whitespace": "aparar espaço em branco", + "regex passes": "passe sregex", + "replace by regex": "substituir por regex", + "replace all by regex": "substituir todas as substrings por regex", + "split by a substring": "dividir por uma substring", + "split": "dividir uma string em uma matriz", + "slice a string": "dividir uma string", + "to uppercase": "para maiúsculas", + "to lowercase": "para minúsculas", + "array unshift": "Adicionar um elemento no início", + "array push": "Adicionar um elemento no final", + "add element at position": "Adicionar um elemento na posição", + "array pop": "Remover o último elemento da matriz", + "array shift": "Remover o primeiro elemento da matriz", + "remove element from array": "Remover o elemento da matriz", + "remove at position": "Remover um elemento na posição", + "filter array": "Filtrar matriz", + "map array": "Mapear elementos da matriz", + "set text": "Definir texto", + "set disabled": "Definir estado desabilitado", + "get text": "obter texto", + "get disabled": "desabilitar", + "define function": "Definir uma função", + "return": "Retornar o resultado", + "execute function": "Executar uma função", + "get function option": "obter uma opção", + "set timer 1": "Definir 1º temporizador", + "set timer 2": "Definir 2º temporizador", + "set timer 3": "Definir 3º temporizador", + "set timer 4": "Definir 4º temporizador", + "set timer 5": "Definir 5º temporizador", + "set timer 6": "Definir 6º temporizador", + "get timer 1": "Obter valor do 1º temporizador", + "get timer 2": "Obter valor do 2º temporizador", + "get timer 3": "Obter valor do 3º temporizador", + "get timer 4": "Obter valor do 4º temporizador", + "get timer 5": "Obter valor do 5º temporizador", + "get timer 6": "Obter valor do 6º temporizador", + "set game speed": "Definir velocidade do jogo", + "get game speed": "Obter velocidade do jogo", + "deserialize object": "Desserializar objeto", + "serialize object": "serializar objeto", + "save to storage": "Salvar no armazenamento na chave", + "delete from storage": "Excluir do armazenamento a chave", + "load from storage": "carregar do armazenamento da chave", + "is key in storage": "é a chave no armazenamento", + "owning room": "sala própria" + }, + "blockDisplayNames": { + "write": "Escrever", + "if else branch": "Se", + "while loop cycle": "Enquanto", + "repeat": "Repetir", + "for each": "Para cada", + "NOT logic operator": "não", + "set": "Definir", + "x of": "x de", + "y of": "y de", + "options": "opções", + "lengthOf": "comprimento de", + "split": "dividir", + "join": "juntar", + "add element": "Adicionar um elemento", + "remove element": "Remover elemento", + "set timer 1 to": "Definir o 1º temporizador para", + "set timer 2 to": "Definir o 2º temporizador para", + "set timer 3 to": "Definir o 3º temporizador para", + "set timer 4 to": "Definir o 4º temporizador para", + "set timer 5 to": "Definir o 5º temporizador para", + "set timer 6 to": "Definir o 6th temporizador para", + "timer": "temporizador", + "read": "ler", + "set game speed to": "Definir velocidade do jogo para", + "game speed": "velocidade do jogo para" + }, + "blockLabels": { + "value": "valor", + "property": "propriedade", + "changeBy": "por", + "is not": "não é", + "is": "é", + "else": "Senão", + "timesCount": "vezes", + "toWrite": "para", + "fromRead": "de", + "atPosition": "em", + "inInside": "em", + "contains": "contém", + "forDuring": "para", + "replace": "substituir", + "replaceAll": "substituir tudo", + "replaceByRegex": "substituir por regex", + "replaceAllByRegex": "substituir tudo por regex", + "fromDestination": "de", + "fromSource": "de", + "toDestination": "para", + "store index in": "armazenar índice em", + "store in": "armazenar em", + "store result in": "armazena o resultado em", + "of array": "da matriz", + "of current room": "da sala atual", + "to current room": "para sala atual", + "AND": "e", + "OR": "ou", + "and": "e", + "then": "Então", + "catch": "Em caso de erro", + "and play animation": "e reproduz a animação", + "and stop animation": "e interrompe a animação", + "scale": "escala", + "position": "posição", + "at position": "na posição", + "with results in": "com resultados armazenados em", + "store new array in": "armazenar nova matriz em", + "and elements": "e elementos", + "secondsUnits": "segundo(s)" + }, + "blockOptions": { + "soundVolume": "Volume", + "loop": "Loop", + "isRoomUi": "Esta sala é uma camada de UI", + "speed": "Velocidade", + "start at": "Começa em", + "soundSingleInstance": "Pare outras instâncias de som" + }, + "blockDocumentation": { + "serialize object": "Este bloco serializa um objeto em uma string que pode ser salva ou transferida com segurança mais tarde. Este método não suporta datas, funções e estruturas que formam referências cíclicas. Objetos serializados com este bloco devem ser desserializados com o bloco \"Deserialize object\"", + "constant string": "Você pode usar este bloco para forçar a criação de uma string: por exemplo, quando você quer escrever um número em uma string ou converter \n em uma quebra de linha ao colocar este valor em slots curinga." + } + }, "colorPicker": { "current": "Atual", "globalPalette": "Paleta Global", "old": "Anterior", "projectPalette": "Paleta do Projeto", - "altClick": "Alt mais click para excluir" + "altClick": "Alt mais clique para excluir" }, "createAsset": { "newAsset": "Novo ativo", @@ -236,6 +680,10 @@ "documentation": "Documentação", "reference": "Referência" }, + "enumEditor": { + "addVariant": "Adicionar uma variante", + "enumUseCases": "Esta enumeração estará disponível em todo o seu código e pode atuar como um tipo de dado em esquemas de conteúdo e campos de comportamentos." + }, "exportPanel": { "hide": "Ocultar", "working": "Trabalhando…", @@ -243,6 +691,7 @@ "export": "Exportar", "exportPanel": "Exportar o Projeto", "log": "Registro de Mensagens", + "goodToGo": "Pronto para continuar! 👍", "windowsCrossBuildWarning": "Para compilar para Windows a partir do Linux/MacOS, você precisa ter o Wine instalado em seu sistema. As instruções de instalação são diferentes em vários tipos de plataformas, então é melhor você pesquisar no google :)", "cannotBuildForMacOnWin": "Infelizmente, Windows produz apenas pacotes Mac quebrados. Então tente utilizar uma máquina Linux; como por exemplo, em uma máquina virtual. Isso é 100% gratuito!", "projectTitleRequired": "You must add a title to your project in the Project tab → Authoring → Name.", @@ -254,9 +703,8 @@ "noJdkFound": "Nenhum JDK 17 foi encontrado (a variável de ambiente JAVA_HOME não foi definida ou não aponta para o JDK 17). Você pode obter o JDK 17 aqui:", "downloadJDK": "Baixe o JDK 17", "firstRunNotice": "A primeira execução para cada plataforma será lenta, uma vez que o ct.js baixará e salvará bibliotecas adicionais necessárias para o empacotamento. Isso levará algum tempo, mas nas próximas vezes será quase que instantâneo.", - "goodToGo": "Pronto para continuar! 👍", "nodeJsNotFound": "Node.js não encontrado no seu sistema", - "nodeJsDownloadPage": "Download Node.js", + "nodeJsDownloadPage": "Baixe o Node.js", "nodeJsIcons": "Node.js é opcional, mas é necessário para corrigir executáveis ​​do Windows para adicionar metadados e ícones. Se você não instalar o node.js, seu jogo não terá um ícone adequado no Windows." }, "extensionsEditor": { @@ -273,8 +721,32 @@ "icon": "Ícone:", "color": "Cor:" }, + "globalSearch": { + "buttonTitle": "Pesquisar ativos", + "nothingFound": "Nada encontrado.", + "searchHint": "Pesquisar qualquer ativo pelo seu nome. Ativos recentes também serão colocados aqui.", + "recent": "aberto recentemente" + }, "intro": { "loading": "Por favor, aguarde: gatinhos estão adquirindo velocidade da luz!", + "newUserHeader": "Bem vindo ao ct.js!", + "welcomeHeaders": [ + "Bem-vindo de volta, Tumblr sexyman!", + "Bem-vindo de volta, Neo!", + "Não é nosso codificador épico? Bem-vindo de volta!", + "É bom ver você de novo!", + "Bem-vindo de volta, Superstar!", + "Você finalmente acordou. Bem-vindo de volta!", + "General Kenobi!", + "🖖", + "Saudações, viajante!", + "Ohayo, sempai uwu", + "Olá :D olá :D olá :D", + "É bom ver você, lumberfoot.", + "✉️ Raid: Piratas Hackerman", + "Bem-vindo ao Summoner's Rift!", + "Bem-vindo à câmara de teste 20" + ], "newProject": { "header": "Criar novo", "projectName": "Nome:", @@ -285,6 +757,15 @@ "nameError": "Nome de projeto errado", "languageError": "Você deve informar uma linguagem de programação" }, + "ctDistributions": { + "released": "Versão de liberação", + "nightly": "Versão Nightly (dev) 🌚", + "dev": "Inicar pelos arquivos-fonte 🤓" + }, + "gamesFromCommunity": "Jogos da comunidade", + "submitYourOwn": "Envie o seu jogo", + "learningResources": "Recursos de aprendizagems", + "authorBy": "por $1", "recovery": { "message": "

Recuperação

O ct.js encontrou um arquivo de recuperação. Possivelmente, o seu projeto não foi salvo corretamente ou o ct.js foi encerrado em algum caso de emergência. Aqui está quando esses arquivos foram modificados pela última vez:

O seu arquivo: {0} {1}
O arquivo de recuperação: {2} {3}

Qual o arquivo que o ct.js deve abrir?

", "loadTarget": "Abrir o arquivo original", @@ -300,11 +781,13 @@ "cloneProject": "Clonar esse projeto em um novo local", "browse": "Navegar", "latest": "Últimos projetos", + "nothingToShowFiller": "Nada para ser exibido aqui! Confira os exemplos ou crie seu próprio projeto abaixo.", "examples": "Exemplos", "templates": "Templates", "templatesInfo": "Você pode rapidamente iniciar o desenvolvimento do seu jogo usando um desses templates. Eles contêm apenas uma estrutura gráfica de marcação com mecânicas funcionais. Ao selecionar um projeto, será aberto o seletor para que você possa escolher em qual pasta o seu projeto será salvo.", "unableToWriteToFolders": "O ct.js não conseguiu determinar um local apropriado para os seus projetos! Certifique-se que o ct.js está em uma pasta que você tem acesso de escrita.", "twitter": "Canal no Twitter", + "telegram": "Grupo de conversa do Telegram", "discord": "Comunidade no Discord", "github": "Ct.js no Github", "itch": "Página do ct.js no itch.io", @@ -331,8 +814,11 @@ "pickCoffeeScript": "I prefiro CoffeeScript!", "jsAndTs": "JavaScript (and TypeScript)", "jsTsDescription": "A linguagem da web. Sua sintaxe é mais complexa, mas possui destaque de erros no editor e sugestões de código. Escolha-o se você já trabalhou com código JS, C# ou Java antes.", - "pickJsTs": "I prefiro JavaScript!", - "acceptAndSpecifyDirectory": "Aceitar e escolher a pasta do projeto" + "pickJsTs": "Eu prefiro JavaScript!", + "acceptAndSpecifyDirectory": "Aceitar e escolher a pasta do projeto", + "catnip": "Catnip", + "catnipDescription": "Uma linguagem de script visual feita para o ct.js. você pode colocar blocos com arrastar-e-soltar e com o teclado. Uma boa opção se você não tem experiência ou pouca experiência com programação.", + "pickCatnip": "Eu prefiro Catnip!" }, "settings": { "actions": { @@ -350,7 +836,7 @@ "makeFromScratch": "Criar do zero", "presets": "Predefinições", "presetXYMovement": "Movimento genérico em XY", - "presetTouchAndMouse": "Mouse & Touch", + "presetTouchAndMouse": "Mouse & Toque", "presetCustom": "Importar a sua própria configuração", "exportActionPreset": "Exportar como uma predefinição", "importErrorMissingModules": "Não foi possível importar a predefinição porque o ct.js não possui os seguintes módulos: $1.", @@ -399,11 +885,17 @@ "fieldReadableName": "Nome legível", "fieldReadableNameHint": "A versão legível do nome que será utilizado no editor de conteúdo.", "fieldType": "Tipo", - "structureTypes": { - "array": "Array" - }, - "deleteContentType": "Delete esse tipo de conteúdo", - "confirmDeletionMessage": "Certeza que você quer deletar esse tipo de conteúdo? Isso é irreversível e também deletará todas as entradas desse tipo de conteúdo.", + "fieldStructure": "Estrutura", + "structureTypes": { + "atomic": "Valor único", + "array": "Matriz", + "map": "Mapa" + }, + "key": "Chave", + "value": "Valor", + "mappedType": "Tipo mapeado", + "deleteContentType": "Remover esse tipo de conteúdo", + "confirmDeletionMessage": "Certeza que você quer apagar esse tipo de conteúdo? Isso é irreversível e também deletará todas as entradas desse tipo de conteúdo.", "gotoEntries": "Vá para as entradas", "entries": "Entradas", "fixedLength": "Quantidade fixa" @@ -458,10 +950,15 @@ "deleteScript": "Deletar o script", "moveDown": "Mover para baixo", "moveUp": "Mover para cima", - "newScriptComment": "Use scripts para definir funções comuns e importar pequenas bibliotecas" + "newScriptComment": "Use scripts para definir funções comuns e importar pequenas bibliotecas", + "scriptsHint": "Scripts criados aqui serão injetados na raiz do seu jogo e, portanto, sempre serão executados no início do jogo. Apenas JavaScript e TypeScript são suportados. Variáveis ​​e tipos definidos aqui estarão disponíveis em todos os lugares do seu projeto." }, "export": { "heading": "Exportar as configurações", + "errorReporting": "Relatório de erros", + "showErrors": "Exibir erros na janela do jogo (altamente recomendado)", + "showErrorsHint": "Ct.js mostrará erros não detectados em uma janela personalizada da qual os jogadores podem copiar mensagens de erro e navegar no link de relatório que você especificar no próximo campo. Isso ajudará seus jogadores a relatar tais erros sem usar devtools.", + "errorsLink": "Link para relatar erros: (problemas do Github, formulário de contato, fórum, etc.)", "functionWrap": "Inclua todo o código em uma função. (Deixa a depuração mais difícil, mas isola o código do jogo para um contexto externo. Não habilite durante o desenvolvimento do jogo.)", "codeModifier": "Transformações de código", "obfuscateWarning": "Essa opção fará o seu código executar de 15-80% mais lento,entretanto, fará com que seja extremamente difícil de reverter o seu código ao código original.", @@ -581,7 +1078,19 @@ "to": "Para:", "textureHeading": "Textura", "selectTexture": "Selecionar…", + "textureMethod": "Use múltiplos quadros como:", + "textureMethods": { + "random": "Randômico", + "animated": "Animado" + }, + "animatedFramerate": "Taxa de quadros:", "importBuiltin": "Importar padrão…", + "easingHeader": "Suavizar", + "easing": { + "none": "Escalonado", + "linear": "Linear", + "smooth": "Suave" + }, "colorAndOpacityHeading": "Cor e Opacidade", "stepped": "Paleta", "steppedColor": "Cor", @@ -595,7 +1104,12 @@ "scale": "Escala:", "minimumSize": "Tamanho mínimo:", "minimumSizeHint": "Definir valores muito pequenos irá randomizar o tamanho de cada partícula. Quanto menor os valores, mais forte é o efeito.", + "movementType": "Tipo de movimento:", "velocityHeading": "Velocidade", + "movementTypes": { + "linear": "Linear", + "accelerated": "Com gravidade" + }, "velocity": "Velocidade:", "minimumSpeed": "Velocidade mínima:", "minimumSpeedHint": "Definir valores muito pequenos irá randomizar a velocidade de cada partícula; quanto menor for o valor, mais lenta a partícula pode ser.", @@ -603,11 +1117,19 @@ "gravityHeading": "Gravidade", "gravityNotice": "Observe que se ela é diferente de (0;0), a interpolação da velocidade será desativada, o que significa que apenas o primeiro nó do gráfico de velocidade terá o efeito.", "directionHeading": "Direção", + "rotationMethod": "Rotação de textura:", + "rotationMethods": { + "static": "Fixa", + "dynamic": "Dinâmica" + }, "startingDirection": "Direção inicial", + "rotateTexture": "Gire a textura ao longo da direção do movimento", "rotationHeading": "Rotação", "rotationSpeed": "Velocidade de rotação", "rotationAcceleration": "Aceleração de rotação:", "spawningHeading": "Gerador", + "burstSpacing": "Espaçamento entre raios, em graus:", +"burstStart": "Direção inicial, em graus:", "timeBetweenBursts": "Tempo entre as rajadas:", "spawnAtOnce": "Gerar de uma vez:", "chanceToSpawn": "Chance de gerar uma partícula:", @@ -620,6 +1142,7 @@ "spawnType": "Tipo de forma:", "spawnShapes": { "rectangle": "Retângulo", + "torus": "Circular", "star": "Estrela" }, "width": "Largura:", @@ -673,7 +1196,7 @@ "é um mágico 🔮", "está aqui para ajudar! 💪", "é um super-herói 🦸‍", - "ainda tem que mostrar 🦹‍", + "ainda tem que o mostrar 🦹‍", "é um mistério não resolvido 🔍", "é épico! ✨", "é provavelmente um robô 🤖", @@ -700,6 +1223,7 @@ }, "mainMenu": { "troubleshooting": { + "heading": "Solucionando problemas", "toggleDevTools": "Alternar ferramenta de desenvolvimento", "copySystemInfo": "Copiar informações do sistema", "systemInfoWait": "Espere um pouquinho só, enquanto busco os dados…", @@ -707,7 +1231,9 @@ "disableAcceleration": "Desativar a aceleração gráfica (precisa ser reiniciado)", "disableBuiltInDebugger": "Desativa o depurador interno", "postAnIssue": "Poste os bugs do ct.js no Github…", - "heading": "Solucionando problemas" + "disableVulkan": "Desabilitar suporte Vulkan", + "disableVulkanSDHint": "Corrige problemas de \"nenhum suporte WebGL\" no SteamDeck e alguns outros sistemas Linux. Requer reinicialização para fazer efeito.", + "restartMessage": "Reinicie o aplicativo para aplicar as alterações." }, "deploy": { "heading": "Implantação - Deploy", @@ -731,7 +1257,11 @@ "HCBlack": "Alto Contraste - Preto", "RosePine": "Pinheiro Rosé", "RosePineMoon": "Pinheiro Rosé Noturno", - "RosePineDawn": "Pinheiro Rosé Crepúsculo" + "RosePineDawn": "Pinheiro Rosé Crepúsculo", + "GoldenEye": "Olho Dourado", + "AlmaSakura": "Alma Sakura", + "Synthwave": "Synthwave '84", + "OneDarkPro": "One Drak Pro" }, "prideMode": "Modo orgulho", "language": "Idioma", @@ -744,7 +1274,16 @@ "codeFont": "Fonte para o código", "codeLigatures": "Ligaduras", "codeDense": "Layout denso", + "changeDebuggerLayout": "Alterar layout do depurador", + "debuggerLayout": "Layout do depurador", + "debuggerLayouts": { + "split": "dividir layout", + "multiwindow": "múltiplas janelas", + "automatic": "automático" + }, + "autoapplyOnLaunch": "Apply assets on game launch", "altTemplateLayout": "Layout alternativo para o editor de templates", + "scrollableCatnipLibrary": "Biblioteca de blocos roláveis ​​para Catnip", "disableSounds": "Desativar sons de UI", "changeDataFolder": "Definir o local da pasta de dados", "forceProductionForDebug": "Forçar as tarefas de produção para as exportações de depuração" @@ -781,6 +1320,7 @@ "patrons": "Patronos", "restart": "Reiniciar", "project": "Projeto", + "loadingPreviouslyOpened": "Carregando ativos abertos anteriormente...", "tour": { "header": "Visão geral do ct.js", "aboutTour": "Bem-vindo ao ct.js! Essa pequena excursão te mostrará as principais opções desse editor, para que assim você possa configurar o ct.js e criar novos assets.", @@ -836,12 +1376,17 @@ "hide": "Ocultar a camada", "findTileset": "Encontrar um mosaico", "addTileLayer": "Adicionar uma camada tile", - "addTileLayerFirst": "Primeiramente adicione uma camada de ladrilho no painel esquerdo!" + "addTileLayerFirst": "Primeiramente adicione uma camada de ladrilho no painel esquerdo!", + "cacheLayer": "Cache para esta camada", + "cacheLayerWarning": "O cache acelera muito a renderização de camadas de ladrilhos. Você deve desabilitar esta opção somente se precisar alterar esta camada de ladrilhos dinamicamente durante o jogo." }, "roomView": { "name": "Nome:", "width": "Largura da visualização:", "height": "Altura da visualização:", + "viewportHeading": "Área de visualização", + "followTemplate": "Seguir um modelo:", + "followCodeHint": "Aprenda como ajustar ainda mais esse recurso com código", "isUi": "É uma camada de UI?", "simulate": "Simular", "grid": "Grade", @@ -851,7 +1396,7 @@ "gridOff": "Desativar a grade", "xrayMode": "Mode raio X", "colorizeTileLayers": "Colorir as camadas tile", - "copies": "Copies", + "copies": "Cópias", "backgrounds": "Planos de fundo", "backgroundColor": "Cor do plano de fundo:", "tiles": "Tiles", @@ -870,6 +1415,8 @@ "shiftCopies": "Altera copies", "sortHorizontally": "Ordenar horizontalmente", "sortVertically": "Ordenar verticalmente", + "sendToBack": "Enviar para trásk", + "sendToFront": "Enviar para frente", "selectAndMove": "Selecionar e Mover", "customProperties": "Propriedades Personalizadas", "findTileset": "Localizar um mosaico", @@ -949,6 +1496,7 @@ "addCopies": "Adicionar copies", "addTiles": "Adicionar tiles", "manageBackgrounds": "Gerenciar os planos de fundo", + "uiTools": "ferramentas de IU", "roomProperties": "Configurações da sala" }, "emptyTextFiller": "" @@ -967,24 +1515,26 @@ "textWrapWidth": "Largura máxima:", "useCustomFont": "Usar fonte personalizada…", "code": "Código", - "copyCode": "Copy", + "copyCode": "Cópia", "fillColor": "Cor:", "fillColor1": "Cor 1:", "fillColor2": "Cor 2:", "fillGrad": "Gradiente", - "fillGradType": "Tipo de gradient:", + "fillGradType": "Tipo de gradiente:", "fillHorizontal": "Horizontal", "fillSolid": "Difuso", "fillType": "Tipo de preenchimento:", "fillVertical": "Vertical", "fontWeight": "Peso:", - "shadowBlur": "Embaçado:", + "shadowBlur": "Sombra Desfocada:", "shadowColor": "Cor da sombra:", "shadowShift": "Deslocar para:", "strokeColor": "Cor do traço:", "strokeWeight": "Espessura da linha:", "testText": "Um texto de teste 0123 +", - "fontSize": "Tamanho da fonte:" + "fallbackFontFamily": "Família de fontes alternativas:", + "fontSize": "Tamanho da fonte:", + "notSupportedForBitmap": "Observe que essas configurações não são suportadas para fontes de bitmap" }, "textureView": { "slicing": "Fatiamento", @@ -1036,7 +1586,9 @@ "scriptView": { "runAutomatically": "Executar no início do jogo", "language": "Linguagem:", - "convertToJavaScript": "Converter para JavaScript" + "convertToJavaScript": "Converter para JavaScript", + "confirmSwitchToCatnip": "Mudar para Catnip removerá todo o código neste script. Tem certeza de que deseja continuar?", +"confirmSwitchFromCatnip": "Mudar de Catnip removerá todo o código neste script. Se você deseja preservar o código, converta o script para JavaScript primeiro. Tem certeza de que deseja continuar e limpar o código?" }, "soundView": { "variants": "Variantes", @@ -1052,7 +1604,7 @@ "reverseReverb": "Reverter", "equalizer": "Equalizar", "hertz": "Hz", - "positionalAudio": "3D audio", + "positionalAudio": "áudio 3D ", "falloff": "dissipação:", "refDistance": "Início do dissipação:", "positionalAudioHint": "Isso afeta apenas o método sounds.playAt. A dissipação define a rapidez com que um som desaparece, sendo 0 sem queda e valores grandes significando queda quase instantânea no silêncio. O início da dissipação descreve a distância após a qual um som começa a desaparecer, sendo 1 metade da tela." @@ -1065,6 +1617,7 @@ "bitmapFontLineHeight": "Altura da linha:", "resultingBitmapFontName": "Nome do recurso", "charset": "Definição de caracteres:", + "addFont": "Adicione uma fonte…", "charsets": { "punctuation": "Dígitos e pontuação (normalmente voce precisa disso)", "basicLatin": "Latim básico", @@ -1076,7 +1629,9 @@ }, "customCharsetHint": "Digite todas as letras que você deseja incluir, maiúscula e minúscula.", "fontWeight": "Espessura da fonte:", - "typefaceName": "Nome da fonte:" + "typefaceName": "Nome da fonte:", + "pixelPerfect": "Precisão de pixel perfeito", + "pixelPerfectTooltip": "Isso deve ser ativado para fontes pixelart. Habilitar isso melhorará a precisão do desenho de linhas de 1 pixel de largura, mas reduzirá o tamanho máximo da textura para cada caractere da fonte para 4k por 4k. (O que ainda é muito para fontes pixel.) Além disso, lembre-se de que os editores de ambiente e estilo usarão um método diferente para desenhar fontes de qualquer maneira e as fontes pixel aparecerão borradas neles — isso é normal." }, "licensePanel": { "ctjsLicense": "Licença do Ct.js (MIT)", @@ -1106,12 +1661,15 @@ "staticEventWarning": "Este evento torna este comportamento estático. Você não poderá adicioná-lo ou removê-lo dinamicamente no jogo com a API Comportamentos, mas caso contrário ele será perfeitamente utilizável.", "restrictedEventWarning": "Este evento funcionará apenas em templates com as seguintes classes base: $1. Este evento não funcionará quando aplicado a templates de outras classes base.", "baseClassWarning": "Este evento não funciona com a classe base atual.", + "typedefs": "Definições de tipo adicionais:", + "typedefsHint": "Você pode descrever propriedades adicionais neste ativo para que sejam definitivamente tipadas. Exemplo:\n\nname: string;\nhp: number;\nmana: number;\ninventory: any[];\n\nIsso é completamente opcional.", "coreEventsCategories": { "lifecycle": "Ciclo de vida", "actions": "Ações", "pointer": "Eventos de ponteiro", "misc": "Diversos", "animation": "Animação", + "input": "Entrada", "timers": "Temporizadores", "app": "Aplicativo" }, @@ -1122,6 +1680,8 @@ "OnDraw": "No fim do quadro", "OnDestroy": "Destruição", "OnRoomEnd": "No final da sala", + "OnBehaviorAdded": "Comportamento adicionado", + "OnBehaviorRemoved": "Comportamento removido", "OnPointerClick": "Clique", "OnPointerSecondaryClick": "Clique com botão direito", "OnPointerEnter": "Quando o ponteiro entrar", @@ -1138,7 +1698,9 @@ "OnAnimationComplete": "Na animação finalizada", "Timer": "Temporizador $1", "OnAppFocus": "Aplicação está is ativa", - "OnAppBlur": "Aplicação está em plano de fundo" + "OnAppBlur": "Aplicação está em plano de fundo", + "OnTextChange": "Dispara quando um usuário termina de editar este campo clicando fora dele ou pressionando a tecla Enter.", + "OnTextInput": "Dispara sempre que um usuário altera o valor deste campo." }, "coreParameterizedNames": { "OnActionPress": "No pressionar da ação %%action%% (único)", @@ -1158,7 +1720,9 @@ }, "coreEventsLocals": { "OnActionDown_value": "Valor da ação atual", - "OnActionPress_value": "Valor da ação atual" + "OnActionPress_value": "Valor da ação atual", + "OnTextChange_value": "Novo valor de texto", + "OnTextInput_value": "Novo valor de texto" }, "coreEventsDescriptions": { "OnCreate": "Acionado quando a sua copy é criada.", @@ -1192,25 +1756,47 @@ "animationFPS": "FPS da animação:", "playAnimationOnStart": "Reproduzir a animação ao iniciar", "loopAnimation": "Repetir a animação", + "useBitmapText": "Usar fontes bitmap", + "errorBitmapNotConfigured": "O estilo que você selecionou não está vinculado a uma fonte que usa uma fonte bitmap. Vá até o estilo, vincule-o a uma fonte e certifique-se de que essa fonte esteja configurada para exportar um bitmap e aplique as alterações.", "blendModes": { "normal": "Normal", "add": "Adicionar (queimar)", "multiply": "Multiplicar (escurecer)", "screen": "Tela (Iluminar)" }, + "fieldType": "Tipo de campo:", + "fieldTypes": { + "text": "Texto", + "number": "Número", + "email": "E-mail", + "password": "Senha" + }, + "useCustomSelectionColor": "Use uma cor personalizada para o texto selecionado", + "maxLength": "Comprimento máximo:", "baseClass": { "AnimatedSprite": "Sprite de animação", "Text": "Texto", + "BitmapText": "Texto bitmap", "NineSlicePlane": "Painel", "Container": "Container", "Button": "Botão", "RepeatingTexture": "Textura repetitiva", - "SpritedCounter": "Contador em sprite" + "SpritedCounter": "Contador em sprite", + "TextBox": "Caixa de texto", + "ScrollBox": "Caixa de rolagem", + "Select": "Selecionar menu", + "ItemList": "Lista de itens" }, "nineSliceTop": "Fatia superior, em pixels", "nineSliceRight": "Fatia direita, em pixels", "nineSliceBottom": "Fatia inferior, em pixels", "nineSliceLeft": "Fatia esquerda, em pixels", + "layoutItemsHeading": "Layout de itens", + "horizontalPadding": "Preenchimento horizontal, em pixels", + "verticalPadding": "Preenchimento vertical, em pixels", + "horizontalSpacing": "Espaçamento horizontal de itens, em pixels", + "verticalSpacing": "Espaçamento vertical de itens, em pixels", + "alignItems": "Alinhar itens:", "autoUpdateNineSlice": "Atualizar forma de colisão automaticamente", "autoUpdateNineSliceHint": "Se um painel mudar de tamanho, ele atualizará automaticamente sua forma de colisão. Normalmente você não precisa disso para elementos puramente cosméticos ou aqueles que nunca mudam de tamanho após a criação. Você ainda pode atualizar sua forma de colisão a qualquer momento com a chamada u.reshapeNinePatch(this).", "panelHeading": "Configurações de fatiamento de textura", @@ -1222,6 +1808,7 @@ "hoverTexture": "Texture ao passar o mouse", "pressedTexture": "Textura ao pressionar o mouse", "disabledTexture": "Textura quando desativada", + "pixelPerfectScroll": "Rolagem em pixel perfeito", "defaultText": "Texto padrão:" }, "behaviorEditor": { @@ -1238,10 +1825,13 @@ "clueSolutions": { "syntax": "Este é um erro de sintaxe no seu código. Vá para o ativo e corrija-o – o editor de código irá destacar o local problemático.", "eventConfiguration": "Um dos eventos está configurado incorretamente, possuindo campos vazios. Vá até o ativo e edite os parâmetros do seu evento.", + "eventMissing": "Há um evento no seu ativo que usa um módulo ausente. Verifique se você instalou todos os módulos necessários e, se você desabilitou catmods recentemente, tente ativá-los novamente.", "emptySound": "Um dos seus áudios não possui nenhum arquivo de som anexado. Importe um arquivo de áudio para ele ou remova este som vazio.", "emptyEmitter": "Um de seus sistemas de partículas está faltando uma textura em seu emissor. Você precisará definir uma textura para ele ou remover o emissor.", "windowsFileLock": "Este é um problema específico do Windows com um arquivo bloqueado. Certifique-se de ter fechado todos os navegadores externos que iniciaram o jogo e tente exportar novamente. Se não ajudar, reinicie o ct.js.", - "noTemplateTexture": "Falta uma textura em um de seus modelos. Você precisa definir uma textura para ele." + "noTemplateTexture": "Falta uma textura em um de seus modelos. Você precisa definir uma textura para ele.", + "blockArgumentMissing": "Um argumento em um código catnip não está definido ou estava se referindo a um ativo agora removido. Vá até o ativo e defina um valor para o bloco mencionado acima.", + "blockDeclarationMissing": "Um bloco no seu código catnip usa um catmod ausente. Se você desabilitou catmods recentemente, tente ligá-los novamente ou remova os blocos problemáticos." }, "stacktrace": "Pilha de chamadas", "jumpToProblem": "Ir direto ao problema", diff --git a/app/data/i18n/Chinese Simplified.json b/app/data/i18n/Chinese Simplified.json index d558e0dde..cd8e5567f 100644 --- a/app/data/i18n/Chinese Simplified.json +++ b/app/data/i18n/Chinese Simplified.json @@ -90,9 +90,9 @@ "模板" ], "tandem": [ - "发射器串联", - "发射器串联", - "发射器串联" + "粒子", + "粒子", + "粒子" ], "room": [ "房间", @@ -128,6 +128,11 @@ "脚本", "脚本", "脚本" + ], + "enum": [ + "枚举", + "枚举", + "枚举" ] }, "next": "下一个", @@ -161,7 +166,9 @@ "bottomRight": "居右下", "fill": "填满", "Scale": "缩放" - } + }, + "download": "下载", + "createStyleFromIt": "从中创建一个样式" }, "colorPicker": { "current": "新建", @@ -257,7 +264,35 @@ "boosty": "在 Boosty 上支持 ct.js!", "sponsoredBy": "自豪地赞助 $1!", "supportedBy": "支持了 $1!", - "nothingToShowFiller": "这里没有什么可展示的! 尝试下面的示例或创建您自己的项目." + "nothingToShowFiller": "这里没有什么可展示的! 尝试下面的示例或创建您自己的项目.", + "newUserHeader": "欢迎来到 ct.js!", + "welcomeHeaders": [ + "欢迎回来, Tumblr 性感男人!", + "欢迎回来, 尼奥!", + "这不是我们代码大师吗? 欢迎回来!", + "很高兴再次见到你!", + "欢迎回来, 超级巨星!", + "你终于醒了, 欢迎回来!", + "克诺比将军!", + "🖖", + "你好, 旅行者!", + "你好, 先生", + "你好 :D 你好 :D 你好 :D", + "很高兴见到你, 大脚精灵怪.", + "✉️ 突袭: 黑客海盗", + "欢迎来到召唤师峡谷!", + "欢迎来到20号实验室" + ], + "ctDistributions": { + "released": "Release 分支", + "nightly": "每日构建 🌚", + "dev": "从源码运行 🤓" + }, + "gamesFromCommunity": "来自社区的游戏", + "submitYourOwn": "提交你自己的", + "learningResources": "学习资源", + "authorBy": "由 $1", + "telegram": "Telegram 讨论群组" }, "onboarding": { "hoorayHeader": "赞! 您成功创建了一个项目!", @@ -367,7 +402,8 @@ "deleteScript": "删除脚本", "newScriptComment": "使用脚本定义常用函数并导入小型库", "moveUp": "向上移动", - "moveDown": "向下移动" + "moveDown": "向下移动", + "scriptsHint": "这里创建的脚本将被注入到游戏的根目录中, 因此将始终在游戏开始时运行. 只支持 JavaScript 和 TypeScript. 这里定义的变量和类型在项目中的任何地方都可用." }, "export": { "heading": "导出设置", @@ -377,14 +413,18 @@ "codeModifierAndWrapNote": "注意,这些设置只适用于导出的项目(导出为web和导出为桌面命令),因为它们显著地降低了打包速度并使调试更加困难。但是,您可以在“故障排除”主菜单中强制执行它们,强制执行用于调试导出的生产任务.", "codeModifiers": { "none": "无", - "minify": "最小化亚索", + "minify": "最小化压缩", "obfuscate": "模糊混淆" }, "assetTree": "资产树", "assetTreeNote": "你可以在游戏运行时中将资产树导出为res.tree, 但它也会显示你的项目结构, 并为导出的项目增加一些权重.", "exportAssetTree": "导出资产树", "exportAssetTypes": "只导出这些资产类型:", - "autocloseDesktop": "当用户按下 \"关闭\" 按钮时退出应用程序." + "autocloseDesktop": "当用户按下 \"关闭\" 按钮时退出应用程序.", + "errorReporting": "错误报告", + "showErrors": "在游戏窗口中显示错误 (强烈建议)", + "showErrorsHint": "Ct.js 将在一个自定义窗口中显示未捕获的错误, 玩家可以从该窗口复制错误消息并在下一个字段中指定的报告链接中导航. 这将帮助您的玩家在不使用 devtools 的情况下报告这些错误.", + "errorsLink": "链接到报告错误: (Github issues,联系方式,论坛等.)" }, "catmodsSettings": "Cat模组设置", "content": { @@ -408,9 +448,15 @@ "gotoEntries": "进入条目", "entries": "条目", "structureTypes": { - "array": "数组" + "array": "数组", + "atomic": "单一值", + "map": "映射" }, - "fixedLength": "固定长度" + "fixedLength": "固定长度", + "fieldStructure": "结构", + "key": "键", + "value": "值", + "mappedType": "映射类型" }, "contentTypes": "内容类型", "main": { @@ -623,7 +669,7 @@ "restartMessage": "请重新启动应用程序以应用更改." }, "deploy": { - "exportDesktop": "导出到桌面", + "exportDesktop": "导出为桌面", "successZipExport": "已成功导出到{0}.", "zipExport": "导出为web", "heading": "部署", @@ -648,7 +694,11 @@ "HCBlack": "High-contrast Black", "RosePine": "Rosé Pine", "RosePineMoon": "Rosé Pine Moon", - "RosePineDawn": "Rosé Pine Dawn" + "RosePineDawn": "Rosé Pine Dawn", + "GoldenEye": "Golden Eye", + "AlmaSakura": "Alma Sakura", + "Synthwave": "Synthwave '84", + "OneDarkPro": "One Dark Pro" }, "newFont": "新字体:", "language": "语言", @@ -659,8 +709,17 @@ "disableSounds": "关闭 UI 音效", "changeDataFolder": "设置数据文件夹位置", "forceProductionForDebug": "强制生产任务用于调试导出", - "prideMode": "傲娇模式", - "altTemplateLayout": "模板编辑器的可选布局" + "prideMode": "彩虹模式", + "altTemplateLayout": "模板编辑器的可选布局", + "changeDebuggerLayout": "更改调试器布局", + "debuggerLayout": "调试器布局", + "debuggerLayouts": { + "split": "分割布局", + "multiwindow": "多窗口", + "automatic": "自动的" + }, + "autoapplyOnLaunch": "游戏启动时应用资产", + "scrollableCatnipLibrary": "Catnip用可滚动方块库" }, "project": { "heading": "项目", @@ -729,7 +788,8 @@ "unsavedAssets": "未保存的资产:", "runWithoutApplying": "依然启动", "applyAndRun": "应用并运行", - "cantAddEditor": "不能添加其他编辑器. 请关闭一些带有空间, 样式或串联编辑器的选项卡." + "cantAddEditor": "不能添加其他编辑器. 请关闭一些带有空间, 样式或串联编辑器的选项卡.", + "loadingPreviouslyOpened": "加载之前打开的资产…" }, "docsPanel": { "documentation": "文档", @@ -789,7 +849,8 @@ "unwrapFolder": "打开文件夹", "confirmDeleteFolder": "您确定要删除此文件夹吗? 它的所有内容也将被删除.", "confirmUnwrapFolder": "您确定要打开此文件夹吗? 它的所有内容都将放在当前文件夹中.", - "exportBehavior": "导出这个行为" + "exportBehavior": "导出这个行为", + "exportTandem": "导出这个粒子发射器" }, "soundRecorder": { "recorderHeading": "录音机", @@ -974,7 +1035,9 @@ "strokeColor": "描边颜色:", "strokeWeight": "线宽:", "testText": "测试文本 0123 +", - "fontSize": "字体大小:" + "fontSize": "字体大小:", + "fallbackFontFamily": "备用字体:", + "notSupportedForBitmap": "请注意位图字体不支持这些设置" }, "textureView": { "center": "轴线", @@ -1042,7 +1105,10 @@ }, "customCharsetHint": "输入所有你想包含的字母, 包括大写和小写.", "fontWeight": "字体粗细:", - "typefaceName": "字体名称:" + "typefaceName": "字体名称:", + "addFont": "添加字体...", + "pixelPerfect": "像素级精度", + "pixelPerfectTooltip": "这应该为像素艺术字体打开. 启用此功能将提高1像素宽的线条的绘制精度, 但是将每个字体的字符的最大纹理大小减少到 4k × 4k. (对于像素字体来说还是太多了.) 除此之外, 请记住房间和样式编辑器将使用不同的方法来绘制字体, 像素字体在它们中会显得模糊 — 这是正常的" }, "licensePanel": { "ctjsLicense": "Ct.js 许可证 (MIT)", @@ -1103,7 +1169,11 @@ "Button": "按钮", "RepeatingTexture": "重复纹理", "SpritedCounter": "精灵计数器", - "TextBox": "文本盒子" + "TextBox": "文本盒子", + "BitmapText": "位图文本", + "ScrollBox": "滚动条", + "Select": "选择菜单", + "ItemList": "项目列表" }, "nineSliceTop": "顶部切片, 以像素为单位", "nineSliceRight": "右侧切片, 以像素为单位", @@ -1115,7 +1185,16 @@ "scrollSpeedX": "X轴滚动速度:", "scrollSpeedY": "Y轴滚动速度:", "isUi": "使用UI时间", - "defaultCount": "默认精灵数:" + "defaultCount": "默认精灵数:", + "useBitmapText": "使用位图字体", + "errorBitmapNotConfigured": "选择的样式没有链接到使用位图字体的字体. 转到样式, 将其链接到字体, 并确保该字体配置为导出位图, 并应用更改.", + "layoutItemsHeading": "项目布局", + "horizontalPadding": "水平填充, 以像素为单位", + "verticalPadding": "垂直填充,以像素为单位", + "horizontalSpacing": "水平项间距, 以像素为单位", + "verticalSpacing": "垂直项间距, 以像素为单位", + "alignItems": "对齐项目:", + "pixelPerfectScroll": "像素级滚动" }, "assetInput": { "changeAsset": "切换资产", @@ -1179,7 +1258,9 @@ "OnAppFocus": "应用运行中", "OnAppBlur": "应用在后台", "OnTextChange": "文本改变时", - "OnTextInput": "文本输入时" + "OnTextInput": "文本输入时", + "OnBehaviorAdded": "行为已添加", + "OnBehaviorRemoved": "行为已移除" }, "coreParameterizedNames": { "OnActionPress": "当 %%action%% 按下", @@ -1219,12 +1300,16 @@ "OnAppFocus": "当用户返回到您的应用程序时触发.", "OnAppBlur": "当用户从你的游戏切换到其他 — 通过切换标签, 切换到另一个窗口, 或最小化游戏时触发.", "OnTextChange": "当用户通过单击该字段外部或按 Enter 键完成对该字段的编辑时触发.", - "OnTextInput": "每当用户更改此字段的值时触发." + "OnTextInput": "每当用户更改此字段的值时触发.", + "OnBehaviorAdded": "当将行为动态添加到此副本时, 将调用此方法. 此事件不适用于静态行为; 对于这些, 请使用 Creation 事件.", + "OnBehaviorRemoved": "当动态地从副本中删除此行为时, 将调用此函数. 这个事件不适用于静态行为; 对于这些, 请使用 Descruction 事件." }, "jumpToProblem": "跳转到问题处", "staticEventWarning": "此事件使此行为成为静态的. 你将无法在游戏中动态添加或删除它的行为API, 但除此之外, 它将是完全可用的.", "restrictedEventWarning": "此事件只适用于具有以下基类的模板: $1. 当应用于其他基类的模板时, 此事件将不起作用.", - "baseClassWarning": "此事件不适用于当前基类" + "baseClassWarning": "此事件不适用于当前基类", + "typedefs": "其他类型定义:", + "typedefsHint": "你可以在此资产中描述其他属性,以便确定它们的类型. 例如:\n\nname: string;\nhp: number;\nmana: number;\ninventory: any[];\n\n这是完全可选的." }, "assetConfirm": { "confirmHeading": "选择动作", @@ -1253,7 +1338,10 @@ "emptySound": "您的一个声音没有附加任何声音文件. 将声音文件导入到它或删除此空声音.", "emptyEmitter": "你的一个粒子系统发射器的纹理丢失了. 你需要为它设置纹理或者移除发射器.", "windowsFileLock": "这是一个windows特有的锁定文件问题. 确保你已经关闭了所有启动游戏的外部浏览器, 然后再次尝试导出. 如果没有帮助, 重新启动ct.js.", - "noTemplateTexture": "其中一个模板缺少纹理. 你需要为它设置纹理." + "noTemplateTexture": "其中一个模板缺少纹理. 你需要为它设置纹理.", + "eventMissing": "资产中有一个事件使用了缺失的模块. 检查您是否已经安装了所有必需的模块, 如果最近禁用了 catmods,请尝试再次打开它们.", + "blockArgumentMissing": "catnip 代码中的参数没有设置, 或者引用了一个现在已删除的资源. 转到资产并为上面提到的地方设置一个值.", + "blockDeclarationMissing": "catnip 代码中的一个代码块使用了一个丢失的 catmod. 如果最近禁用了catmods, 请尝试将其重新打开, 或删除有问题的代码块." }, "stacktrace": "调用栈", "jumpToProblem": "跳转到问题处", @@ -1272,7 +1360,10 @@ "jsAndTs": "JavaScript (以及 TypeScript)", "jsTsDescription": "web语言. 它的语法更复杂, 但它有编辑器内的错误高亮显示和代码建议. 如果你以前使用过JS, C#或Java代码, 请选择它.", "pickJsTs": "我选择JavaScript!", - "acceptAndSpecifyDirectory": "接受并选择项目文件夹" + "acceptAndSpecifyDirectory": "接受并选择项目文件夹", + "catnip": "Catnip", + "catnipDescription": "一种可视化的脚本语言, 专为 ct.js 设计. 你可以用拖放和键盘来放置方块. 如果你几乎没有编码经验, 这是一个不错的选择.", + "pickCatnip": "我选择Catnip!" }, "newAssetPrompt": { "heading": "创建新资产", @@ -1281,7 +1372,9 @@ "scriptView": { "runAutomatically": "在游戏开始时执行", "language": "语言:", - "convertToJavaScript": "转换为JavaScript" + "convertToJavaScript": "转换为JavaScript", + "confirmSwitchToCatnip": "切换到 Catnip 将删除该脚本中的所有代码. 确定要继续吗?", + "confirmSwitchFromCatnip": "从 Catnip 切换将删除该脚本中的所有代码. 如果希望保留代码, 请先将脚本转换为 JavaScript. 确定要继续并清除代码吗?" }, "soundView": { "variants": "变奏", @@ -1301,5 +1394,452 @@ "falloff": "衰减:", "refDistance": "衰减开始:", "positionalAudioHint": "这只影响 sounds.playAt 方法. 衰减设置了声音消失的速度, 0表示没有衰减, 大值表示几乎立即下降到沉默. 衰减开始指的是声音开始消失的距离, 1为屏幕的一半." + }, + "regionalLinks": { + "discord": "https://comigo.games/discord", + "telegram": "https://t.me/ctjsen" + }, + "catnip": { + "trashZoneHint": "拖拽方块到这里来快速删除它们", + "properties": "属性", + "propertiesHint": "属性存储在副本或房间中, 以后可以访问, 包括从其他副本访问.", + "variables": "变量", + "variablesHint": "变量是临时的值且仅在事件运行时存在. 它们很适合存储快速计算的结果.", + "globalVariables": "全局变量", + "globalVariablesHint": "全局变量可以从项目中的任何地方访问. 它们不会在运行期间保存; 为此, 请使用 \"保存到存储\" 和 \"从存储加载\"", + "createNewProperty": "新增属性", + "createNewVariable": "新增变量", + "createNewGlobalVariable": "新增全局变量", + "newPropertyPrompt": "输入新属性名称", + "newVariablePrompt": "输入新变量名称. 变量名不能包含空格或任何特殊字母, 且必须以字母开头.", + "newGlobalVariablePrompt": "输入新全局变量名称. 变量名不能包含空格或任何特殊字母, 且必须以字母开头.", + "invalidVarNameError": "无效的变量名. 名称不能包含空格或任何特殊字母, 且必须以字母开头.", + "renamePropertyPrompt": "输入属性新名称:", + "renameVariablePrompt": "输入变量新名称:", + "renamingAcrossProject": "替换其他资产中的变量名...", + "errorBlock": "丢失代码块支持库", + "errorBlockDeleteHint": "右键单击删除", + "asyncHint": "该块异步运行, 这意味着它将在以后执行, 而不会阻塞脚本的其余部分. 在这个块中使用块区域在它完成时运行命令, 但是请注意, 当这个块运行时, 事情可能会发生变化: 例如, 运行这个块的副本可能会被删除 (取决于你的游戏逻辑), 因此当这个块完成时将变得无法使用.", + "optionsAdvanced": "高级选项", + "addCustomOption": "新增自定义属性", + "changeBlockTo": "更变为 \"$1\"", + "goToActions": "打开动作设置", + "copyDocHtml": "复制为HTML文档", + "copySelection": "复制选定的方块", + "duplicateBlock": "复制这个方块", + "requiredField": "该字段是必需的, 缺少一个值.", + "unnamedGroup": "未命名组", + "placeholders": { + "putBlocksHere": "将方块拖放到这里", + "doNothing": "什么都不做" + }, + "coreLibs": { + "appearance": "外观", + "arrays": "数组", + "backgrounds": "背景", + "behaviors": "行为", + "camera": "相机", + "console": "控制台", + "emitter tandems": "粒子", + "logic": "逻辑", + "math": "数学", + "misc": "其他", + "movement": "移动", + "objects": "对象", + "rooms": "房间", + "settings": "设置", + "sounds": "声音", + "strings": "字符串", + "templates": "模板", + "utilities": "工具", + "actions": "动作", + "timers": "定时器" + }, + "blockNames": { + "kill copy": "销毁这个副本", + "move copy": "移动这个副本", + "set speed": "设置速度为", + "set gravity": "设置重力为", + "set gravityDir": "设置重力方向为", + "set hspeed": "设置水平速度为", + "set vspeed": "设置垂直速度为", + "set direction": "设置方向为", + "get speed": "速度", + "get gravity": "重力", + "get gravityDir": "重力方向", + "get hspeed": "水平速度", + "get vspeed": "垂直速度", + "get direction": "方向", + "y of copy": "副本y值", + "x of copy": "副本x值", + "x prev": "前x值", + "y prev": "前y值", + "get width": "宽度", + "get height": "高度", + "set width": "设置宽度为", + "set height": "设置高度为", + "set property variable": "设置属性/变量", + "increment": "增量", + "decrement": "减量", + "increase": "增加", + "decrease": "减少", + "this write": "写入", + "current room write": "写入到当前房间", + "write property to object": "写入到对象", + "this read": "读取", + "room read": "当前房间属性", + "object read": "对象属性", + "object delete": "删除对象属性", + "new array": "新增数组", + "new object": "新增对象", + "new empty object": "新增空对象", + "convert to string": "转为字符", + "const string": "字符串 (常量)", + "convert to number": "to number", + "convert to boolean": "转为布尔", + "note": "笔记", + "plainJs": "执行 JavaScript", + "color": "颜色", + "variable": "变量", + "property": "属性", + "behavior property": "行为属性", + "content type entries": "内容类型条目", + "if else branch": "If-else 分支", + "if branch": "If 分支", + "while loop cycle": "While 循环", + "repeat": "重复 N 次", + "for each": "对于数组的每个元素", + "break loop": "停止循环", + "NOT logic operator": "非逻辑运算符", + "AND logic operator": "与逻辑运算符", + "OR logic operator": "或逻辑运算符", + "AND AND logic operator": "双与逻辑运算符", + "OR OR logic operator": "双或逻辑运算符", + "is": "是 (等于)", + "is not": "不是 (不等于)", + "set texture": "设置纹理为", + "set scale": "设置缩放为", + "set scale xy": "设置缩放为", + "set angle": "设置纹理旋转为", + "set skew": "设置斜度为", + "skew x": "x轴斜度", + "skew y": "y轴斜度", + "set alpha": "设置不透明度为", + "scale x": "x轴缩放", + "scale y": "y轴缩放", + "get angle": "纹理旋转", + "get alpha": "不透明度", + "set tint": "设置色调为", + "get tint": "色调", + "play animation": "播放动画", + "stop animation": "停止动画", + "goto frame play": "跳转一个帧并播放动画", + "goto frame stop": "跳转一个帧并停止动画", + "goto frame": "跳转一个帧", + "get animation speed": "动画速度", + "set animation speed": "设置动画速度为", + "this": "this", + "concatenate strings": "拼接字符串", + "concatenate strings triple": "拼接字符串 (三元)", + "action value": "动作值", + "is action pressed": "动作被按压", + "is action down": "动作被按下", + "is action released": "动作被放开", + "templates Templates copy into room": "复制模板到房间", + "templates Templates copy": "复制模板", + "templates Templates each": "对于每个副本", + "templates Templates with copy": "同副本", + "templates Templates with template": "同模板所有副本", + "templates templates exists": "模板副本已存在", + "rooms Rooms add bg": "添加背景", + "rooms Rooms clear": "清空当前房间", + "rooms Rooms remove": "移除房间", + "rooms Rooms switch": "切换到", + "rooms Rooms restart": "重启当前房间", + "rooms Rooms append": "尾部追加房间", + "rooms Rooms prepend": "头部追加房间", + "rooms Rooms merge": "合并到当前房间", + "rooms rooms current": "当前房间", + "rooms rooms list": "房间列表", + "rooms rooms starting": "起始房间", + "behaviors Behaviors add": "添加行为到", + "behaviors Behaviors remove": "移除行为从", + "behaviors behaviors has": "有行为", + "sounds Sounds play": "播放声音", + "sounds Sounds play at": "播放 3D 声音", + "sounds Sounds stop": "停止声音", + "sounds Sounds pause": "暂停声音", + "sounds Sounds resume": "恢复声音", + "sounds Sounds global volume": "设置全局音量", + "sounds Sounds fade": "淡化声音", + "sounds Sounds add filter": "给声音添加过滤器", + "sounds Sounds add distortion": "添加失真过滤器", + "sounds Sounds add equalizer": "添加均衡器过滤器", + "sounds Sounds add mono filter": "添加单声道过滤器", + "sounds Sounds add reverb": "添加混响过滤器", + "sounds Sounds add stereo filter": "添加立体声过滤器", + "sounds Sounds add panner filter": "添加 3D 相位过滤器", + "sounds Sounds add telephone": "添加电话过滤器", + "sounds Sounds remove filter": "移除声音过滤器", + "sounds Sounds speed all": "设置全局声音速度", + "sounds sounds load": "加载声音", + "sounds sounds exists": "声音已存在", + "sounds sounds playing": "声音是否正在播放", + "sounds sounds toggle mute all": "所有声音静音", + "sounds sounds toggle pause all": "所有声音暂停", + "styles styles get": "获得样式", + "backgrounds Backgrounds add": "添加背景", + "backgrounds backgrounds list": "背景列表", + "emitter tandems Emitters fire": "在指定位置启动粒子发射器", + "emitter tandems Emitters append": "追加粒子发射器", + "emitter tandems Emitters follow": "创建粒子发射器并且跟随", + "emitter tandems Emitters stop": "停止并且销毁粒子发射器", + "emitter tandems Emitters pause": "暂停粒子发射器", + "emitter tandems Emitters resume": "恢复粒子发射器", + "emitter tandems Emitters clear": "清空发射器粒子效果", + "utilities U reshape nine patch": "重塑 nine-slice 面板", + "utilities u time": "时间", + "utilities u time ui": "ui 时间", + "utilities u get environment": "环境", + "utilities u get os": "当前操作系统", + "utilities u ldx": "向量的 x 长度", + "utilities u ldy": "向量的 y 长度", + "utilities u direction": "方向", + "utilities u distance": "间距", + "utilities u pdn": "2点方向", + "utilities u pdc": "2点距离", + "utilities u deg to rad": "角度到弧度", + "utilities u rad to deg": "弧度到角度", + "utilities u rotate": "旋转向量", + "utilities u rotate rad": "以弧度为单位旋转向量", + "utilities u delta dir": "方向差分", + "utilities u clamp": "对夹", + "utilities u lerp": "插值", + "utilities u unlerp": "无插值", + "utilities u map": "重新映射值", + "utilities u get rect shape": "获得矩形", + "utilities u prect": "是矩形中的点", + "utilities u pcircle": "是圆形中的点", + "utilities u ui to css coord": "转换 ui 坐标为 css 像素点", + "utilities u game to css coord": "转换游戏坐标为 css 像素点", + "utilities u ui to css scalar": "转换 ui 标量为 css 长度", + "utilities u game to css scalar": "转换游戏长度为 css 标量", + "utilities u game to ui coord": "转换游戏坐标为 ui 像素点", + "utilities u ui to game coord": "转换 ui 坐标为游戏像素点", + "utilities U wait": "延迟, 以毫秒计", + "utilities U wait ui": "UI 延迟, 以毫秒计", + "utilities u numbered string": "有序编号字符串", + "utilities u get string number": "获得字符串编号", + "settings settings high density": "高 dpi", + "settings settings target fps": "目标 fps", + "settings settings view mode": "视图模式", + "settings settings fullscreen": "全屏", + "settings settings pixelart": "像素艺术模式", + "settings settings prevent default": "阻止浏览器事件", + "set x": "设置 x 为", + "set y": "设置 y 为", + "get x": "x", + "get y": "y", + "follow this": "跟随这个副本", + "follow": "跟随", + "set zoom": "设置缩放为", + "get zoom": "缩放", + "set targetX": "设置相机目标 x 为", + "set targetY": "设置相机目标 y 为", + "set shiftX": "设置相机水平位移", + "set shiftY": "设置相机垂直位移", + "set drift": "设置相机偏移", + "set rotation": "设置旋转", + "set followX": "启用 x 轴跟踪", + "set followY": "启用 y 轴跟踪", + "set borderX": "设置跟踪的水平边框", + "set borderY": "设置跟踪的垂直边框", + "set shake": "设置屏幕抖动功率为", + "set shakeDecay": "设置屏幕抖动衰减速度为", + "set shakeFrequency": "设置屏幕抖动频率为", + "set shakeX": "设置水平抖动乘数为", + "set shakeY": "设置垂直抖动乘数为", + "set shakeMax": "设置最大抖动功率为", + "set minX": "设置左侧边界为", + "set maxX": "设置右侧边界为", + "set minY": "设置顶部边界为", + "set maxY": "设置右侧边界为", + "get targetX": "目标 x", + "get targetY": "目标 y", + "get computedX": "当前 x", + "get computedY": "当前 y", + "get shiftX": "水平位移", + "get shiftY": "垂直位移", + "get drift": "偏移率", + "get left": "左边", + "get right": "右边", + "get top": "上边", + "get bottom": "下边", + "get rotation": "旋转", + "get followX": "水平跟随", + "get followY": "垂直跟随", + "get borderX": "跟随水平边框", + "get borderY": "跟随垂直边框", + "get shake": "屏幕抖动功率", + "get shakeDecay": "屏幕抖动衰减速度", + "get shakeFrequency": "屏幕抖动频率", + "get shakeX": "水平抖动乘数", + "get shakeY": "垂直抖动乘数", + "get shakeMax": "最大抖动功率", + "get minX": "左边界", + "get maxX": "右边界", + "get minY": "上边界", + "get maxY": "下边界", + "console log": "打印控制台", + "console warn": "发送警告到控制台", + "console error": "发送错误到控制台", + "set depth": "设置景深为", + "get depth": "景深", + "script options": "脚本选项", + "run script": "运行脚本", + "hasSubstring": "有子字符串", + "substringPosition": "子字符串位置", + "stringLength": "字符串长度", + "replace substring": "替换子字符串", + "replace all substrings": "替换所有子字符串", + "trim whitespace": "去除空格", + "regex passes": "正则表达式匹配通过", + "replace by regex": "用正则表达式替换", + "replace all by regex": "用正则表达式替换所有子字符串", + "split by a substring": "用子字符串分割", + "split": "将字符串拆分为数组", + "slice a string": "切片字符串", + "to uppercase": "转大写", + "to lowercase": "转小写", + "array unshift": "头部添加元素", + "array push": "尾部添加元素", + "add element at position": "添加元素在位置", + "array pop": "移除数组最后元素", + "array shift": "移除数组首个元素", + "remove element from array": "移除数组中元素", + "remove at position": "移除数组中元素位置在", + "filter array": "过滤数组", + "map array": "映射数组元素", + "set text": "设置文本", + "set disabled": "设置禁用状态", + "get text": "获得文本", + "get disabled": "获得禁用", + "define function": "定义函数", + "return": "返回结果", + "execute function": "运行函数", + "get function option": "获得选项", + "set timer 1": "设置第1个定时器", + "set timer 2": "设置第2个定时器", + "set timer 3": "设置第3个定时器", + "set timer 4": "设置第4个定时器", + "set timer 5": "设置第5个定时器", + "set timer 6": "设置第6个定时器", + "get timer 1": "获得第1个定时器值", + "get timer 2": "获得第2个定时器值", + "get timer 3": "获得第3个定时器值", + "get timer 4": "获得第4个定时器值", + "get timer 5": "获得第5个定时器值", + "get timer 6": "获得第6个定时器值", + "set game speed": "设置游戏速度", + "get game speed": "获得游戏速度", + "deserialize object": "反序列化对象", + "serialize object": "序列化对象", + "save to storage": "保存到键存储", + "delete from storage": "从存储中删除键", + "load from storage": "从存储中加载键", + "is key in storage": "存储中存在键", + "owning room": "副本所在房间" + }, + "blockDisplayNames": { + "write": "写入", + "if else branch": "如果", + "while loop cycle": "循环", + "repeat": "重复", + "for each": "遍历", + "NOT logic operator": "逻辑非", + "set": "设置", + "x of": "x值", + "y of": "y值", + "options": "选项", + "lengthOf": "长度", + "split": "分离", + "join": "连接", + "add element": "添加元素", + "remove element": "移除元素", + "set timer 1 to": "设置第1个定时器为", + "set timer 2 to": "设置第2个定时器为", + "set timer 3 to": "设置第3个定时器为", + "set timer 4 to": "设置第4个定时器为", + "set timer 5 to": "设置第5个定时器为", + "set timer 6 to": "设置第6个定时器为", + "timer": "定时器", + "read": "读取", + "set game speed to": "设置游戏速度为", + "game speed": "游戏速度" + }, + "blockLabels": { + "value": "值", + "property": "属性", + "changeBy": "通过", + "is not": "不等于", + "is": "等于", + "else": "否则", + "timesCount": "数量", + "toWrite": "到", + "fromRead": "从", + "atPosition": "在", + "inInside": "在", + "contains": "包含", + "forDuring": "for", + "replace": "替换", + "replaceAll": "替换所有", + "replaceByRegex": "用正则表达式替换", + "replaceAllByRegex": "用正则表达式替换", + "fromDestination": "从", + "fromSource": "从", + "toDestination": "到", + "store index in": "存储索引到", + "store in": "存储于", + "store result in": "存储结果于", + "of array": "从数组", + "of current room": "从当前房间", + "to current room": "到当前房间", + "AND": "与", + "OR": "或", + "and": "与", + "then": "然后", + "catch": "错误时", + "and play animation": "且播放动画", + "and stop animation": "且停止动画", + "scale": "缩放", + "position": "位置", + "at position": "在位置", + "with results in": "结果存储在", + "store new array in": "存储新数组在", + "and elements": "和元素", + "secondsUnits": "秒" + }, + "blockOptions": { + "soundVolume": "音量", + "loop": "循环", + "isRoomUi": "房间是否作为 UI 层?", + "speed": "速度", + "start at": "开始于", + "soundSingleInstance": "停止其他声音实例" + }, + "blockDocumentation": { + "serialize object": "这个代码块将对象序列化成一个字符串, 以后可以安全地保存或传输. 此方法不支持构成循环引用的日期、函数和结构. 用这个块序列化的对象应该用 \"反序列化对象\" 代码块进行反序列化.", + "constant string": "可以使用这个代码块强制创建字符串: 例如, 当想要在字符串中写入数字或将 \n \\n 转换为换行符时, 将该值放入通配符插槽." + } + }, + "enumEditor": { + "addVariant": "添加一个变体", + "enumUseCases": "此枚举将在所有代码中可用, 并且可以作为内容模式和行为字段中的数据类型." + }, + "globalSearch": { + "buttonTitle": "搜索资产", + "nothingFound": "什么都没找到.", + "searchHint": "按照名字搜索任何资产. 最近的资产也会出现在这里.", + "recent": "最近打开的" } -} +} \ No newline at end of file diff --git a/app/data/i18n/English.json b/app/data/i18n/English.json index a99cc39e6..ede759614 100644 --- a/app/data/i18n/English.json +++ b/app/data/i18n/English.json @@ -17,6 +17,7 @@ "clear": "Clear", "close": "Close", "confirmDelete": "Are you sure you want to delete {0}? It cannot be undone.", + "andNMore": ", and {0} more", "contribute": "Contribute", "copy": "Copy", "copyName": "Copy the name", @@ -338,6 +339,7 @@ "skew x": "skew by x", "skew y": "skew by y", "set alpha": "Set opacity to", + "set visible": "Set visibility to", "scale x": "scale by x", "scale y": "scale by y", "get angle": "texture rotation", @@ -417,6 +419,8 @@ "utilities u get os": "current os", "utilities u ldx": "x length of a vector", "utilities u ldy": "y length of a vector", + "utilities u direction": "direction", + "utilities u distance": "distance between", "utilities u pdn": "2-point direction", "utilities u pdc": "2-point distance", "utilities u deg to rad": "degrees to radians", @@ -1299,6 +1303,8 @@ "codeFont": "Font for code", "codeLigatures": "Ligatures", "codeDense": "Dense layout", + "specialFont": "Special UI font", + "specialFontDefault": "Default", "changeDebuggerLayout": "Change debugger's layout", "debuggerLayout": "Debugger's layout", "debuggerLayouts": { @@ -1523,7 +1529,9 @@ "addTiles": "Add tiles", "manageBackgrounds": "Manage backgrounds", "uiTools": "UI tools", - "roomProperties": "Room properties" + "roomProperties": "Room properties", + "copiesList": "List of copies", + "tilesList": "List of tiles" } }, "styleView": { diff --git a/app/data/i18n/Russian.json b/app/data/i18n/Russian.json index fc620f421..373920281 100644 --- a/app/data/i18n/Russian.json +++ b/app/data/i18n/Russian.json @@ -257,6 +257,7 @@ "set scale xy": "Установить размер", "set angle": "Повернуть текстуру", "set alpha": "Установить прозрачность", + "set visible": "Установить видимость", "scale x": "размер по x", "scale y": "размер по y", "get angle": "поворот текстуры", @@ -1207,6 +1208,8 @@ "codeFont": "Шрифт для кода", "codeLigatures": "Лигатуры", "codeDense": "Меньшая высота линии", + "specialFont": "Особый шрифт для интерфейса", + "specialFontDefault": "Стандартный", "heading": "Настройки", "language": "Язык", "translateToYourLanguage": "Переведите ct.js на свой язык!", diff --git a/app/package.json b/app/package.json index f8f23f130..cd527347a 100644 --- a/app/package.json +++ b/app/package.json @@ -2,7 +2,7 @@ "main": "index.html", "name": "ctjs", "description": "ct.js — a free 2D game engine", - "version": "5.1.0", + "version": "5.2.0", "homepage": "https://ctjs.rocks/", "author": { "name": "Cosmo Myzrail Gorynych", diff --git a/comigojiChangelog.js b/comigojiChangelog.js index 8e6b13a2b..dcddb1675 100644 --- a/comigojiChangelog.js +++ b/comigojiChangelog.js @@ -1,4 +1,11 @@ -// $ comigoji-changelog ./comigojiChangelog.js > Changelog.md +/* +To append to the current changelog: + +cat ./app/Changelog.md > ./app/Changelog.md.old +comigoji-changelog ./comigojiChangelog.js > ./app/Changelog.md +cat ./app/Changelog.md.old >> ./app/Changelog.md +rm ./app/Changelog.md.old +*/ const gitCommand = 'git log --max-count=1 --tags --simplify-by-decoration --pretty="format:%cI"'; const {exec} = require('child_process'); diff --git a/docs b/docs index cdf661cea..46e970587 160000 --- a/docs +++ b/docs @@ -1 +1 @@ -Subproject commit cdf661cea6d12d43088523521f1824791a83b2c1 +Subproject commit 46e970587c902b6a8a52b0b41cb7855896005b0a diff --git a/gulpfile.mjs b/gulpfile.mjs index d9e94db36..d991c2628 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -541,6 +541,17 @@ export const bakePackages = async () => { log.info('\'bakePackages\': Built to this location:', path.resolve(path.join('./build', `ctjs - v${pack.version}`))); }; +// Reinstalls npm packages without symbolic links, as creating them on Windows +// require administrative privileges and thus fail installation through itch app / unzipping. +export const fixWindowsSymlinks = async () => { + await $({ + cwd: `./build/ctjs - v${pack.version}/win32` + })`npm install --install-links --os=win32 --cpu=ia32`; + await $({ + cwd: `./build/ctjs - v${pack.version}/win64` + })`npm install --install-links --os=win32 --cpu=x64`; +}; + export const dumpPfx = () => { if (!process.env.SIGN_PFX) { log.warn('❔ \'dumpPfx\': Cannot find PFX certificate in environment variables. Provide it as a local file at ./CoMiGoGames.pfx or set the environment variable SIGN_PFX.'); @@ -643,6 +654,7 @@ export const packages = gulp.series([ patronsCache ]), bakePackages, + fixWindowsSymlinks, patchWindowsExecutables ]); diff --git a/package.json b/package.json index 05830ddf0..d8c9393c3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "ctjsbuildenvironment", - "version": "5.1.0", + "version": "5.2.0", "description": "", "directories": { "doc": "docs" diff --git a/src/ct.release/backgrounds.ts b/src/ct.release/backgrounds.ts index 4be6f6b81..5290c9847 100644 --- a/src/ct.release/backgrounds.ts +++ b/src/ct.release/backgrounds.ts @@ -140,6 +140,12 @@ export class Background extends PIXI.TilingSprite { onStep(): void { this.shiftX += uLib.time * this.movementX; this.shiftY += uLib.time * this.movementY; + if (this.repeat === 'repeat-x' || this.repeat === 'repeat') { + this.shiftX %= this.texture.width * this.tileScale.x; + } + if (this.repeat === 'repeat-y' || this.repeat === 'repeat') { + this.shiftY %= this.texture.height * this.tileScale.y; + } } /** * Updates the position of this background. diff --git a/src/ct.release/templateBaseClasses/PixiScrollingTexture.ts b/src/ct.release/templateBaseClasses/PixiScrollingTexture.ts index 175f22896..267d90314 100644 --- a/src/ct.release/templateBaseClasses/PixiScrollingTexture.ts +++ b/src/ct.release/templateBaseClasses/PixiScrollingTexture.ts @@ -70,5 +70,7 @@ export default class PixiScrollingTexture extends PIXI.TilingSprite { this.tilePosition.x = this.scrollX; this.tilePosition.y = this.scrollY; } + this.tilePosition.x %= this.texture.width; + this.tilePosition.y %= this.texture.height; } } diff --git a/src/ct.release/templates.ts b/src/ct.release/templates.ts index 40530d3d0..0688c3d49 100644 --- a/src/ct.release/templates.ts +++ b/src/ct.release/templates.ts @@ -424,6 +424,9 @@ export const makeCopy = ( // eslint-disable-next-line @typescript-eslint/no-explicit-any }; +/** + * @catnipIgnore + */ export const killRecursive = (copy: (BasicCopy & pixiMod.DisplayObject) | Background) => { copy.kill = true; if (templatesLib.isCopy(copy) && (copy as BasicCopy).onDestroy) { diff --git a/src/ct.release/u.ts b/src/ct.release/u.ts index 12834a42c..cbaf76199 100644 --- a/src/ct.release/u.ts +++ b/src/ct.release/u.ts @@ -186,6 +186,21 @@ const uLib = { pdc(x1: number, y1: number, x2: number, y2: number): number { return Math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1)); }, + /** + * Returns direction from one copy to another, in degrees. + * Can also be used with any objects that have `x` and `y` values. + */ + direction(from: { x: number, y: number }, to: { x: number, y: number }): number { + return uLib.pdn(from.x, from.y, to.x, to.y); + }, + /** + * Returns distance from one copy to another. + * Can also be used with any objects that have `x` and `y` values. + */ + distance(from: { x: number, y: number }, to: { x: number, y: number }): number { + return uLib.pdc(from.x, from.y, to.x, to.y); + }, + // Point-Rectangle DistanCe /** * Convers degrees to radians * @param {number} deg The degrees to convert diff --git a/src/icons/special-fonts.svg b/src/icons/special-fonts.svg new file mode 100644 index 000000000..4806c92d9 --- /dev/null +++ b/src/icons/special-fonts.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/node_requires/catnip/index.ts b/src/node_requires/catnip/index.ts index 114f9e70a..42dc02598 100644 --- a/src/node_requires/catnip/index.ts +++ b/src/node_requires/catnip/index.ts @@ -5,6 +5,7 @@ import {getModulePathByName, loadModuleByName} from '../resources/modules'; import {convertFromDtsToBlocks} from './blockUtils'; import {parseFile} from './declarationExtractor'; import {getByPath} from '../i18n'; +import {getBehaviorFields} from '../events'; import propsVarsBlocks from './stdLib/propsVars'; import logicBlocks from './stdLib/logic'; @@ -345,6 +346,7 @@ export const walkOverScript = (script: IBlock[], onblock: (block: IBlock) => voi walkOverScript(value, onblock); } else { onblock(value); + walkOverScript([value], onblock); } } } @@ -592,22 +594,92 @@ export const canPaste = (target: blockArgumentType | 'script'): boolean => { export const paste = ( target: IBlock | BlockScript, index: number | string, + owningAsset: IScriptable, + owningEvent: IScriptableEvent, customOptions?: boolean ): void => { if (Array.isArray(target)) { if (!canPaste('script')) { throw new Error('[catnip] Attempt to paste into a script with an invalid clipboard.'); } - target.splice(index as number, 0, ...structuredClone(clipboard!)); } + // Fist step: Find all the used variables and props and match them + // with variables and behaviors' and regular props of the current asset. + // Use behavior props when possible, and add missing props and variables. + const vars = new Set(), + props = new Set(); + const behaviorFields = getBehaviorFields(owningAsset); + const convertToBh = new Set(); + walkOverScript(clipboard!, block => { + // Filter out visible blocks as props and vars are in a hidden group + if (block.lib !== 'core.hidden') { + return; + } + // Handle behaviors' and regular props + if (block.code === 'property' || block.code === 'behavior property') { + const propName = block.values.variableName as string; + // If the asset has a behavior prop with the same name, + // convert this prop to behavior prop later + if (behaviorFields.includes(propName)) { + convertToBh.add(propName); + } else { + props.add(propName); + } + } else if (block.code === 'variable') { + // Handle variables + vars.add(block.values.variableName as string); + } + }); + // Add missing variables + for (const varName of vars) { + if (!('variables' in owningEvent)) { + owningEvent.variables = []; + } + if (!owningEvent.variables!.includes(varName)) { + owningEvent.variables!.push(varName); + } + } + // Add missing props + for (const propName of props) { + if (!('properties' in owningAsset)) { + owningAsset.properties = []; + } + if (!owningAsset.properties!.includes(propName)) { + owningAsset.properties!.push(propName); + } + } + + /** + * @returns A copy with regular properties replaced with behavior properties where possible. + */ + const patchProps = (contents: IBlock | IBlock[]) => { + const copy = structuredClone(contents); + walkOverScript(Array.isArray(copy) ? copy : [copy], block => { + if (block.lib === 'core.hidden' && block.code === 'property') { + if (convertToBh.has(block.values.variableName as string)) { + block.code = 'behavior property'; + } + } + }); + return copy; + }; + + // Actually paste stuff + if (Array.isArray(target)) { // The target is a block list + target.splice(index as number, 0, ...(patchProps(clipboard!) as IBlock[])); + window.signals.trigger('rerenderCatnipLibrary'); + return; + } + // The target is a block const block = target as IBlock; if (customOptions) { // eslint-disable-next-line prefer-destructuring - block.customOptions![index as string] = structuredClone(clipboard![0]); + block.customOptions![index as string] = patchProps(clipboard![0]) as IBlock; } else { // eslint-disable-next-line prefer-destructuring - block.values[index as string] = structuredClone(clipboard![0]); + block.values[index as string] = patchProps(clipboard![0]) as IBlock; } + window.signals.trigger('rerenderCatnipLibrary'); }; /* @@ -669,6 +741,9 @@ export const clearSelection = (): void => { multipleSelection.clear(); redrawSelectedBlocks(previouslySelected, previousMap); }; +export const copySelected = () => { + clipboard = structuredClone(getTopBlocks([...multipleSelection.keys()])); +}; export const getSelectionHTML = (): void => { const html = []; const dummy = document.createElement('catnip-block'); diff --git a/src/node_requires/catnip/stdLib/appearance.ts b/src/node_requires/catnip/stdLib/appearance.ts index d3b97026f..e3f05d405 100644 --- a/src/node_requires/catnip/stdLib/appearance.ts +++ b/src/node_requires/catnip/stdLib/appearance.ts @@ -121,6 +121,20 @@ const blocks: (IBlockCommandDeclaration | IBlockComputedDeclaration)[] = [{ typeHint: 'number', required: true }] +}, { + name: 'Set visibility to', + type: 'command', + code: 'set visible', + icon: 'droplet', + jsTemplate: (vals) => `this.visible = ${vals.value};`, + lib: 'core.appearance', + i18nKey: 'set visible', + pieces: [{ + type: 'argument', + key: 'value', + typeHint: 'boolean', + required: true + }] }, { name: 'Set depth', type: 'command', diff --git a/src/node_requires/resources/IScriptable.d.ts b/src/node_requires/resources/IScriptable.d.ts index 713187968..749ae5b4b 100644 --- a/src/node_requires/resources/IScriptable.d.ts +++ b/src/node_requires/resources/IScriptable.d.ts @@ -21,5 +21,5 @@ interface IScriptable extends IAsset { */ extendTypes: string; /** Used for Catnip only */ - properties?: []; + properties?: string[]; } diff --git a/src/node_requires/resources/rooms/defaultRoom.ts b/src/node_requires/resources/rooms/defaultRoom.ts index 331796d7a..b617f07a7 100644 --- a/src/node_requires/resources/rooms/defaultRoom.ts +++ b/src/node_requires/resources/rooms/defaultRoom.ts @@ -15,9 +15,9 @@ const room = { height: 720, restrictCamera: false, restrictMaxX: 1280, - restrictMinX: 1280, + restrictMinX: 0, restrictMaxY: 720, - restrictMinY: 720, + restrictMinY: 0, isUi: false }; diff --git a/src/node_requires/resources/textures/index.ts b/src/node_requires/resources/textures/index.ts index 53cee9fbb..e72417cdb 100644 --- a/src/node_requires/resources/textures/index.ts +++ b/src/node_requires/resources/textures/index.ts @@ -277,7 +277,7 @@ const importImageToTexture = async (opts: { }; image.src = 'file://' + dest + '?' + Math.random(); }); - let texName; + let texName: string; if (opts.name) { texName = opts.name; } else if (opts.src instanceof Buffer) { @@ -292,6 +292,15 @@ const importImageToTexture = async (opts: { .replace(/\.(jpg|gif|png|jpeg)/gi, '') .replace(/\s/g, '_'); } + // Avoid name duplicates + const baseName = texName; + const textures = getOfType('texture'); + let texPostfix = 2; + // eslint-disable-next-line no-loop-func + while (textures.some(t => t.name === texName)) { + texName = `${baseName}_${texPostfix}`; + texPostfix++; + } const obj: ITexture = { lastmod: Number(new Date()), type: 'texture', diff --git a/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts b/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts index c8716a369..6ad4116e0 100644 --- a/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts +++ b/src/node_requires/roomEditor/IRoomEditorRiotTag.d.ts @@ -19,7 +19,11 @@ export interface IRoomEditorRiotTag extends IRiotTag { tileEditor?: IRiotTag, backgroundsEditor?: IRiotTag, zoomLabel: HTMLSpanElement, - uiTools?: IRiotTag + uiTools?: IRiotTag, + entriesList?: IRiotTag & { + updateTileEntries(): void; + resetLastSelected(): void; + } }; pixiEditor: RoomEditor; zoom: number; diff --git a/src/node_requires/roomEditor/entityClasses/Copy.ts b/src/node_requires/roomEditor/entityClasses/Copy.ts index 04afd64d8..c46876f9d 100644 --- a/src/node_requires/roomEditor/entityClasses/Copy.ts +++ b/src/node_requires/roomEditor/entityClasses/Copy.ts @@ -21,6 +21,8 @@ class Copy extends PIXI.Container { isGhost: boolean; editor: RoomEditor | RoomEditorPreview; + id: number; + sprite?: PIXI.AnimatedSprite; text?: PIXI.Text; nineSlicePlane?: PIXI.NineSlicePlane & { @@ -54,6 +56,9 @@ class Copy extends PIXI.Container { constructor(copyInfo: IRoomCopy, editor: RoomEditor | RoomEditorPreview, isGhost?: boolean) { super(); this.editor = editor; + if (this.editor.isRoomEditor) { + this.id = this.editor.copyCounter++; + } this.deserialize(copyInfo); this.isGhost = Boolean(isGhost); if (this.editor instanceof RoomEditor) { @@ -97,7 +102,7 @@ class Copy extends PIXI.Container { if (this.animated && this.sprite) { this.sprite?.update(delta); } - if (this.tilingSprite) { + if (this.tilingSprite && hasCapability(this.cachedTemplate.baseClass, 'scroller')) { this.tilingSprite.scrollX += this.tilingSprite.scrollSpeedX * time; this.tilingSprite.scrollY += this.tilingSprite.scrollSpeedY * time; if (this.tilingSprite.pixelPerfect) { diff --git a/src/node_requires/roomEditor/entityClasses/Tile.ts b/src/node_requires/roomEditor/entityClasses/Tile.ts index 0164e9b24..ea6a3c4b3 100644 --- a/src/node_requires/roomEditor/entityClasses/Tile.ts +++ b/src/node_requires/roomEditor/entityClasses/Tile.ts @@ -15,6 +15,8 @@ class Tile extends PIXI.Sprite { editor: RoomEditor | RoomEditorPreview; isGhost: boolean; + id: number; + constructor( tileInfo: ITileTemplate, editor: RoomEditor | RoomEditorPreview, @@ -22,6 +24,9 @@ class Tile extends PIXI.Sprite { ) { super(getPixiTexture(tileInfo.texture, tileInfo.frame, false)); this.editor = editor; + if (this.editor.isRoomEditor) { + this.id = this.editor.tileCounter++; + } this.deserialize(tileInfo); this.isGhost = Boolean(isGhost); this.eventMode = this.isGhost ? 'none' : 'static'; diff --git a/src/node_requires/roomEditor/index.ts b/src/node_requires/roomEditor/index.ts index 307db8a04..f2e5d56d2 100644 --- a/src/node_requires/roomEditor/index.ts +++ b/src/node_requires/roomEditor/index.ts @@ -63,13 +63,21 @@ export type tileClipboardData = ['tile', ITileTemplate, TileLayer]; export type copyClipboardData = ['copy', IRoomCopy]; class RoomEditor extends PIXI.Application { + // To quickly differ RoomEditor from RoomEditorPreview + readonly isRoomEditor = true; + history = new History(this); riotEditor: IRoomEditorRiotTag; ctRoom: IRoom; currentSelection: Set = new Set(); currentUiSelection: Copy | void; + /** + * Used to highlight an entity in a room editor + * when a user hovers over it in copy/template lists + */ + currentHoveredEntity: Copy | Tile | void = void 0; clipboard: Set = new Set(); - /** A sprite that catches any click events */ + /** A sprite that catches any click events if a user clicks in an empty space*/ clicktrap = new PIXI.Sprite(PIXI.Texture.WHITE); /** A small circle that shows currently snapped position and a ghost for copy/tile placement */ snapTarget = new SnapTarget(this); @@ -96,6 +104,11 @@ class RoomEditor extends PIXI.Application { * See this.drawSelection method to actually draw it. */ selectionOverlay = new PIXI.Graphics(); + /** + * A Graphics instance used like selectionOverlay + * to highlight hovered copies, through the entities list or in a room. + */ + hoverOverlay = new PIXI.Graphics(); /** A free transform widget that exists in **global** coordinates. */ transformer = new Transformer(this); primaryViewport: Viewport; @@ -140,6 +153,10 @@ class RoomEditor extends PIXI.Application { viewports = new Set(); tileLayers: TileLayer[] = []; + // Used to assign a non-persistent unique id to each copy/tile while editing. + copyCounter = 0; + tileCounter = 0; + observable: { on(eventName: CustomListener, fn: ((eventData: T) => void)): void; off(eventName: CustomListener, fn: ((eventData: unknown) => void)): void; @@ -169,6 +186,11 @@ class RoomEditor extends PIXI.Application { this.riotEditor = editor; this.clicktrap.alpha = 0; + this.clicktrap.on('pointerover', () => { + if (this.riotEditor.currentTool === 'select') { + this.clearSelectionOverlay(true); + } + }); this.stage.addChild(this.clicktrap); this.resizeClicktrap(); this.room.sortableChildren = true; @@ -194,6 +216,8 @@ class RoomEditor extends PIXI.Application { this.stage.addChild(this.overlays); this.deserialize(editor.room as IRoom); this.stage.addChild(this.selectionOverlay); + this.stage.addChild(this.hoverOverlay); + this.selectionOverlay.eventMode = this.hoverOverlay.eventMode = 'none'; this.stage.addChild(this.transformer); this.pointerCoords.zIndex = Infinity; @@ -219,6 +243,9 @@ class RoomEditor extends PIXI.Application { if (this.transformer.visible) { this.transformer.updateFrame(); } + if (this.currentHoveredEntity) { + this.drawSelection([this.currentHoveredEntity], true); + } // Redraw selection frame if (this.riotEditor.currentTool === 'uiTools' && this.currentUiSelection) { this.drawSelection([this.currentUiSelection]); @@ -506,9 +533,9 @@ class RoomEditor extends PIXI.Application { deleted: changes }); this.transformer.clear(); - if (this.riotEditor.refs.propertiesPanel) { - this.riotEditor.refs.propertiesPanel.updatePropList(); - } + this.riotEditor.refs.propertiesPanel?.updatePropList?.(); + this.riotEditor.refs.entriesList?.updateTileEntries(); + this.riotEditor.refs.entriesList?.resetLastSelected(); } copySelection(): void { if (this.riotEditor.currentTool !== 'select' || !this.currentSelection.size) { @@ -608,9 +635,9 @@ class RoomEditor extends PIXI.Application { this.transformer.applyTranslateY += dy; this.transformer.applyTransforms(); this.transformer.setup(); - if (this.riotEditor.refs.propertiesPanel) { - this.riotEditor.refs.propertiesPanel.updatePropList(); - } + this.riotEditor.refs.propertiesPanel?.updatePropList?.(); + this.riotEditor.refs.entriesList?.resetLastSelected(); + this.riotEditor.refs.entriesList?.updateTileEntries(); this.transformer.blink(); } sort(method: 'x' | 'y' | 'toFront' | 'toBack'): void { @@ -689,9 +716,20 @@ class RoomEditor extends PIXI.Application { }); this.transformer.setup(); } - drawSelection(entities: Iterable): void { - this.selectionOverlay.clear(); - this.selectionOverlay.visible = true; + /** + * Updates selection visualization and snapshots transforms + * for future manipulations and history management. + */ + prepareSelection() { + this.transformer.setup(); + this.marqueeBox.visible = false; + this.riotEditor.refs.propertiesPanel?.updatePropList?.(); + this.riotEditor.refs.entriesList?.update?.(); + } + drawSelection(entities: Iterable, hover?: boolean): void { + const overlay = hover ? this.hoverOverlay : this.selectionOverlay; + overlay.clear(); + overlay.visible = true; for (const entity of entities) { const w = entity.width, h = entity.height, @@ -706,28 +744,36 @@ class RoomEditor extends PIXI.Application { tr = rotateRad(w * (1 - px), -h * py, entity.rotation), bl = rotateRad(-w * px, h * (1 - py), entity.rotation), br = rotateRad(w * (1 - px), h * (1 - py), entity.rotation); - // this.selectionOverlay.lineStyle(3, getPixiSwatch('act')); - this.selectionOverlay.lineStyle(1, getPixiSwatch('background')); - this.selectionOverlay.beginFill(getPixiSwatch('act'), 0.15); - this.selectionOverlay.moveTo(x + tl[0] / sx, y + tl[1] / sy); - this.selectionOverlay.lineTo(x + tr[0] / sx, y + tr[1] / sy); - this.selectionOverlay.lineTo(x + br[0] / sx, y + br[1] / sy); - this.selectionOverlay.lineTo(x + bl[0] / sx, y + bl[1] / sy); - this.selectionOverlay.lineTo(x + tl[0] / sx, y + tl[1] / sy); - this.selectionOverlay.endFill(); - // this.selectionOverlay.lineStyle(1, getPixiSwatch('background')); - // this.selectionOverlay.moveTo(x + tl[0] / sx, y + tl[1] / sy); - // this.selectionOverlay.lineTo(x + tr[0] / sx, y + tr[1] / sy); - // this.selectionOverlay.lineTo(x + br[0] / sx, y + br[1] / sy); - // this.selectionOverlay.lineTo(x + bl[0] / sx, y + bl[1] / sy); - // this.selectionOverlay.lineTo(x + tl[0] / sx, y + tl[1] / sy); + overlay.lineStyle(1, getPixiSwatch('background')); + overlay.beginFill(getPixiSwatch('act'), 0.15); + overlay.moveTo(x + tl[0] / sx, y + tl[1] / sy); + overlay.lineTo(x + tr[0] / sx, y + tr[1] / sy); + overlay.lineTo(x + br[0] / sx, y + br[1] / sy); + overlay.lineTo(x + bl[0] / sx, y + bl[1] / sy); + overlay.lineTo(x + tl[0] / sx, y + tl[1] / sy); + overlay.endFill(); } } /** Cleans the graphic overlay used to highlight selected copies. */ - clearSelectionOverlay(): void { - this.selectionOverlay.clear(); - this.selectionOverlay.visible = false; + clearSelectionOverlay(hover?: boolean): void { + const overlay = hover ? this.hoverOverlay : this.selectionOverlay; + if (overlay.visible) { + overlay.clear(); + overlay.visible = false; + } + } + setHoverSelection(entity: Copy | Tile): void { + this.currentHoveredEntity = entity; + this.drawSelection([entity], true); + } + // Removes hover graphic and drops a link to the current hovered entity. + unhover(): void { + if (this.currentHoveredEntity) { + this.currentHoveredEntity = void 0; + this.clearSelectionOverlay(true); + } } + /** * Rounds up the values of current selection to fix rounding errors * that appear due to global-to-local transformations @@ -753,6 +799,18 @@ class RoomEditor extends PIXI.Application { this.mouseoverHint.y = pointer.global.y; } + /** + * If the properties panel is open, this method applies any possible + * property changes to the current selection set. + * This is needed to apply changes when selecting/deselecting + * additional copies or tiles. + */ + tryApplyProperties() { + if (this.riotEditor.refs.propertiesPanel) { + this.riotEditor.refs.propertiesPanel.applyChanges(); + } + } + updateMouseoverHint(text: string, newRef: unknown): void { this.mouseoverHint.text = text; this.mouseoverHintPrev = newRef; diff --git a/src/node_requires/roomEditor/interactions/clearHover.ts b/src/node_requires/roomEditor/interactions/clearHover.ts new file mode 100644 index 000000000..f2a56139a --- /dev/null +++ b/src/node_requires/roomEditor/interactions/clearHover.ts @@ -0,0 +1,13 @@ +import {IRoomEditorInteraction} from '..'; + +export const clearHover: IRoomEditorInteraction = { + ifListener: 'pointerout', + if() { + this.unhover(); + // This interaction never actually marks itself as valid + // so it never blocks the interaction stack + return false; + }, + listeners: {} +}; + diff --git a/src/node_requires/roomEditor/interactions/copies/deleteHover.ts b/src/node_requires/roomEditor/interactions/copies/deleteHover.ts new file mode 100644 index 000000000..11c5141c1 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/copies/deleteHover.ts @@ -0,0 +1,21 @@ +import {IRoomEditorInteraction} from '../..'; +import {Copy} from '../../entityClasses/Copy'; + +import * as PIXI from 'pixi.js'; + +export const deleteHover: IRoomEditorInteraction = { + ifListener: 'pointerover', + if(e: PIXI.FederatedPointerEvent) { + if (this.riotEditor.currentTool !== 'addCopies' || !e.ctrlKey) { + return false; + } + if (e.target instanceof Copy) { + this.setHoverSelection(e.target); + } + // This interaction never actually marks itself as valid + // so it never blocks the interaction stack + return false; + }, + listeners: {} +}; + diff --git a/src/node_requires/roomEditor/interactions/copies/placeCopy.ts b/src/node_requires/roomEditor/interactions/copies/placeCopy.ts index 1c8d804d6..7dac7cadb 100644 --- a/src/node_requires/roomEditor/interactions/copies/placeCopy.ts +++ b/src/node_requires/roomEditor/interactions/copies/placeCopy.ts @@ -57,10 +57,11 @@ export const placeCopy: IRoomEditorInteraction = { pointerdown(e: PIXI.FederatedPointerEvent, roomTag, affixedData) { this.compoundGhost.removeChildren(); affixedData.created = new Set(); - // Tree possible modes: placing in straight vertical/horizontal/diagonal lines, - // filling in a rectangle (shift + ctrl keys), - // and in a free form, like drawing with a brush. - // Straight method creates a ghost preview before actually creating all the copies, + // Three possible modes: + // placing in straight vertical/horizontal/diagonal lines, + // filling in a rectangle (shift + ctrl keys), + // and in a free form, like drawing with a brush. + // Straight/fill methods create a ghost preview before actually creating all the copies, // while the free form places copies as a user moves their cursor. if (e.shiftKey) { if (e.ctrlKey) { @@ -77,13 +78,15 @@ export const placeCopy: IRoomEditorInteraction = { affixedData.diagonalGrid = this.ctRoom.diagonalGrid; affixedData.startPos = affixedData.prevPos = this.snapTarget.position.clone(); affixedData.noGrid = !roomTag.gridOn || roomTag.freePlacementMode; - const newCopy = createCopy( - affixedData.startPos, - this.riotEditor.currentTemplate as ITemplate, - this - ); - affixedData.created.add([newCopy]); - this.room.addChild(newCopy); + if (affixedData.mode === 'free') { + const newCopy = createCopy( + affixedData.startPos, + this.riotEditor.currentTemplate as ITemplate, + this + ); + affixedData.created.add([newCopy]); + this.room.addChild(newCopy); + } affixedData.stepX = affixedData.stepY = 1; soundbox.play('Wood_Start'); }, diff --git a/src/node_requires/roomEditor/interactions/index.ts b/src/node_requires/roomEditor/interactions/index.ts index edc4378a7..734e9ec0e 100644 --- a/src/node_requires/roomEditor/interactions/index.ts +++ b/src/node_requires/roomEditor/interactions/index.ts @@ -9,6 +9,8 @@ export enum EPixiListeners { pointerupoutside, pointerdown, pointermove, + pointerover, + pointerout, pointerleave, globalpointermove, wheel @@ -60,13 +62,17 @@ export interface IRoomEditorInteraction { } import {updateMousePosition} from './mousePosTracker'; +import {selectHover} from './selectHover'; +import {clearHover} from './clearHover'; import {moveCameraOnWheelPress} from './camera/move'; import {goHome} from './camera/home'; import {zoomInteraction} from './camera/zoom'; import {placeCopy} from './copies/placeCopy'; import {deleteCopies} from './copies/deleteCopies'; +import {deleteHover as copiesDeleteHover} from './copies/deleteHover'; import {placeTile} from './tiles/placeTile'; import {deleteTiles} from './tiles/deleteTiles'; +import {deleteHover as tilesDeleteHover} from './tiles/deleteHover'; import {select} from './transformer/select'; import {nudgeLeft, nudgeRight, nudgeUp, nudgeDown} from './transformer/nudge'; import {rotateSelection} from './transformer/rotate'; @@ -76,10 +82,17 @@ import {deleteSelected} from './transformer/delete'; import {copy, paste} from './transformer/copyPaste'; import {undo, redo} from './history'; import {tab} from './tab'; +import {uiSelectHover} from './uiSelectHover'; import {selectUi} from './selectUi'; export const interactions = [ - updateMousePosition, // Ambient interaction — never blocks the queue + // Ambient interactions — these never block the queue + updateMousePosition, + clearHover, + selectHover, + copiesDeleteHover, + tilesDeleteHover, + uiSelectHover, nudgeLeft, nudgeRight, diff --git a/src/node_requires/roomEditor/interactions/mousePosTracker.ts b/src/node_requires/roomEditor/interactions/mousePosTracker.ts index d2b20ac40..32f1ce046 100644 --- a/src/node_requires/roomEditor/interactions/mousePosTracker.ts +++ b/src/node_requires/roomEditor/interactions/mousePosTracker.ts @@ -11,7 +11,7 @@ const updateMousePosition: IRoomEditorInteraction = { ry = Math.round(y * 100) / 100; this.pointerCoords.text = `(${rx}; ${ry})`; // This interaction never actually marks itself as valid - // So it never blocks the interaction stack + // so it never blocks the interaction stack return false; }, listeners: {} diff --git a/src/node_requires/roomEditor/interactions/placementCalculator.ts b/src/node_requires/roomEditor/interactions/placementCalculator.ts index dc01b7333..0ff27a2b7 100644 --- a/src/node_requires/roomEditor/interactions/placementCalculator.ts +++ b/src/node_requires/roomEditor/interactions/placementCalculator.ts @@ -132,12 +132,12 @@ export const calcPlacement = ( const ghosts = []; // Calculate ghost positions for (let i = 0, {x, y} = startGrid; - i < l; + i <= l; i++, x += incX, y += incY ) { const localPos = from({ - x: x + incX, - y: y + incY + x, + y }, affixedData.gridX, affixedData.gridY); ghosts.push(localPos); } diff --git a/src/node_requires/roomEditor/interactions/selectHover.ts b/src/node_requires/roomEditor/interactions/selectHover.ts new file mode 100644 index 000000000..82eebe306 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/selectHover.ts @@ -0,0 +1,27 @@ +import {IRoomEditorInteraction} from '..'; +import {Tile} from '../entityClasses/Tile'; +import {Copy} from '../entityClasses/Copy'; + +import * as PIXI from 'pixi.js'; + +export const selectHover: IRoomEditorInteraction = { + ifListener: 'pointerover', + if(e: PIXI.FederatedPointerEvent) { + if (this.riotEditor.currentTool !== 'select') { + return false; + } + if (e.target instanceof Copy && this.selectCopies) { + this.setHoverSelection(e.target); + return false; + } + if (e.target instanceof Tile && this.selectTiles) { + this.setHoverSelection(e.target); + return false; + } + // This interaction never actually marks itself as valid + // so it never blocks the interaction stack + return false; + }, + listeners: {} +}; + diff --git a/src/node_requires/roomEditor/interactions/tiles/deleteHover.ts b/src/node_requires/roomEditor/interactions/tiles/deleteHover.ts new file mode 100644 index 000000000..8b047967e --- /dev/null +++ b/src/node_requires/roomEditor/interactions/tiles/deleteHover.ts @@ -0,0 +1,23 @@ +import {IRoomEditorInteraction} from '../..'; +import {Tile} from '../../entityClasses/Tile'; + +import * as PIXI from 'pixi.js'; + +export const deleteHover: IRoomEditorInteraction = { + ifListener: 'pointerover', + if(e: PIXI.FederatedPointerEvent) { + if (this.riotEditor.currentTool !== 'addTiles' || !e.ctrlKey) { + return false; + } + if (e.target instanceof Tile) { + if (this.riotEditor.currentTileLayer.children.includes(e.target)) { + this.setHoverSelection(e.target); + } + } + // This interaction never actually marks itself as valid + // so it never blocks the interaction stack + return false; + }, + listeners: {} +}; + diff --git a/src/node_requires/roomEditor/interactions/transformer/select.ts b/src/node_requires/roomEditor/interactions/transformer/select.ts index 6960f96bd..51d91628c 100644 --- a/src/node_requires/roomEditor/interactions/transformer/select.ts +++ b/src/node_requires/roomEditor/interactions/transformer/select.ts @@ -98,9 +98,7 @@ const select: IRoomEditorInteraction = { // eslint-disable-next-line complexity pointerup(e: PIXI.FederatedPointerEvent, riotTag, affixedData, callback) { // Apply any possible property changes to the previous selectio set - if (this.riotEditor.refs.propertiesPanel) { - this.riotEditor.refs.propertiesPanel.applyChanges(); - } + this.tryApplyProperties(); const selectMap: [boolean, Iterable][] = [ [this.selectCopies, this.copies], @@ -159,11 +157,7 @@ const select: IRoomEditorInteraction = { } modifySet(this.currentSelection, delta, affixedData.mode); } - this.transformer.setup(); - this.marqueeBox.visible = false; - if (this.riotEditor.refs.propertiesPanel) { - this.riotEditor.refs.propertiesPanel.updatePropList(); - } + this.prepareSelection(); callback(); } } diff --git a/src/node_requires/roomEditor/interactions/uiSelectHover.ts b/src/node_requires/roomEditor/interactions/uiSelectHover.ts new file mode 100644 index 000000000..e6c2270b5 --- /dev/null +++ b/src/node_requires/roomEditor/interactions/uiSelectHover.ts @@ -0,0 +1,21 @@ +import {IRoomEditorInteraction} from '..'; +import {Copy} from '../entityClasses/Copy'; + +import * as PIXI from 'pixi.js'; + +export const uiSelectHover: IRoomEditorInteraction = { + ifListener: 'pointerover', + if(e: PIXI.FederatedPointerEvent) { + if (this.riotEditor.currentTool !== 'uiTools') { + return false; + } + if (e.target instanceof Copy) { + this.setHoverSelection(e.target); + } + // This interaction never actually marks itself as valid + // so it never blocks the interaction stack + return false; + }, + listeners: {} +}; + diff --git a/src/node_requires/roomEditor/previewer.ts b/src/node_requires/roomEditor/previewer.ts index 44acbd362..1ba16dc69 100644 --- a/src/node_requires/roomEditor/previewer.ts +++ b/src/node_requires/roomEditor/previewer.ts @@ -26,6 +26,8 @@ interface RoomSettings { } export class RoomEditorPreview extends PIXI.Application { + readonly isRoomEditor = false; + ctRoom: IRoom; camera = new PIXI.Container(); room = new PIXI.Container(); diff --git a/src/riotTags/catnip/catnip-block-list.tag b/src/riotTags/catnip/catnip-block-list.tag index 8abc415e9..bdd8ebcef 100644 --- a/src/riotTags/catnip/catnip-block-list.tag +++ b/src/riotTags/catnip/catnip-block-list.tag @@ -1,6 +1,12 @@ //- @attribute blocks (BlockScript) + Catnip blocks to display in the list + @attribute asset (IScriptable) + The asset that owns these blocks + @attribute scriptableevent (IScriptableEvent) + The catnip event that owns this block @attribute [placeholder] (Array) + Shown when there are no blocks in this list (when opts.blocks is an empty array) @attribute [showplaceholder] (atomic) @attribute [readonly] (atomic) catnip-block-list( @@ -32,6 +38,8 @@ catnip-block-list( oncontextmenu="{parent.onContextMenu}" ref="blocks" onclick="{parent.manageSelection}" + asset="{opts.asset}" + scriptableevent="{opts.scriptableevent}" ) catnip-insert-mark( if="{!opts.readonly}" @@ -48,7 +56,7 @@ catnip-block-list( this.namespace = 'catnip'; this.mixin(require('src/node_requires/riotMixins/voc').default); - const {getDeclaration, getMenuMutators, mutate, startBlocksTransmit, endBlocksTransmit, getTransmissionType, getSuggestedTarget, setSuggestedTarget, emptyTexture, copy, canPaste, paste, setSelection, toggleSelection, getSelectionHTML, isSelected, removeSelectedBlocks} = require('src/node_requires/catnip'); + const {getDeclaration, getMenuMutators, mutate, startBlocksTransmit, endBlocksTransmit, getTransmissionType, getSuggestedTarget, setSuggestedTarget, emptyTexture, copySelected, canPaste, paste, setSelection, toggleSelection, getSelectionHTML, isSelected, removeSelectedBlocks} = require('src/node_requires/catnip'); const {isDev} = require('src/node_requires/platformUtils'); this.getSuggestedTarget = getSuggestedTarget; @@ -149,7 +157,7 @@ catnip-block-list( label: this.voc.copySelection, icon: 'copy', click: () => { - copy([this.contextBlock]); + copySelected(); this.contextBlock = false; this.update(); } @@ -259,7 +267,12 @@ catnip-block-list( label: this.vocGlob.paste, icon: 'clipboard', click: () => { - paste(this.opts.blocks, pastePosition); + paste( + this.opts.blocks, + pastePosition, + this.opts.asset, + this.opts.scriptableevent + ); this.update(); } }] diff --git a/src/riotTags/catnip/catnip-block.tag b/src/riotTags/catnip/catnip-block.tag index b827b7bc8..0608ed691 100644 --- a/src/riotTags/catnip/catnip-block.tag +++ b/src/riotTags/catnip/catnip-block.tag @@ -1,6 +1,10 @@ //- @attribute block (IBlock) The block from the block script that is rendered + @attribute asset (IScriptable) + The asset that owns this block + @attribute scriptableevent (IScriptableEvent) + The catnip event that owns this block @atribute [nodrag] (atomic) Prohibits dragging this block @attribute [readonly] (atomic) @@ -72,6 +76,8 @@ catnip-block( showplaceholder="showplaceholder" placeholder="{piece.placeholder}" readonly="{parent.opts.readonly}" + asset="{opts.asset}" + scriptableevent="{opts.scriptableevent}" ) // Options .catnip-block-Options(if="{piece.type === 'options'}") @@ -96,6 +102,8 @@ catnip-block( ondragend="{parent.onDragEnd}" oncontextmenu="{parent.onContextMenu}" onclick="{parent.tryMutate}" + asset="{opts.asset}" + scriptableevent="{opts.scriptableevent}" ) input.catnip-block-aConstantInput( ondrop="{parent.onDrop}" @@ -157,6 +165,8 @@ catnip-block( ondragend="{parent.onOptionDragEnd}" oncontextmenu="{parent.onContextMenu}" onclick="{parent.tryMutateCustomOption}" + asset="{opts.asset}" + scriptableevent="{opts.scriptableevent}" ) input.catnip-block-aConstantInput.wildcard( ondrop="{parent.onOptionDrop}" @@ -185,6 +195,8 @@ catnip-block( ondragend="{parent.onDragEnd}" oncontextmenu="{parent.onContextMenu}" onclick="{parent.tryMutate}" + asset="{opts.asset}" + scriptableevent="{opts.scriptableevent}" ) input.catnip-block-aConstantInput( ondrop="{parent.onDrop}" @@ -513,10 +525,21 @@ catnip-block( }, click: () => { if (this.contextOption) { - paste(this.opts.block, this.contextOption, true); + paste( + this.opts.block, + this.contextOption, + this.opts.asset, + this.opts.scriptableevent, + true + ); this.contextOption = false; } else { - paste(this.opts.block, this.contextPiece.key); + paste( + this.opts.block, + this.contextPiece.key, + this.opts.asset, + this.opts.scriptableevent + ); this.contextPiece = false; } this.update(); @@ -590,10 +613,21 @@ catnip-block( icon: 'clipboard', click: () => { if (this.contextOption) { - paste(this.opts.block, this.contextOption, true); + paste( + this.opts.block, + this.contextOption, + this.opts.asset, + this.opts.scriptableevent, + true + ); this.contextOption = false; } else { - paste(this.opts.block, this.contextPiece.key); + paste( + this.opts.block, + this.contextPiece.key, + this.opts.asset, + this.opts.scriptableevent + ); this.contextPiece = false; } this.update(); diff --git a/src/riotTags/catnip/catnip-editor.tag b/src/riotTags/catnip/catnip-editor.tag index b59bae56a..d6f485aba 100644 --- a/src/riotTags/catnip/catnip-editor.tag +++ b/src/riotTags/catnip/catnip-editor.tag @@ -23,6 +23,8 @@ catnip-editor(class="flexrow {opts.class}" onpointermove="{repositionGhost}" ond showplaceholder="showplaceholder" if="{opts.event || opts.scriptmode}" onclick="{tryDeselect}" + asset="{opts.asset}" + scriptableevent="{opts.event}" ) .flexfix(ondragenter="{handlePreDrop}" ondragover="{handlePreDrop}" if="{opts.event || opts.scriptmode}") catnip-library.flexfix-body( diff --git a/src/riotTags/catnip/catnip-library.tag b/src/riotTags/catnip/catnip-library.tag index 0903aa7e6..8810b1a1a 100644 --- a/src/riotTags/catnip/catnip-library.tag +++ b/src/riotTags/catnip/catnip-library.tag @@ -267,13 +267,16 @@ catnip-library(class="{opts.class}").flexrow this.enums = getOfType('enum'); this.update(); }; + const update = () => this.update(); window.signals.on('enumCreated', updateEnums); window.signals.on('enumRemoved', updateEnums); window.signals.on('enumChanged', updateEnums); + window.signals.on('rerenderCatnipLibrary', update); this.on('unmount', () => { window.signals.off('enumCreated', updateEnums); window.signals.off('enumRemoved', updateEnums); window.signals.off('enumChanged', updateEnums); + window.signals.off('rerenderCatnipLibrary', update); }); this.onDragStart = e => { diff --git a/src/riotTags/editors/room-editor/room-editor.tag b/src/riotTags/editors/room-editor/room-editor.tag index 1e6388486..eceb25261 100644 --- a/src/riotTags/editors/room-editor/room-editor.tag +++ b/src/riotTags/editors/room-editor/room-editor.tag @@ -5,8 +5,8 @@ room-editor.aPanel.aView(data-hotkey-scope="{asset.uid}") canvas(ref="canvas" oncontextmenu="{openMenus}") - // Toolbar .room-editor-aToolsetHolder + // Toolbar .room-editor-aToolbar.aButtonGroup.vertical button.forcebackground( onclick="{setTool('select')}" @@ -150,6 +150,34 @@ room-editor.aPanel.aView(data-hotkey-scope="{asset.uid}") use(xlink:href="#check") span {vocGlob.apply} + // Additional contextual panels on the right side + room-entities-list.room-editor-aContextPanel.np( + if="{currentTool === 'select' && currentEntityList}" + entitytype="{currentEntityList}" + room="{asset}" + editor="{pixiEditor}" + ref="entriesList" + ) + // Additional contextual tools + .room-editor-aToolbar.aButtonGroup.vertical.last(if="{currentTool === 'select'}") + button.forcebackground( + onclick="{setEntityList('copies')}" + class="{active: currentEntityList === 'copies'}" + title="{voc.tools.copiesList} (A)" + data-hotkey="a" + data-hotkey-require-scope="{asset.uid}" + ) + svg.feather + use(xlink:href="#template") + button.forcebackground( + onclick="{setEntityList('tiles')}" + class="{active: currentEntityList === 'tiles'}" + title="{voc.tools.tilesList} (S)" + data-hotkey="s" + data-hotkey-require-scope="{asset.uid}" + ) + svg.feather + use(xlink:href="#grid") room-events-editor(if="{editingEvents}" room="{room}" onsave="{closeRoomEvents}") context-menu(menu="{gridMenu}" ref="gridMenu") context-menu(menu="{zoomMenu}" ref="zoomMenu") @@ -346,8 +374,12 @@ room-editor.aPanel.aView(data-hotkey-scope="{asset.uid}") manageBackgrounds: 'backgroundsVisible', uiTools: 'copiesVisible' }; - this.setTool = tool => () => { + this.setTool = tool => e => { const prevTool = this.currentTool; + if (tool === prevTool) { // Bail out w/o a redraw if a user selects the same tool + e.preventUpdate = true; + return; + } this.currentTool = tool; if (tool in mandatoryVisibilityMap) { this.pixiEditor[mandatoryVisibilityMap[tool]] = true; @@ -362,6 +394,9 @@ room-editor.aPanel.aView(data-hotkey-scope="{asset.uid}") } if (tool !== 'select') { + // Clear hover visualization when switching to modes other than 'select' + this.pixiEditor.clearSelectionOverlay(true); + // Deselect everything this.pixiEditor.transformer.clear(); } if (tool !== 'uiTools') { @@ -373,6 +408,13 @@ room-editor.aPanel.aView(data-hotkey-scope="{asset.uid}") [this.currentTileLayer] = this.pixiEditor.tileLayers; } }; + this.setEntityList = entityType => () => { + if (this.currentEntityList !== entityType) { + this.currentEntityList = entityType; + } else { + this.currentEntityList = void 0; + } + }; /* These are used to describe current template selection when the addCopy tool is on */ this.currentTemplate = -1; diff --git a/src/riotTags/editors/room-editor/room-entities-list.tag b/src/riotTags/editors/room-editor/room-entities-list.tag new file mode 100644 index 000000000..88a5edab4 --- /dev/null +++ b/src/riotTags/editors/room-editor/room-entities-list.tag @@ -0,0 +1,141 @@ +//- + @attribute entitytype (resourceType) + @attribute room (IRoom) + @attribute editor (RoomEditor) +room-entities-list(onpointerout="{clearHover}") + .pad + .aSearchWrap.wide + input.inline(type="text" onfocus="{selectAllSearch}" onkeyup="{updateSearch}") + svg.feather + use(xlink:href="#search") + ul.aMenu.nmt(if="{opts.entitytype === 'copies'}") + li( + each="{copy, ind in opts.editor.copies}" + if="{!searchQuery || copy.cachedTemplate.name.toLowerCase().includes(searchQuery)}" + class="{active: parent.opts.editor.currentSelection.has(copy)}" + onclick="{select}" + onpointerenter="{drawHover}" + ) + img.room-entities-list-aThumbnail(src="{getThumbnail(copy.cachedTemplate)}") + span {copy.cachedTemplate.name} + | + | + span.small.dim \#{copy.id} + ul.aMenu(if="{opts.entitytype === 'tiles'}") + li( + each="{tile, ind in tilesEntries}" + if="{!searchQuery || getById('texture', tile.tileTexture).name.toLowerCase().includes(searchQuery)}" + class="{active: parent.opts.editor.currentSelection.has(tile)}" + onclick="{select}" + onpointerenter="{drawHover}" + ) + img.room-entities-list-aThumbnail( + if="{tilesEntries.length <= 500}" + src="{getThumbnail(getById('texture', tile.tileTexture))}" + ) + span {getById('texture', tile.tileTexture).name} + | + | + span @{tile.tileFrame} + | + | + span.small.dim \#{tile.id} + script. + const {getThumbnail, getById} = require('src/node_requires/resources'); + this.getThumbnail = getThumbnail; + this.getById = getById; + + let lastSelected; + this.select = e => { + this.opts.editor.tryApplyProperties(); + const {copy, tile, ind} = e.item; + const object = copy ?? tile; + const set = this.opts.editor.currentSelection; + if (e.ctrlKey) { + // Toggle selection + if (set.has(object)) { + set.delete(object); + } else { + set.add(object); + } + } else if (e.shiftKey) { + // Add a range of objects to selection + if (lastSelected) { + let array; + if (copy) { + array = this.opts.editor.copies; + } else if (tile) { + array = this.tilesEntries; + } + const ind2 = array.indexOf(lastSelected), + from = Math.min(ind, ind2), + to = Math.max(ind, ind2); + // Split code for faster selection when no search query is specified + if (!this.searchQuery) { + for (const rangeObj of array.slice(from, to)) { + set.add(rangeObj); + } + } else if (tile) { + for (const rangeObj of array.slice(from, to)) { + if (getById('texture', rangeObj.tileTexture).name.toLowerCase().includes(this.searchQuery)) { + set.add(rangeObj); + } + } + } else if (copy) { + for (const rangeObj of array.slice(from, to)) { + if (rangeObj.cachedTemplate.name.toLowerCase().includes(this.searchQuery)) { + set.add(rangeObj); + } + } + } + } + set.add(object); + } else { + // Override selection + set.clear(); + set.add(object); + } + lastSelected = object; + this.opts.editor.prepareSelection(); + }; + this.drawHover = e => { + const {copy, tile} = e.item; + this.opts.editor.setHoverSelection(copy ?? tile); + e.preventUpdate = true; + }; + this.clearHover = e => { + e.preventUpdate = true; + if (e.target !== this.root) { + return; + } + this.opts.editor.clearSelectionOverlay(true); + this.resetLastSelected(); + }; + + // Cache the result of RoomEditor.tiles.entries() + let lastMode = this.opts.entitytype; + this.updateTileEntries = () => { + this.tilesEntries = [...this.opts.editor.tiles]; + }; + this.on('update', () => { + // Update tile cache when switching modes + if (lastMode !== this.opts.entitytype) { + lastSelected = void 0; + lastMode = this.opts.entitytype; + if (this.opts.entitytype === 'tiles') { + this.updateTileEntries(); + } + } + }); + this.updateTileEntries(); + + this.resetLastSelected = () => { + lastSelected = void 0; + }; + + this.selectAllSearch = e => { + e.target.select(); + }; + this.updateSearch = e => { + this.searchQuery = e.target.value.trim().toLowerCase(); + }; diff --git a/src/riotTags/editors/room-editor/room-entities-properties.tag b/src/riotTags/editors/room-editor/room-entities-properties.tag index 26ed50bd5..6586c4190 100644 --- a/src/riotTags/editors/room-editor/room-entities-properties.tag +++ b/src/riotTags/editors/room-editor/room-entities-properties.tag @@ -27,7 +27,7 @@ room-entities-properties type="number" oninput="{wireAndApply('this.changes.basic.' + prop.key + '.x')}" onchange="{memorizeChanges}" - value="{changes.basic[prop.key] && changes.basic[prop.key].x}" + value="{String(changes.basic[prop.key] && changes.basic[prop.key].x)}" placeholder="{String(changes.basic[prop.key] && changes.basic[prop.key].x)}" step="{prop.step}" ) @@ -38,7 +38,7 @@ room-entities-properties type="number" oninput="{wireAndApply('this.changes.basic.' + prop.key + '.y')}" onchange="{memorizeChanges}" - value="{changes.basic[prop.key] && changes.basic[prop.key].y}" + value="{String(changes.basic[prop.key] && changes.basic[prop.key].y)}" placeholder="{String(changes.basic[prop.key] && changes.basic[prop.key].y)}" step="{prop.step}" ) @@ -189,6 +189,7 @@ room-entities-properties Magic.prototype.toNumber = () => 0; this.multipleType = new Magic(); + // Skips first apply of properties, as there will be no changes this.firstRun = true; /** * Rescans the list of selected items and forms a list of matching and different properties. diff --git a/src/riotTags/editors/room-editor/room-events-editor.tag b/src/riotTags/editors/room-editor/room-events-editor.tag index ef53966cf..80cfdb659 100644 --- a/src/riotTags/editors/room-editor/room-events-editor.tag +++ b/src/riotTags/editors/room-editor/room-events-editor.tag @@ -33,7 +33,7 @@ room-events-editor.aDimmer.relative.pad.fadein(onclick="{tryClose}") this.focusEditor = (tab) => { if (tab?.uid === this.room.uid) { - this.refs.codeeditor.codeEditor.focus(); + this.refs.codeeditor.codeEditor?.focus(); } }; window.signals.on('globalTabChanged', this.focusEditor); diff --git a/src/riotTags/editors/room-editor/room-tile-editor.tag b/src/riotTags/editors/room-editor/room-tile-editor.tag index 915235dd9..4e2878e65 100644 --- a/src/riotTags/editors/room-editor/room-tile-editor.tag +++ b/src/riotTags/editors/room-editor/room-tile-editor.tag @@ -113,9 +113,10 @@ room-tile-editor.room-editor-Tiles.flexfix(class="{opts.class}") .cancelBtn(this.vocGlob.cancel) .prompt(this.vocFull.roomView.newDepth) .then(e => { - if (e.inputValue && Number(e.inputValue)) { + const n = Number(e.inputValue); + if (isFinite(n)) { const before = layer.zIndex; - layer.zIndex = Number(e.inputValue); + layer.zIndex = n; this.opts.layers.sort((a, b) => b.zIndex - a.zIndex); this.opts.pixieditor.history.pushChange({ type: 'propChange', @@ -133,9 +134,10 @@ room-tile-editor.room-editor-Tiles.flexfix(class="{opts.class}") .defaultValue(-10) .prompt(this.vocFull.roomView.newDepth) .then(e => { - if (e.inputValue && Number(e.inputValue)) { + const n = Number(e.inputValue); + if (isFinite(n)) { const layer = { - depth: Number(e.inputValue), + depth: n, tiles: [], extends: {}, cache: true diff --git a/src/riotTags/main-menu/license-panel.tag b/src/riotTags/main-menu/license-panel.tag index e28748938..f95504228 100644 --- a/src/riotTags/main-menu/license-panel.tag +++ b/src/riotTags/main-menu/license-panel.tag @@ -159,6 +159,55 @@ license-panel CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + + h2 Comic Relief font by Loudifier + pre + code. + Copyright (c) 2013, Jeff Davis (info@loudifier.com), + with Reserved Font Name “Comic Relief”. + + This Font Software is licensed under the SIL Open Font License, Version 1.1. + This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL + + —————————————————————————————- + SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + —————————————————————————————- + + PREAMBLE + The goals of the Open Font License (OFL) are to stimulate worldwide development of collaborative font projects, to support the font creation efforts of academic and linguistic communities, and to provide a free and open framework in which fonts may be shared and improved in partnership with others. + + The OFL allows the licensed fonts to be used, studied, modified and redistributed freely as long as they are not sold by themselves. The fonts, including any derivative works, can be bundled, embedded, redistributed and/or sold with any software provided that any reserved names are not used by derivative works. The fonts and derivatives, however, cannot be released under any other type of license. The requirement for fonts to remain under this license does not apply to any document created using the fonts or their derivatives. + + DEFINITIONS + “Font Software” refers to the set of files released by the Copyright Holder(s) under this license and clearly marked as such. This may include source files, build scripts and documentation. + + “Reserved Font Name” refers to any names specified as such after the copyright statement(s). + + “Original Version” refers to the collection of Font Software components as distributed by the Copyright Holder(s). + + “Modified Version” refers to any derivative made by adding to, deleting, or substituting—in part or in whole—any of the components of the Original Version, by changing formats or by porting the Font Software to a new environment. + + “Author” refers to any designer, engineer, programmer, technical writer or other person who contributed to the Font Software. + + PERMISSION & CONDITIONS + Permission is hereby granted, free of charge, to any person obtaining a copy of the Font Software, to use, study, copy, merge, embed, modify, redistribute, and sell modified and unmodified copies of the Font Software, subject to the following conditions: + + 1) Neither the Font Software nor any of its individual components, in Original or Modified Versions, may be sold by itself. + + 2) Original or Modified Versions of the Font Software may be bundled, redistributed and/or sold with any software, provided that each copy contains the above copyright notice and this license. These can be included either as stand-alone text files, human-readable headers or in the appropriate machine-readable metadata fields within text or binary files as long as those fields can be easily viewed by the user. + + 3) No Modified Version of the Font Software may use the Reserved Font Name(s) unless explicit written permission is granted by the corresponding Copyright Holder. This restriction only applies to the primary font name as presented to the users. + + 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font Software shall not be used to promote, endorse or advertise any Modified Version, except to acknowledge the contribution(s) of the Copyright Holder(s) and the Author(s) or with their explicit written permission. + + 5) The Font Software, modified or unmodified, in part or in whole, must be distributed entirely under this license, and must not be distributed under any other license. The requirement for fonts to remain under this license does not apply to any document created using the Font Software. + + TERMINATION + This license becomes null and void if any of the above conditions are not met. + + DISCLAIMER + THE FONT SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM OTHER DEALINGS IN THE FONT SOFTWARE. + h2 DragonBonesJS pre code. @@ -257,6 +306,105 @@ license-panel LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + h2 Open Dyslexic font by Abbie Gonzalez + pre + code. + Copyright (c) 2019-07-29, Abbie Gonzalez (https://abbiecod.es|support@abbiecod.es), + with Reserved Font Name OpenDyslexic. + Copyright (c) 12/2012 - 2019 + This Font Software is licensed under the SIL Open Font License, Version 1.1. + This license is copied below, and is also available with a FAQ at: + http://scripts.sil.org/OFL + + + ----------------------------------------------------------- + SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 + ----------------------------------------------------------- + + PREAMBLE + The goals of the Open Font License (OFL) are to stimulate worldwide + development of collaborative font projects, to support the font creation + efforts of academic and linguistic communities, and to provide a free and + open framework in which fonts may be shared and improved in partnership + with others. + + The OFL allows the licensed fonts to be used, studied, modified and + redistributed freely as long as they are not sold by themselves. The + fonts, including any derivative works, can be bundled, embedded, + redistributed and/or sold with any software provided that any reserved + names are not used by derivative works. The fonts and derivatives, + however, cannot be released under any other type of license. The + requirement for fonts to remain under this license does not apply + to any document created using the fonts or their derivatives. + + DEFINITIONS + "Font Software" refers to the set of files released by the Copyright + Holder(s) under this license and clearly marked as such. This may + include source files, build scripts and documentation. + + "Reserved Font Name" refers to any names specified as such after the + copyright statement(s). + + "Original Version" refers to the collection of Font Software components as + distributed by the Copyright Holder(s). + + "Modified Version" refers to any derivative made by adding to, deleting, + or substituting -- in part or in whole -- any of the components of the + Original Version, by changing formats or by porting the Font Software to a + new environment. + + "Author" refers to any designer, engineer, programmer, technical + writer or other person who contributed to the Font Software. + + PERMISSION & CONDITIONS + Permission is hereby granted, free of charge, to any person obtaining + a copy of the Font Software, to use, study, copy, merge, embed, modify, + redistribute, and sell modified and unmodified copies of the Font + Software, subject to the following conditions: + + 1) Neither the Font Software nor any of its individual components, + in Original or Modified Versions, may be sold by itself. + + 2) Original or Modified Versions of the Font Software may be bundled, + redistributed and/or sold with any software, provided that each copy + contains the above copyright notice and this license. These can be + included either as stand-alone text files, human-readable headers or + in the appropriate machine-readable metadata fields within text or + binary files as long as those fields can be easily viewed by the user. + + 3) No Modified Version of the Font Software may use the Reserved Font + Name(s) unless explicit written permission is granted by the corresponding + Copyright Holder. This restriction only applies to the primary font name as + presented to the users. + + 4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font + Software shall not be used to promote, endorse or advertise any + Modified Version, except to acknowledge the contribution(s) of the + Copyright Holder(s) and the Author(s) or with their explicit written + permission. + + 5) The Font Software, modified or unmodified, in part or in whole, + must be distributed entirely under this license, and must not be + distributed under any other license. The requirement for fonts to + remain under this license does not apply to any document created + using the Font Software. + + TERMINATION + This license becomes null and void if any of the above conditions are + not met. + + DISCLAIMER + THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF + MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT + OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE + COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL + DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM + OTHER DEALINGS IN THE FONT SOFTWARE. + + h2 Open Sans font by Steve Matteson p Released under #[a(href="http://www.apache.org/licenses/LICENSE-2.0") Apache License 2.0]. diff --git a/src/riotTags/main-menu/main-menu-settings.tag b/src/riotTags/main-menu/main-menu-settings.tag index 697853d7e..d8b810fb5 100644 --- a/src/riotTags/main-menu/main-menu-settings.tag +++ b/src/riotTags/main-menu/main-menu-settings.tag @@ -13,6 +13,10 @@ main-menu-settings svg.feather use(xlink:href="#font") span {voc.codeFont} + li(onclick="{toggleUiFontSelector}") + svg.feather + use(xlink:href="#special-fonts") + span {voc.specialFont} li(onclick="{togglePrideMode}") svg.feather use(xlink:href="#{localStorage.prideMode === 'on' ? 'check-square' : 'square'}") @@ -69,6 +73,7 @@ main-menu-settings context-menu(menu="{themesSubmenu}" ref="themeslist") context-menu(menu="{languagesSubmenu}" ref="languageslist") context-menu(menu="{codeFontSubmenu}" ref="codesettings") + context-menu(menu="{specialFontSubmenu}" ref="specialfontsettings") script. this.namespace = 'mainMenu.settings'; this.mixin(require('src/node_requires/riotMixins/voc').default); @@ -269,6 +274,31 @@ main-menu-settings }] }; + this.specialFontSubmenu = { + items: [{ + label: this.vocFull.mainMenu.settings.specialFontDefault, + icon: () => !localStorage.specialFont && 'check', + click: () => { + localStorage.specialFont = ''; + window.signals.trigger('specialFontUpdated'); + } + }, { + label: 'Comic Relief', + icon: () => localStorage.specialFont === '"Comic Relief", cursive' && 'check', + click: () => { + localStorage.specialFont = '"Comic Relief", cursive'; + window.signals.trigger('specialFontUpdated'); + } + }, { + label: 'Open Dyslexic', + icon: () => localStorage.specialFont === '"Open Dyslexic", cursive' && 'check', + click: () => { + localStorage.specialFont = '"Open Dyslexic", cursive'; + window.signals.trigger('specialFontUpdated'); + } + }] + }; + this.toggleThemeSelector = e => { this.refs.themeslist.popup(e.clientX, e.clientY); }; @@ -278,3 +308,6 @@ main-menu-settings this.toggleCodeFontSelector = e => { this.refs.codesettings.popup(e.clientX, e.clientY); }; + this.toggleUiFontSelector = e => { + this.refs.specialfontsettings.popup(e.clientX, e.clientY); + }; diff --git a/src/riotTags/root-tag.tag b/src/riotTags/root-tag.tag index 16fe3bc61..836ca400a 100644 --- a/src/riotTags/root-tag.tag +++ b/src/riotTags/root-tag.tag @@ -41,11 +41,16 @@ root-tag(class="{pride: localStorage.prideMode === 'on'}") .monaco-editor .aView-lines.aView-lines { line-height: ${localStorage.codeDense === 'on' ? 1.5 : 1.75}; } + body { + ${localStorage.specialFont ? 'font-family: ' + localStorage.specialFont + ';' : ''} + ${localStorage.specialFont ? '--ui-font: ' + localStorage.specialFont + ';' : ''} + } `; stylesheet.innerHTML = css; }; updateStylesheet(); window.signals.on('codeFontUpdated', updateStylesheet); + window.signals.on('specialFontUpdated', updateStylesheet); window.signals.on('prideModeUpdated', () => { this.update(); }); diff --git a/src/riotTags/shared/asset-browser.tag b/src/riotTags/shared/asset-browser.tag index c7e1b3894..3b90fc6df 100644 --- a/src/riotTags/shared/asset-browser.tag +++ b/src/riotTags/shared/asset-browser.tag @@ -613,8 +613,13 @@ asset-browser.flexfix(class="{opts.namespace} {opts.class} {compact: opts.compac icon: 'trash', label: this.vocGlob.delete, click: async () => { - const names = [...this.selectedItems] - .map(asset => asset.name).join(', '); + let names = [...this.selectedItems] + .slice(0, Math.min(this.selectedItems.size, 10)) + .map(asset => asset.name) + .join(', '); + if (this.selectedItems.size > 10) { + names += this.vocGlob.andNMore.replace('{0}', this.selectedItems.size - 10); + } const reply = await alertify .okBtn(this.vocGlob.delete) .cancelBtn(this.vocGlob.cancel) diff --git a/src/styl/3rdParty/alertify.styl b/src/styl/3rdParty/alertify.styl index b7a7c6956..21a114539 100644 --- a/src/styl/3rdParty/alertify.styl +++ b/src/styl/3rdParty/alertify.styl @@ -115,3 +115,13 @@ max-height 1000px padding 12px pointer-events auto + +// Fix modals' contents overflowing the window if there is too much content there +.alertify .dialog + & > div + max-height 90vh + display flex + flex-flow column nowrap + .msg + overflow auto + flex 1 1 auto diff --git a/src/styl/3rdParty/fonts.styl b/src/styl/3rdParty/fonts.styl index dea63fb6a..b2686bea5 100644 --- a/src/styl/3rdParty/fonts.styl +++ b/src/styl/3rdParty/fonts.styl @@ -4,6 +4,33 @@ font-weight 400 font-style normal +@font-face + font-family 'Comic Relief' + src url('../data/fonts/ComicRelief.ttf') + font-weight 400 + font-style normal + +@font-face + font-family 'Open Dyslexic' + src url('../data/fonts/OpenDyslexic-Bold-Italic.woff2') + font-weight 600 + font-style italic +@font-face + font-family 'Open Dyslexic' + src url('../data/fonts/OpenDyslexic-Bold.woff2') + font-weight 400 + font-style italic +@font-face + font-family 'Open Dyslexic' + src url('../data/fonts/OpenDyslexic-Italic.woff2') + font-weight 600 + font-style normal +@font-face + font-family 'Open Dyslexic' + src url('../data/fonts/OpenDyslexic-Regular.woff2') + font-weight 400 + font-style normal + @css { @font-face { font-family: 'Open Sans'; diff --git a/src/styl/3rdParty/monacoEdits.styl b/src/styl/3rdParty/monacoEdits.styl index 637080713..3488262cc 100644 --- a/src/styl/3rdParty/monacoEdits.styl +++ b/src/styl/3rdParty/monacoEdits.styl @@ -2,6 +2,7 @@ border-radius br .monaco-editor font-family fonts + font-family var(--ui-font) .button padding unset margin unset @@ -35,4 +36,4 @@ margin-left 0 margin-right 0 .action-menu-item - height 2.5em \ No newline at end of file + height 2.5em diff --git a/src/styl/common.styl b/src/styl/common.styl index 06ce28583..accd40f65 100644 --- a/src/styl/common.styl +++ b/src/styl/common.styl @@ -8,6 +8,7 @@ html, body height 100% body font 16px/32px fonts + font 16px/32px var(--ui-font) color text background background cursor default diff --git a/src/styl/tags/editors/room-editor/room-editor.styl b/src/styl/tags/editors/room-editor/room-editor.styl index 3425abd9d..8ce6b7785 100644 --- a/src/styl/tags/editors/room-editor/room-editor.styl +++ b/src/styl/tags/editors/room-editor/room-editor.styl @@ -22,7 +22,7 @@ room-editor overflow hidden padding 1rem display grid - grid-template-columns auto auto 1fr + grid-template-columns auto auto 1fr auto auto grid-gap 0 1rem align-items start .&-aToolbar @@ -31,6 +31,8 @@ room-editor {shad} button border-color borderBright + &.last + grid-column 5 .&-aContextPanel @extends .aPanel display block diff --git a/src/styl/tags/editors/room-editor/room-entities-list.styl b/src/styl/tags/editors/room-editor/room-entities-list.styl new file mode 100644 index 000000000..c6a0d89d7 --- /dev/null +++ b/src/styl/tags/editors/room-editor/room-entities-list.styl @@ -0,0 +1,5 @@ +room-entities-list + .&-aThumbnail + width 1.5rem + vertical-align text-bottom + margin-right 0.5ch diff --git a/src/styl/tags/home-news.styl b/src/styl/tags/home-news.styl index 811623d0a..4d904b68d 100644 --- a/src/styl/tags/home-news.styl +++ b/src/styl/tags/home-news.styl @@ -36,6 +36,7 @@ home-news max-height 15rem button, span font-family fonts + font-family var(--ui-font) h3 margin-bottom 0.25rem p, .dim diff --git a/src/styl/themeDay.styl b/src/styl/themeDay.styl index 2e81f514e..58b3a65ce 100644 --- a/src/styl/themeDay.styl +++ b/src/styl/themeDay.styl @@ -17,6 +17,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeForest.styl b/src/styl/themeForest.styl index 9a0f8dd22..7b1775a91 100644 --- a/src/styl/themeForest.styl +++ b/src/styl/themeForest.styl @@ -23,6 +23,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0 iconsize = 1.5rem diff --git a/src/styl/themeGhost.styl b/src/styl/themeGhost.styl index 46e2cc5dd..6313aca37 100644 --- a/src/styl/themeGhost.styl +++ b/src/styl/themeGhost.styl @@ -17,6 +17,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.35rem iconsize = 1.5rem diff --git a/src/styl/themeGoldenEye.styl b/src/styl/themeGoldenEye.styl index a6c730fc8..db9910a63 100644 --- a/src/styl/themeGoldenEye.styl +++ b/src/styl/themeGoldenEye.styl @@ -23,6 +23,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 2px iconsize = 1.5rem diff --git a/src/styl/themeHCBlack.styl b/src/styl/themeHCBlack.styl index f02eab81b..a0a91211a 100644 --- a/src/styl/themeHCBlack.styl +++ b/src/styl/themeHCBlack.styl @@ -23,6 +23,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeHorizon.styl b/src/styl/themeHorizon.styl index 7290e48fd..332383549 100644 --- a/src/styl/themeHorizon.styl +++ b/src/styl/themeHorizon.styl @@ -22,6 +22,8 @@ shadamb = /* Fonts */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.25rem iconsize = 1.5rem diff --git a/src/styl/themeLucasDracula.styl b/src/styl/themeLucasDracula.styl index 5d0f29511..4b23467b8 100644 --- a/src/styl/themeLucasDracula.styl +++ b/src/styl/themeLucasDracula.styl @@ -21,6 +21,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeNight.styl b/src/styl/themeNight.styl index 42b2238e2..1ddf6c67b 100644 --- a/src/styl/themeNight.styl +++ b/src/styl/themeNight.styl @@ -23,6 +23,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeNord.styl b/src/styl/themeNord.styl index a340d5825..67dbbab42 100644 --- a/src/styl/themeNord.styl +++ b/src/styl/themeNord.styl @@ -20,6 +20,8 @@ shadamb = fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeOneDarkPro.styl b/src/styl/themeOneDarkPro.styl index 0729a95a0..69c323164 100644 --- a/src/styl/themeOneDarkPro.styl +++ b/src/styl/themeOneDarkPro.styl @@ -20,6 +20,8 @@ shadamb = fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.15rem iconsize = 1.5rem diff --git a/src/styl/themePooxelGreen.styl b/src/styl/themePooxelGreen.styl index de387f08c..1312acad7 100644 --- a/src/styl/themePooxelGreen.styl +++ b/src/styl/themePooxelGreen.styl @@ -14,6 +14,8 @@ /* Base fonts for UI */ fonts = font = 'Pooxel', sans-serif, serif font-mono = mono = Basis, monospace +body + --ui-font fonts br = 0 iconsize = 1.5rem diff --git a/src/styl/themeRosePine.styl b/src/styl/themeRosePine.styl index 87f3b648b..6d68b8904 100644 --- a/src/styl/themeRosePine.styl +++ b/src/styl/themeRosePine.styl @@ -21,6 +21,8 @@ shadamb = fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeRosePineDawn.styl b/src/styl/themeRosePineDawn.styl index 6af23962d..cb6413549 100644 --- a/src/styl/themeRosePineDawn.styl +++ b/src/styl/themeRosePineDawn.styl @@ -21,6 +21,8 @@ shadamb = fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeRosePineMoon.styl b/src/styl/themeRosePineMoon.styl index 3220cd0c2..ae4ec6981 100644 --- a/src/styl/themeRosePineMoon.styl +++ b/src/styl/themeRosePineMoon.styl @@ -21,6 +21,8 @@ shadamb = fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem diff --git a/src/styl/themeSpringStream.styl b/src/styl/themeSpringStream.styl index 3b84ac747..da955d358 100644 --- a/src/styl/themeSpringStream.styl +++ b/src/styl/themeSpringStream.styl @@ -44,6 +44,8 @@ shadamb = fonts = font = 'Open Sans', sans-serif, serif fontsHeaders = fontHeader = Comfortaa, 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.35rem iconsize = 1.5rem diff --git a/src/styl/themeSynthwave.styl b/src/styl/themeSynthwave.styl index 204a9a177..2a05883a4 100644 --- a/src/styl/themeSynthwave.styl +++ b/src/styl/themeSynthwave.styl @@ -21,6 +21,8 @@ shadamb = /* Base fonts for UI */ fonts = font = 'Open Sans', sans-serif, serif font-mono = mono = Iosevka, monospace +body + --ui-font fonts br = 0.2rem iconsize = 1.5rem