From 284b0b3ce26c97a3276f47fd38cbad226afd8418 Mon Sep 17 00:00:00 2001 From: leonardofelin <33718368+leonardofelin@users.noreply.github.com> Date: Tue, 14 May 2024 08:25:51 -0300 Subject: [PATCH 01/42] Update pt.json - Brazilian Portuguese translations (#752) * Update pt.json Update Brazilian Portuguese translations: - Updated Modules - Engineering & Experimental Effect - Corrections * Update Portuguese Brazilian Fixed Tab/Spaces indentation --- src/app/i18n/pt.json | 240 +++++++++++++++++++++++++++++++++---------- 1 file changed, 185 insertions(+), 55 deletions(-) diff --git a/src/app/i18n/pt.json b/src/app/i18n/pt.json index c5a87056..464dfe14 100644 --- a/src/app/i18n/pt.json +++ b/src/app/i18n/pt.json @@ -33,25 +33,25 @@ "PHRASE_TIME_TO_RECHARGE_SHIELDS": "Escudos vão recarregar em", "PHRASE_SHIELD_SOURCES": "Detalhamento do fornecimento de energia para o escudo", "PHRASE_EFFECTIVE_SHIELD": "Eficácia do escudo contra diferentes tipos de dano", - "PHRASE_ARMOUR_SOURCES": "Detalhamento do suprimento de armadura", - "PHRASE_EFFECTIVE_ARMOUR": "Eficácia da força da armadura contra diferentes tipos de dano", + "PHRASE_ARMOUR_SOURCES": "Detalhamento do suprimento de blindagem", + "PHRASE_EFFECTIVE_ARMOUR": "Eficácia da força da blindagem contra diferentes tipos de dano", "PHRASE_DAMAGE_TAKEN": "% de dano total tomado por diferentes tipos de dano", "PHRASE_TIME_TO_LOSE_ARMOUR": "Armadura vai aguentar por", "PHRASE_MODULE_PROTECTION_EXTERNAL": "Proteção para os encaixes", "PHRASE_MODULE_PROTECTION_INTERNAL": "Proteção para todos os outros módulos", "PHRASE_OVERALL_DAMAGE": "Detalhamento das fontes para DPS sustentado", "PHRASE_SHIELD_DAMAGE": "Detalhamento das fontes para DPS sustentado contra escudos", - "PHRASE_ARMOUR_DAMAGE": "Detalhamento das fontes para DPS sustentado contra armadura", + "PHRASE_ARMOUR_DAMAGE": "Detalhamento das fontes para DPS sustentado contra blindagem", "PHRASE_TIME_TO_REMOVE_SHIELDS": "Removerá escudos em", "PHRASE_MULTI_CREW_CAPACITOR_POINTS": "Clique com o botão direito para assignar pontos de capacitor de multi-crew.", "TT_TIME_TO_REMOVE_SHIELDS": "Com fogo continuo por todas as armas", - "PHRASE_TIME_TO_REMOVE_ARMOUR": "Removerá armadura em", + "PHRASE_TIME_TO_REMOVE_ARMOUR": "Removerá blindagem em", "TT_TIME_TO_REMOVE_ARMOUR": "Com fogo continuo por todas as armas", "PHRASE_TIME_TO_DRAIN_WEP": "Vai drenar o ARM em", "TT_TIME_TO_DRAIN_WEP": "Tempo para drenar o capacitor ARM com todas as armas disparando", "TT_TIME_TO_LOSE_SHIELDS": "Contra o fogo continuo das armas de todos os adversários", "TT_TIME_TO_LOSE_ARMOUR": "Contra o fogo contínuo das armas de todos os adversários", - "TT_MODULE_ARMOUR": "Proteção da armadura contra danos no módulo", + "TT_MODULE_ARMOUR": "Proteção da blindagem contra danos no módulo", "TT_MODULE_PROTECTION_EXTERNAL": "Porcentagem de danos desviados dos encaixes para o reforço de módulos", "TT_MODULE_PROTECTION_INTERNAL": "Porcentagem de danos desviados de módulos que não são encaixes para o reforço de módulos", "TT_EFFECTIVE_SDPS_SHIELDS": "DPS contínuo real enquanto o capacitor ARM não está vazio", @@ -59,7 +59,7 @@ "TT_EFFECTIVE_SDPS_ARMOUR": "DPS contínuo real enquanto o capacitor ARM não está vazio", "TT_EFFECTIVENESS_ARMOUR": "Eficácia em comparação com atingir um alvo de 0-resistência a 0m", "PHRASE_EFFECTIVE_SDPS_SHIELDS": "DSPS contra escudos", - "PHRASE_EFFECTIVE_SDPS_ARMOUR": "DSPS contra armadura", + "PHRASE_EFFECTIVE_SDPS_ARMOUR": "DSPS contra blindagem", "TT_SUMMARY_SPEED": "Com tanque de combustível completo e 4 pips para MTR", "TT_SUMMARY_SPEED_NONFUNCTIONAL": "Propulsores desligados ou acima da massa máxima com combustível e carga completos", "TT_SUMMARY_BOOST": "Com tanque de combustível completo e 4 pips para MTR", @@ -81,12 +81,12 @@ "TT_SUMMARY_UNLADEN_TOTAL_JUMP": "A maior alcance possível sem carga, tanque de combustível completo e saltar o máximo possível a cada vez", "TT_SUMMARY_LADEN_TOTAL_JUMP": "O maior alcance possível com carga total, tanque de combustível completo e saltar o máximo possível a cada vez", "HELP_MODIFICATIONS_MENU": "Clique em um número para inserir um novo valor, ou arraste a barra para pequenas mudanças", - + "core internal": "Internos Principais", "hardpoints": "Encaixes", "optional internal": "Internos Opcionais", "utility mounts": "Encaixe de apoio", - + "ships": "naves", "builds": "builds", "compare": "comparar", @@ -117,14 +117,14 @@ "reset": "limpar", "export": "exportar", "shortlink": "link curto", - + "federation rank required": "Rank mínimo da Federação para comprar", "empire rank required": "Rank mínimo do Império para comprar", "horizons": "Horizons", "horizons required": "Apenas disponível para quem comprou Elite Dangerous: Horizons", "horizons early adoption": "Horizons Early Adopter", "horizons early adoption required": "Disponível apenas para quem comprou Elite Dangerous: Horizons para PC antes de 05-Fev-2016 ou no Xbox One antes de 30-Jul-2016", - + "ship": "Nave", "manufacturer": "Fabricante", "cost": "Custo", @@ -135,7 +135,7 @@ "crew": "Trip", "MLF": "FBM", "mass lock factor": "Fator de bloqueio de massa", - "agility": "Manobrabilidade", + "agility": "Agilidade", "hrd": "DRZ", "hardness": "Dureza do Casco", "hull hardness": "Dureza do Casco", @@ -143,15 +143,26 @@ "base": "Base", "speed": "Velocidade", "boost": "impulso", - "armour": "Armadura", + "armour": "Blindagem", "max": "max", "jump": "Salto", "cargo": "Carga", - "pax": "Pax", - "fuel": "Combustível", + "pax": "Pasg", + "fuel": "Combustível", "passenger capacity": "Capacidade de passageiros", "core module classes": "Internos Principais", + "PP": "GE", + "TH": "PR", + "FSD": "MDD", + "LS": "SV", + "PD": "DE", + "S": "SE", + "FT": "TC", "internal compartments": "Internos Opcionais", + "S": "P", + "M": "M", + "L": "G", + "H": "E", "jump range": "alcance salto", "unladen": "vazia", "laden": "cheia", @@ -162,22 +173,22 @@ "integrity": "Integridade", "mass": "Massa", "boost interval": "Intervalo impulso", - "resting heat (Beta)": "Temp. Repouso", + "resting heat (Beta)": "Temperat. Repouso", "No Shield": "Sem Escudo", "resistance": "Resistências", - "HP": "HP", - "absolute": "absoluto", - "explosive": "explosivo", - "kinetic": "cinético", - "thermal": "térmico", - "caustic": "cáustico", + "HP": "Pontos de Vida", + "absolute": "absoluta", + "explosive": "explosiva", + "kinetic": "cinética", + "thermal": "térmica", + "caustic": "cáustica", "recovery": "recuperação", "recharge": "recarga", - "raw module armour": "armadura de módulo", + "raw module armour": "blindagem de módulo", "internal protection": "Proteção interna", "external protection": "Proteção externa", - "Group highlighted ships": "Agrupar naves selecionadas", - + "Group highlighted ships": "Agrupar naves selecionadas", + "ship control": "controle da nave", "SYS": "SIS", "WEP": "ARM", @@ -188,7 +199,7 @@ "opponent": "oponente", "stock": "padrão", "engagement range": "distância de combate", - + "power and costs": "energia e custos", "module": "módulo", "type": "tipo", @@ -213,7 +224,7 @@ "unit cost": "unitário", "subtotal": "subtotal", "limpets": "drones", - + "profiles": "perfis", "engine profile": "perfil do motor", "fsd profile": "perfil do mdd", @@ -223,16 +234,16 @@ "maximum speed": "velocidade máxima", "maximum range": "alcance máximo", "sdps": "Dano sustentado por segundo", - + "offence": "ataque", "weapon": "arma", "overall": "geral", "opponent's shields": "escudo do oponente", - "opponent's armour": "armadura do oponente", + "opponent's armour": "blindagem do oponente", "offence metrics": "métricas de ataque", "overall damage": "dano geral", "shield damage sources": "fontes de dano no escudo", - "armour damage sources": "fontes de dano na armadura", + "armour damage sources": "fontes de dano na blindagem", "tab_defence": "defesa", "shield metrics": "métricas do escudo", "raw shield strength": "força do escudo total", @@ -244,15 +255,17 @@ "shield addition": "reforços", "damage taken": "dano recebido", "effective shield": "escudo efetivo", - "armour metrics": "métricas da armadura", - "raw armour strength": "força da armadura total", - "armour sources": "fontes da armadura", + "armour metrics": "métricas da blindagem", + "raw armour strength": "força da blindagem total", + "armour sources": "fontes da blindagem", "bulkheads": "fuselagem", "reinforcement": "reforços", - "effective armour": "armadura efetiva", + "effective armour": "blindagem efetiva", "ever": "sempre", + "Ever": "Sempre", "never": "nunca", - + "Never": "Nunca", + "empty": "Vazio", "emptyrestricted": "vazio (restrito)", "empty all": "Limpar todos", @@ -306,7 +319,7 @@ "protection": "proteção", "hacktime": "tempo de hackeamento", "jump addition": "Adição de Salto", - + "dps": "Dano por segundo", "dpssdps": "Dano por segundo (dano sustentado por segundo)", "shotdmg": "Dano por tiro", @@ -321,12 +334,12 @@ "shotspeed": "velocidade", "piercing": "penetração", "jitter": "tremida", - + "scanrate": "taxa", "scantime": "tempo", "scan range": "alcance", "max angle": "angulo max", - + "modifications": "modificações", "feature": "característica", "current": "atual", @@ -361,7 +374,7 @@ "thermload": "carga térmica", "proberadius": "raio da sonda", "shieldboost": "melhoria de escudo", - + "comparison": "comparação", "added": "adicionado", "ok": "ok", @@ -407,8 +420,8 @@ "PHRASE_UPLOAD_ORBIS": "Enviar para orbis.zone (Período trial.)", "orbis username": "Usuário/email para orbis.zone", "orbis password": "Senha para orbis.zone", - "HELP_TEXT": "\n

Introdução

\nCoriolis é um construtor de naves para Elite: Dangerous. Este arquivo de ajuda fornece as informações que você precisa para usar o Coriolis.\n\n

Importando sua nave para Coriolis

\nMuitas vezes, você quer começar com o sua nave já existente no Coriolis e ver como mudanças específicas podem afetá-la, por exemplo, melhorar o seu MDD. Há uma série de ferramentas que podem ser usadas para importar sua nave sem que você tenha que criá-la manualmente. Isso tem o benefício adicional de copiar sobre quaisquer modificações dos engenheiros que também tenham sido realizados.

\n\n

Importando sua nave do EDDI

\nPara importar a sua nave do EDDI, primeiro certifique-se de que sua conexão com o servidores da Frontier' companion API está funcionando. Para fazer isso, marque a aba 'Companion App' onde você deve ver \"Sua conexão com o companion app está funcionando\". Caso contrário, siga as instruções na aba companion app no EDDI para se conectar aos servidores da Frontier.

\n\nUna vez que você tenha a conexão com o companion API funcionando, vá para a aba 'Estaleiro'. No lado direito de cada nave tem um botão 'Exportar para o Coriolis' que abrirá seu navegador web padrão em Coriolis com a configuração da nave.

\n\nObserve que o Internet Explorer e Edge podem não importar corretamente, devido às restrições do comprimento do URL. Se você achar que este é o caso, então, altere seu navegador padrão para o Chrome.

\n\nAlém disso, as informações importadas não fornecem dados sobre as prioridades de energia ou o status de habilitação de seu compartimento de carga. O Coriolis define este item para ter prioridade de energia \"5\" e está por padrão desativado. Você pode alterar isso após a importação na seção Gerenciamento de Energia.

\n\n

Importando Sua Nave do EDMC

\nPara importar sua nave do EDMC uma vez que sua conexão com o servidor companion API da Frontier esteja funcionando, vá para 'Opções ->Configurações' e selecione o 'Estaleiro Preferido' para 'Coriolis'. Uma vez que isso esteja configurado, clicando em sua nave na janela principal, abrirá seu navegador web padrão no Coriolis com a configuração da sua nave.

\n\nObserve que o Internet Explorer e Edge podem não importar corretamente, devido às restrições do comprimento do URL. Se você achar que este é o caso, então, altere seu navegador padrão para o Chrome.

\n\n

Compreender e Usar Os Painéis de Equipamentos

\nA página de equipamentos é onde você vai passar a maior parte do tempo, e contém as informações para a sua nave. As informações em cada um dos painéis serão fornecidas abaixo.

\n\n

Valores-Chave

\nAo longo da parte superior da tela estão alguns dos valores-chave da sua configuração. Esta é uma referência prática para os valores, mas será fornecida mais informação para os valores nos outros painéis.

\n\nAqui, juntamente com a maioria dos lugares no Coriolis, acrônimos terão dicas de ferramentas, explicando o que elas significam. Passe o mouse sobre o acrônimo para obter mais detalhes ou veja no glossário no final desta ajuda.

\n\nTodos os valores serão o mais alto possível, assumindo que você tenha uma configuração ideal para esse valor particular (pips máximos em MTR para velocidade, combustível mínimo para alcance de salto, etc.). Isso significa que esses valores não serão afetados por alterações nas configurações de pip. Detalhes da configuração específica para cada valor serão listados na dica de ferramenta associado.

\n\n

Módulos

\nO próximo conjunto de painéis dispostos horizontalmente na tela contém os módulos que você colocou na sua configuração. Da esquerda para a direita, estes são os módulos principais, os módulos internos, os encaixes e os encaixe de apoio. Estes representam os espaços disponíveis em sua nave e não podem ser alterados. Cada espaço tem uma classe ou tamanho e em geral, qualquer módulo até um tamanho determinado pode caber em um determinado espaço (com exceção de fuselagem, suporte vital e sensores em módulos principais e espaços internos restritos, que só podem levar um subconjunto de módulo dependendo de suas restrições).

\n\nPara adicionar um módulo a um espaço, clique com o botão esquerdo no espaço e selecione o módulo desejado. Somente os módulos capazes de encaixar no espaço selecionado serão mostrados.

\n\nPara remover um módulo de um espaço, clique com o botão direito do mouse no módulo.

\n\nPara mover um módulo de um espaço para outro, arraste-o. Se você deseja copiar o módulo, arraste-o enquanto mantém pressionada a tecla 'Alt'.

\n\nAo clicar no cabeçalho de cada conjunto de módulos, você pode selecionar uma função geral para a sua nave (ao clicar no cabeçalho dos Internos Principais) ou num módulo específico com o qual você deseja preencher todos os espaços aplicáveis (ao clicar nos outros cabeçalhos).

\n\n

Controles da Nave

\nOs controles da nave permitem que você defina seus pips, impulsor e quantidade de combustível e carga que sua configuração carrega. As mudanças feitas aqui afetarão as informações fornecidas nos painéis subsequentes, o que lhe dará uma visão mais clara do efeito diferente que esses itens terão.

\n\nAs configurações de controle da nave são salvas como parte da configuração.

\n\n

Adversário

\nA seleção adversário permite que você escolha sua nave oponente. O adversário pode ser uma compilação de padrão de uma nave ou uma das suas construções salvas. Você também pode definir o intervalo de engajamento entre você e seu oponente. Sua seleção aqui afetará as informações fornecidas nos painéis subsequentes, especificamente os painéis de Ataque e Defesa.

\n\nAs configurações do adversário são salvas como parte de uma compilação.

\n\n

Sub-painéis de Energia e Custos

\n

Energia

\nO painel de gerenciamento de energia fornece informações sobre o uso de energia e as prioridades. Permite habilitar e desabilitar módulos individuais, bem como definir prioridades de energia para cada módulo. Os módulos desativados não serão incluídos nas estatísticas da configuração, com exceção dos Bancos de Célula de Escudo, pois geralmente são desativados quando não estão em uso e habilitados somente quando necessário.

\n\n

Custos

\nO painel de custos fornece informações sobre os custos para cada um de seus módulos e o custo total e seguro para sua configuração. Por definição, o Coriolis usa os custos padrão, porém os descontos para a sua nave, módulos e seguros podem ser alterados em 'Opções' no canto superior direito da página.

\n\nOs custos de reequipamento fornecem informações sobre os custos da alteração da configuração base para a sua nave, ou a sua configuração salva, para a configuração atual.

\n\nOs custos de recarga fornecem informações sobre os custos de recarregar a sua configuração atual.

\n\n

Perfis

\nOs perfis fornecem gráficos que mostram o desempenho geral dos módulos em sua compilação\n\n

Perfil do Motor

\nO painel do perfil do motor fornece informações sobre as capacidades dos seus propulsores atuais. O gráfico mostra como a velocidade máxima se altera com a massa total da sua configuração. A linha tracejada vertical no gráfico mostra sua massa atual. O perfil do seu motor pode ser alterado pela obtenção de propulsores diferentes ou engenharia de seus propulsores existentes e você pode aumentar sua velocidade máxima adicionando pips ao capacitor MTR, reduzir a quantidade de combustível e carga que está transportando ou reduzir o peso total da configuração. Você também pode aumentar sua velocidade temporariamente apertando o botão do impulsor.

\n\n

Perfil do MDD

\nO painel de perfil do MDD fornece informações sobre os recursos de sua unidade de mudança de quadro atual. O gráfico mostra como o alcance do salto máximo se altera com a massa total da sua configuração. A linha tracejada vertical no gráfico mostra seu alcance de salto único máximo atual. Seu perfil MDD pode ser alterado pela obtenção de um MDD diferente ou pela engenharia de seu MDD existente, e você pode aumentar o seu alcance de salto máximo, reduzindo a quantidade de combustível e carga que está transportando ou reduzindo o peso total da configuração,

\n\n

Perfil de Manobrabilidade

\nO painel de perfil de Manobrabilidade fornece informações sobre os recursos de seus propulsores atuais com sua massa global atual e as configurações de pips do MTR. O diagrama mostra sua capacidade de mover e girar nos diferentes eixos:\n\n
\n
Velocidade
O mais rápido que a nave pode mover, em metros por segundo
\n
Inclinação
O mais rápido que a nave pode elevar ou baixar o nariz, em graus por segundo
\n
Rolamento
O mais mais rápido a nave pode rotacionar, em graus por segundo
\n
Guinada
O mais rápido qua a nave pode virar o nariz para a esquerda ou para a direita, em graus por segundo
\n
\n\nO seu perfil de manobrabilidade pode ser alterado através da obtenção de propulsores diferentes ou engenharia de seus propulsores existentes, e você pode aumentar seus valores de movimento adicionando pips ao capacitor MTR, além de reduzir a quantidade de combustível e carga que está transportando ou reduzir o peso total da configuração. Você também pode aumentar seu perfil de movimento temporariamente apertando o botão do impulsor.

\n\n

Perfil de Dano

\nO perfil de dano fornece dois gráficos que mostram como o dano da configuração ao escudo do adversário e mudanças no casco com o tempo de engajamento. A linha tracejada vertical no gráfico mostra sua faixa de engajamento atual. Isso combina informações sobre as armas da configuração com os escudos e o casco do adversário para fornecer uma imagem precisa do dano sustentado que pode ser imposto ao oponente.

\n\n

Ataque

\n

Resumo

\nO resumo de ataque fornece informações por arma sobre danos sustentados por segundo imposto ao escudo e casco, juntamente com uma medida de eficácia dessa arma. O valor da eficácia tem uma ferramenta de ajuda que fornece uma discriminação da eficácia e pode incluir reduções ou aumentos devido ao alcance, resistência e distribuidor de energia (para escudos) ou dureza (para o casco). O valor final da eficácia é calculado multiplicando estas porcentagens em conjunto.

\n\n

Valores de Ataque

\nO painel de valores de ataque fornece informações sobre seu poder de causar dano.

\n\nO tempo de drenagem é uma medida de quão rápido o seu capacitor ARM drenará quando disparar todas as armas. É afetado pelo número de pips que você possui em seu capacitor ARM, com mais pips resultando em uma taxa de recarga mais alta das armas e portanto, um tempo maior para drenar.

\n\nO próximo valor é o tempo que você levará para remover os escudos do seu oponente. Isso pressupõe que você tenha 100% de tempo no alvo e que sua período de engajamento permaneça constante. Observe que se o seu tempo para derrubar o escudo é maior do que o seu tempo de drenar, isso pressupõe que você continue a disparar do começo ao fim, causando danos menores devido à energia reduzida em seu capacitor ARM.

\n\nO próximo valor é o tempo que você levará para abater a casco do seu oponente. Isso segue a mesma lógica do tempo para remover os escudos.

\n\n

Fontes de Danos ao Escudo

\nAs fontes de danos do escudo fornecem informações sobre as fontes de danos ao seu oponente por tipo de dano. Para cada tipo de dano aplicável (absoluto, explosivo, cinético, térmico) é fornecido um valor de dano sustentado por segundo.

\n\n

Fontes de Danos ao Casco

\nAs fontes de danos no casco fornecem informações sobre as fontes de danos ao seu oponente por tipo de dano. Para cada tipo de dano aplicável (explosivo absoluto, cinético, térmico) é fornecido um valor de dano sustentado por segundo.

\n\n

Defesas

\n

Valores do Escudo

\nOs valores do escudo fornecem informações sobre o potencial defensivo do escudo.

\n\nA força do escudo total é a soma do seu gerador de escudo, potenciadores e banco de célula de escudo. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nO tempo em que os escudos resistirão é o tempo que levará ao seu oponente para derrubar seu escudo. Isso pressupõe que eles atirem em 100% do tempo no alvo e que o período de engajamento permanece constante. Também pressupõe que você use todos os seus banco de célula de escudo antes que os seus escudos caiam.

\n\nO tempo de recuperação do escudo é o tempo que levará o seu escudo a partir do colapso (0%) até recuperar (50%). Isso é afetado pelo número de pips que você possui no seu capacitor SIS.

\n\nO tempo de recarga do escudo é o tempo que levará o seu escudo a serem recuperados (50%) até a carga completa (100%). Isso é afetado pelo número de pips que você possui no seu capacitor SIS.

\n\nFontes do Escudo\nEste gráfico fornece informações sobre as fontes dos seus escudos. Para cada fonte aplicável de escudos (gerador, potenciadores, bancos de célula de escudo) é fornecido um valor.

\n\nDano Recebido\nEste gráfico mostra como o dano inicial das armas de cada tipo é reduzido antes do seu dano ser aplicado aos escudos. Para cada tipo de dano (absoluto, explosivo, cinético, térmico) é proporcionada uma porcentagem do dano inicial. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nEficiência do Escudo\nEste gráfico mostra a eficiência do escudo para cada tipo de dano, dividindo a capacidade total do escudo pelo dano recebido por tipo.

\n\n

Valores da Armadura

\nOs valores da armadura fornecem informações sobre sua defesa da armadura.

\n\nA força total da armadura é a soma da armadura de sua fuselagem e dos reforços de casco. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nO tempo que a armadura irá resistir é o tempo que levará ao seu oponente à levar sua armadura a 0. Isso pressupõe que eles atirem em 100% do tempo no alvo e o tempo de engajamento permaneça constante e que todo o dano é dado na armadura ao invés dos módulos.

\n\nA armadura total do módulo é a soma da proteção dos reforços de módulo.

\n\nA proteção para os encaixes é a quantidade de proteção que seus reforços de módulos fornecem aos encaixes. Esta porcentagem de danos aos encaixes será desviada para os reforços de módulo.

\n\nA proteção para todos os outros módulos é a quantidade de proteção que seus reforços de módulo fornecem para tudo além dos encaixes. Esta porcentagem de danos aos módulos será desviada para os reforços de módulo.

\n\nFontes da Armadura\nEste gráfico fornece informações sobre as fontes da sua armadura. Para cada fonte aplicável de escudos (fuselagem, reforços de casco), é fornecido um valor.

\n\nDano Recebido\nEste gráfico mostra como o dano inicial das armas de cada tipo é reduzido antes do dano ser aplicado à armadura. Para cada tipo de dano (absoluto, explosivo, cinético, térmico) é proporcionada uma porcentagem do dano inicial. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nArmadura Efetiva\nEste gráfico mostra a armadura efetiva para cada tipo de dano encontrado, dividindo o valor da armadura total pelo dano recebido por esse tipo.

\n\n

Atalhos do Teclado

\n
\n
Ctrl-b
ativar impulsor
\n
Ctrl-e
mensagem de exportação aberto (exclusivo página equipamentos)
\n
Ctrl-h
mensagem de ajuda aberto
\n
Ctrl-i
mensagem de importação aberto
\n
Ctrl-o
mensagem de ligação curta aberto
\n
Ctrl-seta-esq
Aumentar capacitor SIS
\n
Ctrl-seta-cima
Aumentar capacitor MTR
\n
Ctrl-seta-dir
Aumentar capacitor SIS
\n
Ctrl-seta-baixo
Distribuidor de energia padrão
\n
Esc
fechar qualquer mensagem aberta
\n
\n

Glossário

\n
\n
Dano absoluto
Um tipo de dano, sem qualquer proteção. O dano absoluto é sempre distribuído a 100%, independentemente de o dano ser ao escudo, casco ou módulos, independentemente das resistências
\n
DPS
Dano por segundo; a quantidade de dano que uma arma ou uma nave pode engajar por segundo contra um alvo em condições ideais
\n
EPS
Energia por segundo; a quantidade de energia que uma arma ou uma nave drena do capacitor de armas por segundo ao disparar
\n
APS
Aquecimento por segundo; a quantidade de calor que uma arma ou uma nave gera por segundo ao disparar
\n
Eficácia
Uma comparação do DPS máximo de uma determinada arma com o DPS real da arma em uma situação específica. DPS pode ser reduzido pelo alcance para o alvo, pelo casco do alvo e pela resistência do escudo e pela dureza do alvo
\n
Dano explosivo
Um tipo de dano, protege contra resistência explosiva
\n
Dureza
A resistência inerente ao dano do casco de uma nave. A dureza é definida por nave e atualmente não há nada que possa ser feito para mudá-lo. A dureza do casco de uma nave é comparada penetração das armas: se a penetração for maior do que a dureza, a arma causa 100% de dano, caso contrário faz-se uma porcentagem de seu dano calculado como penetração/dureza
\n
Redução
A distância a que as armas começam a causar menos danos do que o DPS declarado
\n
Dano cinético
Um tipo de dano, protege contra resistência cinética
\n
DSPS
Dano sustentado por segundo; a quantidade de dano que uma arma ou uma nave pode engajar por segundo contra um alvo, levando em conta a recarga de munições
\n
ESPS
Energia sustentada por segundo; a quantidade de energia que uma arma ou uma nave drena do capacitor de armas por segundo ao disparar, levando em conta a recarga de munições
\n
ASPS
Aquecimento sustentado por segundo; a quantidade de calor que uma arma ou uma nave gera por segundo ao disparar, levando em conta a recarga de munições
\n
Dano térmico
Um tipo de dano, protege contra resistência térmica
\n
", - + "HELP_TEXT": "\n

Introdução

\nCoriolis é um construtor de naves para Elite: Dangerous. Este arquivo de ajuda fornece as informações que você precisa para usar o Coriolis.\n\n

Importando sua nave para Coriolis

\nMuitas vezes, você quer começar com o sua nave já existente no Coriolis e ver como mudanças específicas podem afetá-la, por exemplo, melhorar o seu MDD. Há uma série de ferramentas que podem ser usadas para importar sua nave sem que você tenha que criá-la manualmente. Isso tem o benefício adicional de copiar sobre quaisquer modificações dos engenheiros que também tenham sido realizados.

\n\n

Importando sua nave do EDDI

\nPara importar a sua nave do EDDI, primeiro certifique-se de que sua conexão com o servidores da Frontier' companion API está funcionando. Para fazer isso, marque a aba 'Companion App' onde você deve ver \"Sua conexão com o companion app está funcionando\". Caso contrário, siga as instruções na aba companion app no EDDI para se conectar aos servidores da Frontier.

\n\nUna vez que você tenha a conexão com o companion API funcionando, vá para a aba 'Estaleiro'. No lado direito de cada nave tem um botão 'Exportar para o Coriolis' que abrirá seu navegador web padrão em Coriolis com a configuração da nave.

\n\nObserve que o Internet Explorer e Edge podem não importar corretamente, devido às restrições do comprimento do URL. Se você achar que este é o caso, então, altere seu navegador padrão para o Chrome.

\n\nAlém disso, as informações importadas não fornecem dados sobre as prioridades de energia ou o status de habilitação de seu compartimento de carga. O Coriolis define este item para ter prioridade de energia \"5\" e está por padrão desativado. Você pode alterar isso após a importação na seção Gerenciamento de Energia.

\n\n

Importando Sua Nave do EDMC

\nPara importar sua nave do EDMC uma vez que sua conexão com o servidor companion API da Frontier esteja funcionando, vá para 'Opções ->Configurações' e selecione o 'Estaleiro Preferido' para 'Coriolis'. Uma vez que isso esteja configurado, clicando em sua nave na janela principal, abrirá seu navegador web padrão no Coriolis com a configuração da sua nave.

\n\nObserve que o Internet Explorer e Edge podem não importar corretamente, devido às restrições do comprimento do URL. Se você achar que este é o caso, então, altere seu navegador padrão para o Chrome.

\n\n

Compreender e Usar Os Painéis de Equipamentos

\nA página de equipamentos é onde você vai passar a maior parte do tempo, e contém as informações para a sua nave. As informações em cada um dos painéis serão fornecidas abaixo.

\n\n

Valores-Chave

\nAo longo da parte superior da tela estão alguns dos valores-chave da sua configuração. Esta é uma referência prática para os valores, mas será fornecida mais informação para os valores nos outros painéis.

\n\nAqui, juntamente com a maioria dos lugares no Coriolis, acrônimos terão dicas de ferramentas, explicando o que elas significam. Passe o mouse sobre o acrônimo para obter mais detalhes ou veja no glossário no final desta ajuda.

\n\nTodos os valores serão o mais alto possível, assumindo que você tenha uma configuração ideal para esse valor particular (pips máximos em MTR para velocidade, combustível mínimo para alcance de salto, etc.). Isso significa que esses valores não serão afetados por alterações nas configurações de pip. Detalhes da configuração específica para cada valor serão listados na dica de ferramenta associado.

\n\n

Módulos

\nO próximo conjunto de painéis dispostos horizontalmente na tela contém os módulos que você colocou na sua configuração. Da esquerda para a direita, estes são os módulos principais, os módulos internos, os encaixes e os encaixe de apoio. Estes representam os espaços disponíveis em sua nave e não podem ser alterados. Cada espaço tem uma classe ou tamanho e em geral, qualquer módulo até um tamanho determinado pode caber em um determinado espaço (com exceção de fuselagem, suporte vital e sensores em módulos principais e espaços internos restritos, que só podem levar um subconjunto de módulo dependendo de suas restrições).

\n\nPara adicionar um módulo a um espaço, clique com o botão esquerdo no espaço e selecione o módulo desejado. Somente os módulos capazes de encaixar no espaço selecionado serão mostrados.

\n\nPara remover um módulo de um espaço, clique com o botão direito do mouse no módulo.

\n\nPara mover um módulo de um espaço para outro, arraste-o. Se você deseja copiar o módulo, arraste-o enquanto mantém pressionada a tecla 'Alt'.

\n\nAo clicar no cabeçalho de cada conjunto de módulos, você pode selecionar uma função geral para a sua nave (ao clicar no cabeçalho dos Internos Principais) ou num módulo específico com o qual você deseja preencher todos os espaços aplicáveis (ao clicar nos outros cabeçalhos).

\n\n

Controles da Nave

\nOs controles da nave permitem que você defina seus pips, impulsor e quantidade de combustível e carga que sua configuração carrega. As mudanças feitas aqui afetarão as informações fornecidas nos painéis subsequentes, o que lhe dará uma visão mais clara do efeito diferente que esses itens terão.

\n\nAs configurações de controle da nave são salvas como parte da configuração.

\n\n

Adversário

\nA seleção adversário permite que você escolha sua nave oponente. O adversário pode ser uma compilação de padrão de uma nave ou uma das suas construções salvas. Você também pode definir o intervalo de engajamento entre você e seu oponente. Sua seleção aqui afetará as informações fornecidas nos painéis subsequentes, especificamente os painéis de Ataque e Defesa.

\n\nAs configurações do adversário são salvas como parte de uma compilação.

\n\n

Sub-painéis de Energia e Custos

\n

Energia

\nO painel de gerenciamento de energia fornece informações sobre o uso de energia e as prioridades. Permite habilitar e desabilitar módulos individuais, bem como definir prioridades de energia para cada módulo. Os módulos desativados não serão incluídos nas estatísticas da configuração, com exceção dos Bancos de Célula de Escudo, pois geralmente são desativados quando não estão em uso e habilitados somente quando necessário.

\n\n

Custos

\nO painel de custos fornece informações sobre os custos para cada um de seus módulos e o custo total e seguro para sua configuração. Por definição, o Coriolis usa os custos padrão, porém os descontos para a sua nave, módulos e seguros podem ser alterados em 'Opções' no canto superior direito da página.

\n\nOs custos de reequipamento fornecem informações sobre os custos da alteração da configuração base para a sua nave, ou a sua configuração salva, para a configuração atual.

\n\nOs custos de recarga fornecem informações sobre os custos de recarregar a sua configuração atual.

\n\n

Perfis

\nOs perfis fornecem gráficos que mostram o desempenho geral dos módulos em sua compilação\n\n

Perfil do Motor

\nO painel do perfil do motor fornece informações sobre as capacidades dos seus propulsores atuais. O gráfico mostra como a velocidade máxima se altera com a massa total da sua configuração. A linha tracejada vertical no gráfico mostra sua massa atual. O perfil do seu motor pode ser alterado pela obtenção de propulsores diferentes ou engenharia de seus propulsores existentes e você pode aumentar sua velocidade máxima adicionando pips ao capacitor MTR, reduzir a quantidade de combustível e carga que está transportando ou reduzir o peso total da configuração. Você também pode aumentar sua velocidade temporariamente apertando o botão do impulsor.

\n\n

Perfil do MDD

\nO painel de perfil do MDD fornece informações sobre os recursos de sua unidade de mudança de quadro atual. O gráfico mostra como o alcance do salto máximo se altera com a massa total da sua configuração. A linha tracejada vertical no gráfico mostra seu alcance de salto único máximo atual. Seu perfil MDD pode ser alterado pela obtenção de um MDD diferente ou pela engenharia de seu MDD existente, e você pode aumentar o seu alcance de salto máximo, reduzindo a quantidade de combustível e carga que está transportando ou reduzindo o peso total da configuração,

\n\n

Perfil de Manobrabilidade

\nO painel de perfil de Manobrabilidade fornece informações sobre os recursos de seus propulsores atuais com sua massa global atual e as configurações de pips do MTR. O diagrama mostra sua capacidade de mover e girar nos diferentes eixos:\n\n
\n
Velocidade
O mais rápido que a nave pode mover, em metros por segundo
\n
Inclinação
O mais rápido que a nave pode elevar ou baixar o nariz, em graus por segundo
\n
Rolamento
O mais mais rápido a nave pode rotacionar, em graus por segundo
\n
Guinada
O mais rápido qua a nave pode virar o nariz para a esquerda ou para a direita, em graus por segundo
\n
\n\nO seu perfil de manobrabilidade pode ser alterado através da obtenção de propulsores diferentes ou engenharia de seus propulsores existentes, e você pode aumentar seus valores de movimento adicionando pips ao capacitor MTR, além de reduzir a quantidade de combustível e carga que está transportando ou reduzir o peso total da configuração. Você também pode aumentar seu perfil de movimento temporariamente apertando o botão do impulsor.

\n\n

Perfil de Dano

\nO perfil de dano fornece dois gráficos que mostram como o dano da configuração ao escudo do adversário e mudanças no casco com o tempo de engajamento. A linha tracejada vertical no gráfico mostra sua faixa de engajamento atual. Isso combina informações sobre as armas da configuração com os escudos e o casco do adversário para fornecer uma imagem precisa do dano sustentado que pode ser imposto ao oponente.

\n\n

Ataque

\n

Resumo

\nO resumo de ataque fornece informações por arma sobre danos sustentados por segundo imposto ao escudo e casco, juntamente com uma medida de eficácia dessa arma. O valor da eficácia tem uma ferramenta de ajuda que fornece uma discriminação da eficácia e pode incluir reduções ou aumentos devido ao alcance, resistência e distribuidor de energia (para escudos) ou dureza (para o casco). O valor final da eficácia é calculado multiplicando estas porcentagens em conjunto.

\n\n

Valores de Ataque

\nO painel de valores de ataque fornece informações sobre seu poder de causar dano.

\n\nO tempo de drenagem é uma medida de quão rápido o seu capacitor ARM drenará quando disparar todas as armas. É afetado pelo número de pips que você possui em seu capacitor ARM, com mais pips resultando em uma taxa de recarga mais alta das armas e portanto, um tempo maior para drenar.

\n\nO próximo valor é o tempo que você levará para remover os escudos do seu oponente. Isso pressupõe que você tenha 100% de tempo no alvo e que sua período de engajamento permaneça constante. Observe que se o seu tempo para derrubar o escudo é maior do que o seu tempo de drenar, isso pressupõe que você continue a disparar do começo ao fim, causando danos menores devido à energia reduzida em seu capacitor ARM.

\n\nO próximo valor é o tempo que você levará para abater a casco do seu oponente. Isso segue a mesma lógica do tempo para remover os escudos.

\n\n

Fontes de Danos ao Escudo

\nAs fontes de danos do escudo fornecem informações sobre as fontes de danos ao seu oponente por tipo de dano. Para cada tipo de dano aplicável (absoluto, explosivo, cinético, térmico) é fornecido um valor de dano sustentado por segundo.

\n\n

Fontes de Danos ao Casco

\nAs fontes de danos no casco fornecem informações sobre as fontes de danos ao seu oponente por tipo de dano. Para cada tipo de dano aplicável (explosivo absoluto, cinético, térmico) é fornecido um valor de dano sustentado por segundo.

\n\n

Defesas

\n

Valores do Escudo

\nOs valores do escudo fornecem informações sobre o potencial defensivo do escudo.

\n\nA força do escudo total é a soma do seu gerador de escudo, potenciadores e banco de célula de escudo. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nO tempo em que os escudos resistirão é o tempo que levará ao seu oponente para derrubar seu escudo. Isso pressupõe que eles atirem em 100% do tempo no alvo e que o período de engajamento permanece constante. Também pressupõe que você use todos os seus banco de célula de escudo antes que os seus escudos caiam.

\n\nO tempo de recuperação do escudo é o tempo que levará o seu escudo a partir do colapso (0%) até recuperar (50%). Isso é afetado pelo número de pips que você possui no seu capacitor SIS.

\n\nO tempo de recarga do escudo é o tempo que levará o seu escudo a serem recuperados (50%) até a carga completa (100%). Isso é afetado pelo número de pips que você possui no seu capacitor SIS.

\n\nFontes do Escudo\nEste gráfico fornece informações sobre as fontes dos seus escudos. Para cada fonte aplicável de escudos (gerador, potenciadores, bancos de célula de escudo) é fornecido um valor.

\n\nDano Recebido\nEste gráfico mostra como o dano inicial das armas de cada tipo é reduzido antes do seu dano ser aplicado aos escudos. Para cada tipo de dano (absoluto, explosivo, cinético, térmico) é proporcionada uma porcentagem do dano inicial. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nEficiência do Escudo\nEste gráfico mostra a eficiência do escudo para cada tipo de dano, dividindo a capacidade total do escudo pelo dano recebido por tipo.

\n\n

Valores da Armadura

\nOs valores da blindagem fornecem informações sobre sua defesa da blindagem.

\n\nA força total da blindagem é a soma da blindagem de sua fuselagem e dos reforços de casco. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nO tempo que a blindagem irá resistir é o tempo que levará ao seu oponente à levar sua blindagem a 0. Isso pressupõe que eles atirem em 100% do tempo no alvo e o tempo de engajamento permaneça constante e que todo o dano é dado na blindagem ao invés dos módulos.

\n\nA blindagem total do módulo é a soma da proteção dos reforços de módulo.

\n\nA proteção para os encaixes é a quantidade de proteção que seus reforços de módulos fornecem aos encaixes. Esta porcentagem de danos aos encaixes será desviada para os reforços de módulo.

\n\nA proteção para todos os outros módulos é a quantidade de proteção que seus reforços de módulo fornecem para tudo além dos encaixes. Esta porcentagem de danos aos módulos será desviada para os reforços de módulo.

\n\nFontes da Armadura\nEste gráfico fornece informações sobre as fontes da sua blindagem. Para cada fonte aplicável de escudos (fuselagem, reforços de casco), é fornecido um valor.

\n\nDano Recebido\nEste gráfico mostra como o dano inicial das armas de cada tipo é reduzido antes do dano ser aplicado à blindagem. Para cada tipo de dano (absoluto, explosivo, cinético, térmico) é proporcionada uma porcentagem do dano inicial. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nArmadura Efetiva\nEste gráfico mostra a blindagem efetiva para cada tipo de dano encontrado, dividindo o valor da blindagem total pelo dano recebido por esse tipo.

\n\n

Atalhos do Teclado

\n
\n
Ctrl-b
ativar impulsor
\n
Ctrl-e
mensagem de exportação aberto (exclusivo página equipamentos)
\n
Ctrl-h
mensagem de ajuda aberto
\n
Ctrl-i
mensagem de importação aberto
\n
Ctrl-o
mensagem de ligação curta aberto
\n
Ctrl-seta-esq
Aumentar capacitor SIS
\n
Ctrl-seta-cima
Aumentar capacitor MTR
\n
Ctrl-seta-dir
Aumentar capacitor SIS
\n
Ctrl-seta-baixo
Distribuidor de energia padrão
\n
Esc
fechar qualquer mensagem aberta
\n
\n

Glossário

\n
\n
Dano absoluto
Um tipo de dano, sem qualquer proteção. O dano absoluto é sempre distribuído a 100%, independentemente de o dano ser ao escudo, casco ou módulos, independentemente das resistências
\n
DPS
Dano por segundo; a quantidade de dano que uma arma ou uma nave pode engajar por segundo contra um alvo em condições ideais
\n
EPS
Energia por segundo; a quantidade de energia que uma arma ou uma nave drena do capacitor de armas por segundo ao disparar
\n
APS
Aquecimento por segundo; a quantidade de calor que uma arma ou uma nave gera por segundo ao disparar
\n
Eficácia
Uma comparação do DPS máximo de uma determinada arma com o DPS real da arma em uma situação específica. DPS pode ser reduzido pelo alcance para o alvo, pelo casco do alvo e pela resistência do escudo e pela dureza do alvo
\n
Dano explosivo
Um tipo de dano, protege contra resistência explosiva
\n
Dureza
A resistência inerente ao dano do casco de uma nave. A dureza é definida por nave e atualmente não há nada que possa ser feito para mudá-lo. A dureza do casco de uma nave é comparada penetração das armas: se a penetração for maior do que a dureza, a arma causa 100% de dano, caso contrário faz-se uma porcentagem de seu dano calculado como penetração/dureza
\n
Redução
A distância a que as armas começam a causar menos danos do que o DPS declarado
\n
Dano cinético
Um tipo de dano, protege contra resistência cinética
\n
DSPS
Dano sustentado por segundo; a quantidade de dano que uma arma ou uma nave pode engajar por segundo contra um alvo, levando em conta a recarga de munições
\n
ESPS
Energia sustentada por segundo; a quantidade de energia que uma arma ou uma nave drena do capacitor de armas por segundo ao disparar, levando em conta a recarga de munições
\n
ASPS
Aquecimento sustentado por segundo; a quantidade de calor que uma arma ou uma nave gera por segundo ao disparar, levando em conta a recarga de munições
\n
Dano térmico
Um tipo de dano, protege contra resistência térmica
\n
", + "th": "Propulsores", "damage dealt to": "Dano causado a", "damage received from": "Dano recebido de", @@ -447,10 +460,10 @@ "lasers": "Lasers", "projectiles": "Projéteis", "ordnance": "Artilharia", - "Lightweight Alloy": "Ligas Leves", - "Reinforced Alloy": "Ligas Reforçadas", - "Military Grade Composite": "Composto Nível Militar", - "Mirrored Surface Composite": "Composto de Superfície Espelhada", + "Lightweight Alloy": "Liga Leve", + "Reinforced Alloy": "Liga Reforçada", + "Military Grade Composite": "Composto de Nível Militar", + "Mirrored Surface Composite": "Composto de Superfície Reflexiva", "Reactive Surface Composite": "Composto de Superfície Reativa", "scanners": "Scanners", "Basic Discovery Scanner": "Scanner de Descoberta Básico", @@ -459,12 +472,15 @@ "Intermedia Discovery Scanner": "Scanner de Descoberta Intermediário", "Standard Docking Computer": "Computador de Docagem Padrão", "Advanced Docking Computer": "Computador de Docagem Avançado", + "defence": "Defesa", "Point Defence": "Defesa de Ponto", "Chaff Launcher": "Lançador de Chaff", - "Heat Sink Launcher": "Dissipador Térmico", - "Electronic Countermeasure": "Contramedida eletrônica", - "Xeno Scanner": "Scanner Xeno", - "Shutdown Field Neutraliser": "Neutralizador de campo de desativação", + "Electronic Countermeasure": "Contramedida Eletrônica", + "Xeno Scanner": "Scanner Xeno", + "Enhanced Xeno Scanner": "Scanner Xeno Aprimorado", + "Pulse Wave Xeno Scanner": "Scanner Xeno de Onda de Pulso", + "Shutdown Field Neutraliser": "Neutralizador de campo de desativação", + "Thargoid Pulse Neutraliser": "Neutralizador de Pulso Thargoid", "Cargo Hatch": "Coletor de carga", "federation rank 1": "Recruta", "federation rank 2": "Cadete", @@ -494,7 +510,7 @@ "empire rank 12": "Arqui-duque", "empire rank 13": "Príncipe", "empire rank 14": "Rei", - + "am": "Unidade de Manutenção", "bh": "Fuselagem", "bl": "Laser Contínuo", @@ -503,6 +519,7 @@ "cc": "Drones de Coleta", "ch": "Lançador de Chaff", "cr": "Estante de Carga", + "Corrosion Resistant": "Estante de Carga Anti-Corrosiva", "cs": "Scanner de Manifesto", "dc": "Computador de Docagem", "ec": "Contramedida Eletrônica", @@ -512,11 +529,11 @@ "fs": "Coletor de Combustível", "fsd": "Motor de Distorção de Fase", "frame shift drive": "Motor de Distorção de Fase", + "Frame Shift Drive (SCO)": "Motor de Distorção de Fase (SSV)", "ft": "Tanque de Combustível", "fx": "Drones de Combustível", "hb": "Drones Quebra-escotilha", "hr": "Pacote de Reforço de Casco", - "hs": "Dissipador Térmico", "kw": "Scanner de Registro Criminal", "ls": "Suporte de Vida", "mc": "Canhão de Repetição", @@ -524,6 +541,7 @@ "ml": "Laser de Mineração", "mr": "Estante de Mísseis", "axmr": "Estante de Mísseis AX", + "Advanced Missile Rack": "Estante de Mísseis Av.", "mrp": "Pacote de Reforço de Módulo", "nl": "Lança Minas", "shock mine launcher": "Lança Minas de Impacto", @@ -532,14 +550,16 @@ "pc": "Drones Prospectores", "pce": "Cabine de Passageiro Classe Econômica", "pci": "Cabine de Passageiro Classe Executiva", - "pcm": "Cabine de Passageiro de Primeira Classe", - "pcq": "Cabine de Passageiro de Luxo", + "pcm": "Cabine de Passageiro Primeira Classe", + "pcq": "Cabine de Passageiro Luxo", "pd": "Distribuidor de Energia", "pl": "Laser de Pulso", "po": "Defesa de Ponto", "pp": "Gerador de Energia", - "gpp": "Gerador de Energia Híbrido Guardian", - "gpd": "Distribuidor de Energia Híbrido Guardian", + "gpp": "Gerador de Energia Guardian", + "Guardian Hybrid Power Plant": "Gerador de Energia Guardian", + "gpd": "Distribuidor de Energia Guardian", + "Guardian Power Distributor": "Distribuidor de Energia Guardian", "gpc": "Carregador de Plasma Guardian", "ggc": "Canhão Gauss Guardian", "gsrp": "Pacote de Reforço de Escudo Guardian", @@ -572,6 +592,10 @@ "rcpl": "Drones de Reconhecimento", "xs": "Scanner Xeno", "dtl": "Drones de Descontaminação", + "hs": "Dissipador Térmico", + "Heat Sink Launcher": "Lançador de Dissipador Térmico", + "csl": "Dissipador Cáustico", + "Caustic Sink Launcher": "Lançador de Dissipador Cáustico", "Supercruise Assist": "Assistência de Supervelocidade", "tbem": "Estante de Mísseis Enzimáticos", "tbrfl": "Lançador de Dardos de Liberação Remota", @@ -581,5 +605,111 @@ "power plant": "Gerador de Energia", "sensors": "Sensores", "fuel tank": "Tanque de Combustível", - "mahr": "Pacote de Reforço de Casco de Meta-liga" + "mahr": "Pacote de Reforço de Casco de Meta-liga", + "ews": "Estabilizador de Arma Experimental", + "weapon stabilizers": "Estabilizadores de Arma", + "mlc": "Controladores Multidrones", + "Universal Multi Limpet Controller": "Controlador Multidrones Universal", + "Mining Multi Limpet Controller": "Cont. Multidrones de Mineração", + "Operations Multi Limpet Controller": "Controlador de Drones Operacionais", + "Rescue Multi Limpet Controller": "Controlador de Drones de Resgate", + "Xeno Multi Limpet Controller": "Controlador de Drones Xeno", + + "Lightweight": "Leve", + "Heavy duty": "Pesado", + "Blast resistant": "Resistência Explosiva", + "Kinetic resistant": "Resistência Cinética", + "Thermal resistant": "Resistência Térmica", + "Angled Plating": "Revestimento Inclinado", + "Layered Plating": "Revestimento em camadas", + "Deep Plating": "Revestimento espesso", + "Reflective Plating": "Revestimento refletor", + "Armoured": "Blindado", + "Low emissions": "Baixa Emissão", + "Overcharged": "Sobrecarregado", + "Monstered": "Parrudo", + "Thermal Spread": "Propagação Térmica", + "Double Braced": "Braçadeira Dupla", + "Stripped Down": "Modelo Leve", + "Dirty": "Ajuste Sujo", + "Reinforced": "Reforçado", + "Clean": "Ajuste Limpo", + "Drag Drives": "Motores de Arrasto", + "Drive Distributors": "Distribuidores do Motor", + "Faster boot sequence": "Sequencia de Inicialização Rápida", + "Increased range": "Alcance Aumentado", + "Deep Charge": "Carga Intensa", + "Mass Manager": "Gestor de Massa", + "Shielded": "Protegido", + "Charge enhanced": "Recarga Melhorada", + "Engine focused": "Foco nos Motores", + "High charge capacity": "Alta Capacidade", + "System focused": "Foco no Sistema", + "Weapon focused": "Foco nas Armas", + "Cluster Capacitors": "Banco de Capacitores", + "Super Conduits": "Supercondutores", + "Flow Control": "Controle de Fluxo", + "Long range": "Longa Distância", + "Wide angle": "Ângulo Amplo", + "Expanded capture arc": "Arco de Captura Expandido", + "Enhanced low power": "Baixo Consumo, Aprimorados", + "Fast Charge": "Carga Rápida", + "Multi-weave": "Rede Múltipla", + "Hi-Cap": "Alta Capacitância", + "Lo-draw": "Baixo Consumo", + "Thermo Block": "Bloqueio Térmico", + "Force Block": "Bloqueio Cinético", + "Rapid charge": "Carga Rápida", + "Specialised": "Especializado", + "Recycling Cell": "Célula de Reciclagem", + "Boss Cells": "Células Resistentes", + "Expanded Probe Scanning Radius": "Expansão Raio Escaneamento da Sonda", + "Resistance augmented": "Resistência Aumentada", + "Blast Block": "Bloqueio Explosivo", + "Super Capacitors": "Supercapacitores", + "Ammo capacity": "Capacidade de Munição", + "Fast scan": "Escaneio Rápido", + "Efficient": "Eficiente", + "Focused": "Arma com Foco", + "Rapid fire": "Modificação de Disparo Rápido", + "Short range": "Disparo de Curto Alcance", + "Sturdy": "Robusto", + "Concordant sequence": "Sequência Concordante", + "Emissive munitions": "Munições Emissivas", + "Phasing sequence": "Sequência Faseada", + "Scramble spectrum": "Espectro Randômico", + "Thermal shock": "Choque Térmico", + "Oversized": "Reforçado", + "Multi-servos": "Servos Múltiplos", + "Inertial impact": "Impacto Inercial", + "Regeneration sequence": "Sequência de Regeneração", + "Thermal conduit": "Condutor Térmico", + "Thermal vent": "Exaustor Térmico", + "High capacity": "Cartucho de Alta Capacidade", + "Auto loader": "Carregador Automático", + "Corrosive shell": "Projétil Corrosivo", + "Incendiary rounds": "Munição Incendiária", + "Smart rounds": "Projéteis Inteligentes", + "Dispersal field": "Campo de Dispersão", + "Force shell": "Projétil de Força", + "High yield shell": "Projétil de Alto Rendimento", + "Thermal cascade": "Cascata Térmica", + "Double shot": "Tiro Duplo", + "Dazzle shell": "Projétil Cegante", + "Drag munitions": "Munições de Arrasto", + "Screening shell": "Projétil de Triagem", + "Plasma Slug": "Projétil de Plasma", + "Target lock breaker": "Quebra de Trava de Alvo", + "Feedback Cascade": "Cascata de Realimentação", + "Super Penetrator": "Super Penetrador", + "FSD interrupt": "Interruptor de MDD", + "Overload munitions": "Munições de Sobrecarga", + "Penetrator Munitions": "Munições Penetrantes", + "Mass lock munition": "Munição de Bloqueio de Massa", + "Penetrator payload": "Carga Penetrante", + "Reverberating cascade": "Carga Reverberante", + "Mass Lock Munition": "Munição de Bloqueio de Massa", + "Ion disruptor": "Perturbação de Íon", + "Radiant Canister": "Mina Radiante", + "Shift-lock canister": "Mina de Trava de Distorção" } From 8593e18de436f0de553dba185a07c07123fdba4c Mon Sep 17 00:00:00 2001 From: leonardofelin <33718368+leonardofelin@users.noreply.github.com> Date: Tue, 14 May 2024 13:13:56 -0300 Subject: [PATCH 02/42] Updated PT-BR translation with Planetary Approach Suite --- src/app/i18n/pt.json | 94 ++++++++++++++++++++++---------------------- 1 file changed, 48 insertions(+), 46 deletions(-) diff --git a/src/app/i18n/pt.json b/src/app/i18n/pt.json index 464dfe14..bfd10920 100644 --- a/src/app/i18n/pt.json +++ b/src/app/i18n/pt.json @@ -168,14 +168,14 @@ "laden": "cheia", "total unladen": "total vazia", "total laden": "total cheia", - "shield": "Escudo", - "shields": "Escudos", - "integrity": "Integridade", - "mass": "Massa", - "boost interval": "Intervalo impulso", + "shield": "escudo", + "shields": "escudos", + "integrity": "integridade", + "mass": "massa", + "boost interval": "intervalo impulso", "resting heat (Beta)": "Temperat. Repouso", "No Shield": "Sem Escudo", - "resistance": "Resistências", + "resistance": "resistências", "HP": "Pontos de Vida", "absolute": "absoluta", "explosive": "explosiva", @@ -360,7 +360,7 @@ "damage": "dano", "distdraw": "consumo distribuidor", "eff": "eficiência", - "fallofffromrange": "Início da perda", + "fallofffromrange": "início da perda", "hullboost": "melhoria de casco", "hullreinforcement": "reforço de casco", "maxfuel": "combustível máximo por salto", @@ -369,7 +369,7 @@ "engrate": "recarga dos motores", "syscap": "capacidade dos sistemas", "sysrate": "recarga dos sistemas", - "wepcap": "Capacidade das armas", + "wepcap": "capacidade das armas", "weprate": "recarga das armas", "thermload": "carga térmica", "proberadius": "raio da sonda", @@ -422,44 +422,44 @@ "orbis password": "Senha para orbis.zone", "HELP_TEXT": "\n

Introdução

\nCoriolis é um construtor de naves para Elite: Dangerous. Este arquivo de ajuda fornece as informações que você precisa para usar o Coriolis.\n\n

Importando sua nave para Coriolis

\nMuitas vezes, você quer começar com o sua nave já existente no Coriolis e ver como mudanças específicas podem afetá-la, por exemplo, melhorar o seu MDD. Há uma série de ferramentas que podem ser usadas para importar sua nave sem que você tenha que criá-la manualmente. Isso tem o benefício adicional de copiar sobre quaisquer modificações dos engenheiros que também tenham sido realizados.

\n\n

Importando sua nave do EDDI

\nPara importar a sua nave do EDDI, primeiro certifique-se de que sua conexão com o servidores da Frontier' companion API está funcionando. Para fazer isso, marque a aba 'Companion App' onde você deve ver \"Sua conexão com o companion app está funcionando\". Caso contrário, siga as instruções na aba companion app no EDDI para se conectar aos servidores da Frontier.

\n\nUna vez que você tenha a conexão com o companion API funcionando, vá para a aba 'Estaleiro'. No lado direito de cada nave tem um botão 'Exportar para o Coriolis' que abrirá seu navegador web padrão em Coriolis com a configuração da nave.

\n\nObserve que o Internet Explorer e Edge podem não importar corretamente, devido às restrições do comprimento do URL. Se você achar que este é o caso, então, altere seu navegador padrão para o Chrome.

\n\nAlém disso, as informações importadas não fornecem dados sobre as prioridades de energia ou o status de habilitação de seu compartimento de carga. O Coriolis define este item para ter prioridade de energia \"5\" e está por padrão desativado. Você pode alterar isso após a importação na seção Gerenciamento de Energia.

\n\n

Importando Sua Nave do EDMC

\nPara importar sua nave do EDMC uma vez que sua conexão com o servidor companion API da Frontier esteja funcionando, vá para 'Opções ->Configurações' e selecione o 'Estaleiro Preferido' para 'Coriolis'. Uma vez que isso esteja configurado, clicando em sua nave na janela principal, abrirá seu navegador web padrão no Coriolis com a configuração da sua nave.

\n\nObserve que o Internet Explorer e Edge podem não importar corretamente, devido às restrições do comprimento do URL. Se você achar que este é o caso, então, altere seu navegador padrão para o Chrome.

\n\n

Compreender e Usar Os Painéis de Equipamentos

\nA página de equipamentos é onde você vai passar a maior parte do tempo, e contém as informações para a sua nave. As informações em cada um dos painéis serão fornecidas abaixo.

\n\n

Valores-Chave

\nAo longo da parte superior da tela estão alguns dos valores-chave da sua configuração. Esta é uma referência prática para os valores, mas será fornecida mais informação para os valores nos outros painéis.

\n\nAqui, juntamente com a maioria dos lugares no Coriolis, acrônimos terão dicas de ferramentas, explicando o que elas significam. Passe o mouse sobre o acrônimo para obter mais detalhes ou veja no glossário no final desta ajuda.

\n\nTodos os valores serão o mais alto possível, assumindo que você tenha uma configuração ideal para esse valor particular (pips máximos em MTR para velocidade, combustível mínimo para alcance de salto, etc.). Isso significa que esses valores não serão afetados por alterações nas configurações de pip. Detalhes da configuração específica para cada valor serão listados na dica de ferramenta associado.

\n\n

Módulos

\nO próximo conjunto de painéis dispostos horizontalmente na tela contém os módulos que você colocou na sua configuração. Da esquerda para a direita, estes são os módulos principais, os módulos internos, os encaixes e os encaixe de apoio. Estes representam os espaços disponíveis em sua nave e não podem ser alterados. Cada espaço tem uma classe ou tamanho e em geral, qualquer módulo até um tamanho determinado pode caber em um determinado espaço (com exceção de fuselagem, suporte vital e sensores em módulos principais e espaços internos restritos, que só podem levar um subconjunto de módulo dependendo de suas restrições).

\n\nPara adicionar um módulo a um espaço, clique com o botão esquerdo no espaço e selecione o módulo desejado. Somente os módulos capazes de encaixar no espaço selecionado serão mostrados.

\n\nPara remover um módulo de um espaço, clique com o botão direito do mouse no módulo.

\n\nPara mover um módulo de um espaço para outro, arraste-o. Se você deseja copiar o módulo, arraste-o enquanto mantém pressionada a tecla 'Alt'.

\n\nAo clicar no cabeçalho de cada conjunto de módulos, você pode selecionar uma função geral para a sua nave (ao clicar no cabeçalho dos Internos Principais) ou num módulo específico com o qual você deseja preencher todos os espaços aplicáveis (ao clicar nos outros cabeçalhos).

\n\n

Controles da Nave

\nOs controles da nave permitem que você defina seus pips, impulsor e quantidade de combustível e carga que sua configuração carrega. As mudanças feitas aqui afetarão as informações fornecidas nos painéis subsequentes, o que lhe dará uma visão mais clara do efeito diferente que esses itens terão.

\n\nAs configurações de controle da nave são salvas como parte da configuração.

\n\n

Adversário

\nA seleção adversário permite que você escolha sua nave oponente. O adversário pode ser uma compilação de padrão de uma nave ou uma das suas construções salvas. Você também pode definir o intervalo de engajamento entre você e seu oponente. Sua seleção aqui afetará as informações fornecidas nos painéis subsequentes, especificamente os painéis de Ataque e Defesa.

\n\nAs configurações do adversário são salvas como parte de uma compilação.

\n\n

Sub-painéis de Energia e Custos

\n

Energia

\nO painel de gerenciamento de energia fornece informações sobre o uso de energia e as prioridades. Permite habilitar e desabilitar módulos individuais, bem como definir prioridades de energia para cada módulo. Os módulos desativados não serão incluídos nas estatísticas da configuração, com exceção dos Bancos de Célula de Escudo, pois geralmente são desativados quando não estão em uso e habilitados somente quando necessário.

\n\n

Custos

\nO painel de custos fornece informações sobre os custos para cada um de seus módulos e o custo total e seguro para sua configuração. Por definição, o Coriolis usa os custos padrão, porém os descontos para a sua nave, módulos e seguros podem ser alterados em 'Opções' no canto superior direito da página.

\n\nOs custos de reequipamento fornecem informações sobre os custos da alteração da configuração base para a sua nave, ou a sua configuração salva, para a configuração atual.

\n\nOs custos de recarga fornecem informações sobre os custos de recarregar a sua configuração atual.

\n\n

Perfis

\nOs perfis fornecem gráficos que mostram o desempenho geral dos módulos em sua compilação\n\n

Perfil do Motor

\nO painel do perfil do motor fornece informações sobre as capacidades dos seus propulsores atuais. O gráfico mostra como a velocidade máxima se altera com a massa total da sua configuração. A linha tracejada vertical no gráfico mostra sua massa atual. O perfil do seu motor pode ser alterado pela obtenção de propulsores diferentes ou engenharia de seus propulsores existentes e você pode aumentar sua velocidade máxima adicionando pips ao capacitor MTR, reduzir a quantidade de combustível e carga que está transportando ou reduzir o peso total da configuração. Você também pode aumentar sua velocidade temporariamente apertando o botão do impulsor.

\n\n

Perfil do MDD

\nO painel de perfil do MDD fornece informações sobre os recursos de sua unidade de mudança de quadro atual. O gráfico mostra como o alcance do salto máximo se altera com a massa total da sua configuração. A linha tracejada vertical no gráfico mostra seu alcance de salto único máximo atual. Seu perfil MDD pode ser alterado pela obtenção de um MDD diferente ou pela engenharia de seu MDD existente, e você pode aumentar o seu alcance de salto máximo, reduzindo a quantidade de combustível e carga que está transportando ou reduzindo o peso total da configuração,

\n\n

Perfil de Manobrabilidade

\nO painel de perfil de Manobrabilidade fornece informações sobre os recursos de seus propulsores atuais com sua massa global atual e as configurações de pips do MTR. O diagrama mostra sua capacidade de mover e girar nos diferentes eixos:\n\n
\n
Velocidade
O mais rápido que a nave pode mover, em metros por segundo
\n
Inclinação
O mais rápido que a nave pode elevar ou baixar o nariz, em graus por segundo
\n
Rolamento
O mais mais rápido a nave pode rotacionar, em graus por segundo
\n
Guinada
O mais rápido qua a nave pode virar o nariz para a esquerda ou para a direita, em graus por segundo
\n
\n\nO seu perfil de manobrabilidade pode ser alterado através da obtenção de propulsores diferentes ou engenharia de seus propulsores existentes, e você pode aumentar seus valores de movimento adicionando pips ao capacitor MTR, além de reduzir a quantidade de combustível e carga que está transportando ou reduzir o peso total da configuração. Você também pode aumentar seu perfil de movimento temporariamente apertando o botão do impulsor.

\n\n

Perfil de Dano

\nO perfil de dano fornece dois gráficos que mostram como o dano da configuração ao escudo do adversário e mudanças no casco com o tempo de engajamento. A linha tracejada vertical no gráfico mostra sua faixa de engajamento atual. Isso combina informações sobre as armas da configuração com os escudos e o casco do adversário para fornecer uma imagem precisa do dano sustentado que pode ser imposto ao oponente.

\n\n

Ataque

\n

Resumo

\nO resumo de ataque fornece informações por arma sobre danos sustentados por segundo imposto ao escudo e casco, juntamente com uma medida de eficácia dessa arma. O valor da eficácia tem uma ferramenta de ajuda que fornece uma discriminação da eficácia e pode incluir reduções ou aumentos devido ao alcance, resistência e distribuidor de energia (para escudos) ou dureza (para o casco). O valor final da eficácia é calculado multiplicando estas porcentagens em conjunto.

\n\n

Valores de Ataque

\nO painel de valores de ataque fornece informações sobre seu poder de causar dano.

\n\nO tempo de drenagem é uma medida de quão rápido o seu capacitor ARM drenará quando disparar todas as armas. É afetado pelo número de pips que você possui em seu capacitor ARM, com mais pips resultando em uma taxa de recarga mais alta das armas e portanto, um tempo maior para drenar.

\n\nO próximo valor é o tempo que você levará para remover os escudos do seu oponente. Isso pressupõe que você tenha 100% de tempo no alvo e que sua período de engajamento permaneça constante. Observe que se o seu tempo para derrubar o escudo é maior do que o seu tempo de drenar, isso pressupõe que você continue a disparar do começo ao fim, causando danos menores devido à energia reduzida em seu capacitor ARM.

\n\nO próximo valor é o tempo que você levará para abater a casco do seu oponente. Isso segue a mesma lógica do tempo para remover os escudos.

\n\n

Fontes de Danos ao Escudo

\nAs fontes de danos do escudo fornecem informações sobre as fontes de danos ao seu oponente por tipo de dano. Para cada tipo de dano aplicável (absoluto, explosivo, cinético, térmico) é fornecido um valor de dano sustentado por segundo.

\n\n

Fontes de Danos ao Casco

\nAs fontes de danos no casco fornecem informações sobre as fontes de danos ao seu oponente por tipo de dano. Para cada tipo de dano aplicável (explosivo absoluto, cinético, térmico) é fornecido um valor de dano sustentado por segundo.

\n\n

Defesas

\n

Valores do Escudo

\nOs valores do escudo fornecem informações sobre o potencial defensivo do escudo.

\n\nA força do escudo total é a soma do seu gerador de escudo, potenciadores e banco de célula de escudo. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nO tempo em que os escudos resistirão é o tempo que levará ao seu oponente para derrubar seu escudo. Isso pressupõe que eles atirem em 100% do tempo no alvo e que o período de engajamento permanece constante. Também pressupõe que você use todos os seus banco de célula de escudo antes que os seus escudos caiam.

\n\nO tempo de recuperação do escudo é o tempo que levará o seu escudo a partir do colapso (0%) até recuperar (50%). Isso é afetado pelo número de pips que você possui no seu capacitor SIS.

\n\nO tempo de recarga do escudo é o tempo que levará o seu escudo a serem recuperados (50%) até a carga completa (100%). Isso é afetado pelo número de pips que você possui no seu capacitor SIS.

\n\nFontes do Escudo\nEste gráfico fornece informações sobre as fontes dos seus escudos. Para cada fonte aplicável de escudos (gerador, potenciadores, bancos de célula de escudo) é fornecido um valor.

\n\nDano Recebido\nEste gráfico mostra como o dano inicial das armas de cada tipo é reduzido antes do seu dano ser aplicado aos escudos. Para cada tipo de dano (absoluto, explosivo, cinético, térmico) é proporcionada uma porcentagem do dano inicial. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nEficiência do Escudo\nEste gráfico mostra a eficiência do escudo para cada tipo de dano, dividindo a capacidade total do escudo pelo dano recebido por tipo.

\n\n

Valores da Armadura

\nOs valores da blindagem fornecem informações sobre sua defesa da blindagem.

\n\nA força total da blindagem é a soma da blindagem de sua fuselagem e dos reforços de casco. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nO tempo que a blindagem irá resistir é o tempo que levará ao seu oponente à levar sua blindagem a 0. Isso pressupõe que eles atirem em 100% do tempo no alvo e o tempo de engajamento permaneça constante e que todo o dano é dado na blindagem ao invés dos módulos.

\n\nA blindagem total do módulo é a soma da proteção dos reforços de módulo.

\n\nA proteção para os encaixes é a quantidade de proteção que seus reforços de módulos fornecem aos encaixes. Esta porcentagem de danos aos encaixes será desviada para os reforços de módulo.

\n\nA proteção para todos os outros módulos é a quantidade de proteção que seus reforços de módulo fornecem para tudo além dos encaixes. Esta porcentagem de danos aos módulos será desviada para os reforços de módulo.

\n\nFontes da Armadura\nEste gráfico fornece informações sobre as fontes da sua blindagem. Para cada fonte aplicável de escudos (fuselagem, reforços de casco), é fornecido um valor.

\n\nDano Recebido\nEste gráfico mostra como o dano inicial das armas de cada tipo é reduzido antes do dano ser aplicado à blindagem. Para cada tipo de dano (absoluto, explosivo, cinético, térmico) é proporcionada uma porcentagem do dano inicial. A ferramenta de ajuda fornece um detalhamento desses valores.

\n\nArmadura Efetiva\nEste gráfico mostra a blindagem efetiva para cada tipo de dano encontrado, dividindo o valor da blindagem total pelo dano recebido por esse tipo.

\n\n

Atalhos do Teclado

\n
\n
Ctrl-b
ativar impulsor
\n
Ctrl-e
mensagem de exportação aberto (exclusivo página equipamentos)
\n
Ctrl-h
mensagem de ajuda aberto
\n
Ctrl-i
mensagem de importação aberto
\n
Ctrl-o
mensagem de ligação curta aberto
\n
Ctrl-seta-esq
Aumentar capacitor SIS
\n
Ctrl-seta-cima
Aumentar capacitor MTR
\n
Ctrl-seta-dir
Aumentar capacitor SIS
\n
Ctrl-seta-baixo
Distribuidor de energia padrão
\n
Esc
fechar qualquer mensagem aberta
\n
\n

Glossário

\n
\n
Dano absoluto
Um tipo de dano, sem qualquer proteção. O dano absoluto é sempre distribuído a 100%, independentemente de o dano ser ao escudo, casco ou módulos, independentemente das resistências
\n
DPS
Dano por segundo; a quantidade de dano que uma arma ou uma nave pode engajar por segundo contra um alvo em condições ideais
\n
EPS
Energia por segundo; a quantidade de energia que uma arma ou uma nave drena do capacitor de armas por segundo ao disparar
\n
APS
Aquecimento por segundo; a quantidade de calor que uma arma ou uma nave gera por segundo ao disparar
\n
Eficácia
Uma comparação do DPS máximo de uma determinada arma com o DPS real da arma em uma situação específica. DPS pode ser reduzido pelo alcance para o alvo, pelo casco do alvo e pela resistência do escudo e pela dureza do alvo
\n
Dano explosivo
Um tipo de dano, protege contra resistência explosiva
\n
Dureza
A resistência inerente ao dano do casco de uma nave. A dureza é definida por nave e atualmente não há nada que possa ser feito para mudá-lo. A dureza do casco de uma nave é comparada penetração das armas: se a penetração for maior do que a dureza, a arma causa 100% de dano, caso contrário faz-se uma porcentagem de seu dano calculado como penetração/dureza
\n
Redução
A distância a que as armas começam a causar menos danos do que o DPS declarado
\n
Dano cinético
Um tipo de dano, protege contra resistência cinética
\n
DSPS
Dano sustentado por segundo; a quantidade de dano que uma arma ou uma nave pode engajar por segundo contra um alvo, levando em conta a recarga de munições
\n
ESPS
Energia sustentada por segundo; a quantidade de energia que uma arma ou uma nave drena do capacitor de armas por segundo ao disparar, levando em conta a recarga de munições
\n
ASPS
Aquecimento sustentado por segundo; a quantidade de calor que uma arma ou uma nave gera por segundo ao disparar, levando em conta a recarga de munições
\n
Dano térmico
Um tipo de dano, protege contra resistência térmica
\n
", - "th": "Propulsores", - "damage dealt to": "Dano causado a", - "damage received from": "Dano recebido de", - "against shields": "Contra escudos", - "against hull": "Contra casco", - "total effective shield": "Escudo total efetivo", - "damage by": "Dano por", - "damage from": "Dano de", - "engine pips": "Pips do motor", + "th": "propulsores", + "damage dealt to": "dano causado a", + "damage received from": "dano recebido de", + "against shields": "contra escudos", + "against hull": "contra casco", + "total effective shield": "escudo total efetivo", + "damage by": "dano por", + "damage from": "dano de", + "engine pips": "pips do motor", "4b": "4 pips e impulso", "experimental": "experimental", - "mining": "Mineração", - "minmass_sg": "Massa de casco mínima", - "optmass_sg": "Massa de casco ideal", - "maxmass_sg": "Massa de casco máxima", - "minmul_sg": "Força mínima", - "optmul_sg": "Força ideal", - "maxmul_sg": "Força mínima", - "minmass_psg": "Massa de casco mínima", - "optmass_psg": "Massa de casco ideal", - "maxmass_psg": "Massa de casco máxima", - "minmul_psg": "Força mínima", - "optmul_psg": "Força ideal", - "maxmul_psg": "Força mínima", - "minmass_bsg": "Massa de casco mínima", - "optmass_bsg": "Massa de casco ideal", - "maxmass_bsg": "Massa de casco máxima", - "minmul_bsg": "Força mínima", - "optmul_bsg": "Força ideal", - "maxmul_bsg": "Força mínima", - "hangars": "Hangares", - "flight assists": "Assistências de voo", - "limpet controllers": "Controladores de Drones", - "passenger cabins": "Cabine de Passageiros", - "structural reinforcement": "Reforços estruturais", - "lasers": "Lasers", - "projectiles": "Projéteis", - "ordnance": "Artilharia", + "mining": "mineração", + "minmass_sg": "massa de casco mínima", + "optmass_sg": "massa de casco ideal", + "maxmass_sg": "massa de casco máxima", + "minmul_sg": "força mínima", + "optmul_sg": "força ideal", + "maxmul_sg": "força mínima", + "minmass_psg": "massa de casco mínima", + "optmass_psg": "massa de casco ideal", + "maxmass_psg": "massa de casco máxima", + "minmul_psg": "força mínima", + "optmul_psg": "força ideal", + "maxmul_psg": "força mínima", + "minmass_bsg": "massa de casco mínima", + "optmass_bsg": "massa de casco ideal", + "maxmass_bsg": "massa de casco máxima", + "minmul_bsg": "força mínima", + "optmul_bsg": "força ideal", + "maxmul_bsg": "força mínima", + "hangars": "hangares", + "flight assists": "assistências de voo", + "limpet controllers": "controladores de Drones", + "passenger cabins": "cabine de Passageiros", + "structural reinforcement": "reforços estruturais", + "lasers": "lasers", + "projectiles": "projéteis", + "ordnance": "artilharia", "Lightweight Alloy": "Liga Leve", "Reinforced Alloy": "Liga Reforçada", "Military Grade Composite": "Composto de Nível Militar", @@ -472,7 +472,7 @@ "Intermedia Discovery Scanner": "Scanner de Descoberta Intermediário", "Standard Docking Computer": "Computador de Docagem Padrão", "Advanced Docking Computer": "Computador de Docagem Avançado", - "defence": "Defesa", + "defence": "defesa", "Point Defence": "Defesa de Ponto", "Chaff Launcher": "Lançador de Chaff", "Electronic Countermeasure": "Contramedida Eletrônica", @@ -547,6 +547,8 @@ "shock mine launcher": "Lança Minas de Impacto", "pa": "Acelerador de Plasma", "pas": "Instrumento de Aproximação Planetária", + "Planetary Approach Suite (Horizons)": "Instr. de Aprox. Planetária (Horizons)", + "Advanced Planetary Approach Suite (Odyssey)": "Instr. de Aprox. Planetária Avançado (Odyssey)", "pc": "Drones Prospectores", "pce": "Cabine de Passageiro Classe Econômica", "pci": "Cabine de Passageiro Classe Executiva", @@ -652,7 +654,7 @@ "Long range": "Longa Distância", "Wide angle": "Ângulo Amplo", "Expanded capture arc": "Arco de Captura Expandido", - "Enhanced low power": "Baixo Consumo, Aprimorados", + "Enhanced low power": "Baixo Consumo, Aprimorado", "Fast Charge": "Carga Rápida", "Multi-weave": "Rede Múltipla", "Hi-Cap": "Alta Capacitância", From 14ffa26ef98fda7b3e269c7e5c32795754f14b7b Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Thu, 16 May 2024 19:23:33 +0100 Subject: [PATCH 03/42] Adds valid module checking to all types of modules on import --- src/app/components/AvailableModulesMenu.jsx | 27 +- src/app/components/HardpointSlot.jsx | 1 + src/app/components/InternalSlot.jsx | 1 + src/app/components/Slot.jsx | 15 +- src/app/components/StandardSlot.jsx | 9 +- src/app/i18n/en.json | 4 + src/app/pages/ErrorDetails.jsx | 3 + src/app/shipyard/Module.js | 9 + src/app/shipyard/ModuleUtils.js | 9 + src/app/utils/BlueprintFunctions.js | 870 ++++++++++---------- src/app/utils/JournalUtils.js | 123 ++- 11 files changed, 610 insertions(+), 461 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index de4a6e17..110953cd 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -213,16 +213,30 @@ export default class AvailableModulesMenu extends TranslatedComponent { if (categories.length === 1) { // Show category header instead of group header if (m && grp == m.grp) { - list.push(
this.groupElem = elem} key={category} + // If this is a missing module/weapon, skip it + if (m.grp == "mh" || m.grp == "mm"){ + continue; + } else { + list.push(
this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}
); + } } else { - list.push(
{translate(category)}
); + if (category == "mh"){ + continue; + } else { + list.push(
{translate(category)}
); + } } } else { // Show category header as well as group header if (!categoryHeader) { - list.push(
{translate(category)}
); - categoryHeader = true; + if (category == "mh"){ + continue; + } + else { + list.push(
{translate(category)}
); + categoryHeader = true; + } } if (m && grp == m.grp) { list.push(
this.groupElem = elem} key={grp} @@ -298,6 +312,10 @@ export default class AvailableModulesMenu extends TranslatedComponent { let itemsOnThisRow = 0; for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; + if (ModuleUtils.isMissingModule(m.info)) { + // If this is a missing module, skip it + continue; + } let mount = null; let disabled = false; prevName = m.name; @@ -312,6 +330,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length; } let active = mountedModule && mountedModule.id === m.id; + let classes = cn(m.name ? 'lc' : 'c', { warning: !disabled && warningFunc && warningFunc(m), active, diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 724d9fd5..e9edda43 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -136,6 +136,7 @@ export default class HardpointSlot extends Slot { {showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null} {m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null} + {m.getInfo() ?
{translate(m.getInfo())}
: null} {m && validMods.length > 0 ?
this.modButton = modButton}>
: null }
; diff --git a/src/app/components/Slot.jsx b/src/app/components/Slot.jsx index 8c04dbab..1594cad6 100644 --- a/src/app/components/Slot.jsx +++ b/src/app/components/Slot.jsx @@ -99,6 +99,7 @@ export default class Slot extends TranslatedComponent { let translate = language.translate; let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props; let slotDetails, modificationsMarker, menu; + let missing = false; if (!selected) { // If not selected then sure that modifications flag is unset @@ -108,6 +109,11 @@ export default class Slot extends TranslatedComponent { if (m) { slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes modificationsMarker = JSON.stringify(m); + if(typeof m.grp !== 'undefined' || m.grp !== null) { + if(m.grp == "mh" || m.grp == "mm") { + missing = true; + } + } } else { slotDetails =
{translate(eligible ? 'emptyrestricted' : 'empty')}
; modificationsMarker = ''; @@ -138,13 +144,16 @@ export default class Slot extends TranslatedComponent { } // TODO: implement touch dragging - + return (
this.slotDiv = slotDiv}> -
+ { + // If missing module/hardpoint, set the div container to warning status. + } +
{this._getMaxClassLabel(translate)}
{slotDetails} -
+
{menu}
); diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 1e810982..70bb57ae 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -93,6 +93,10 @@ export default class StandardSlot extends TranslatedComponent { this._modificationsSelected = false; } + if (m.info) { + warning = () => true; + } + const modificationsMarker = JSON.stringify(m); if (selected) { @@ -124,7 +128,7 @@ export default class StandardSlot extends TranslatedComponent {
{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}
-
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
+
{classRating} {m.getInfo() ? translate(m.ukName) : translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
@@ -144,7 +148,8 @@ export default class StandardSlot extends TranslatedComponent { { showModuleResistances && m.getKineticResistance() ?
{translate('kinres')}: {formats.pct(m.getKineticResistance())}
: null } { showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null } { m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null } - { validMods.length > 0 ?
this.modButton = modButton }>
: null } + { m.getInfo() ?
{translate(m.getInfo())}
: null } + { m.getInfo() ?
: validMods.length > 0 ?
this.modButton = modButton }>
: null }
diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index fd6b3f0b..59beffb2 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -83,6 +83,7 @@ "HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes", "PHRASE_FAIL_EDENGINEER": "Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)", "PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.", + "MISSING_MODULES": "Missing Modules", "am": "Auto Field-Maintenance Unit", "bh": "Bulkheads", "bl": "Beam Laser", @@ -109,6 +110,8 @@ "kw": "Kill Warrant Scanner", "ls": "Life Support", "mc": "Multi-cannon", + "mh": "Missing Weapon/Utility", + "mm": "Missing Module", "axmc": "AX Multi-cannon", "ml": "Mining Laser", "mlc": "Multi Limpet Controller", @@ -212,6 +215,7 @@ "boost interval": "Boost interval", "total": "Total", "ammo": "Ammunition maximum", + "info": "Info", "boot": "Boot time", "hacktime": "Hack time", "brokenregen": "Broken regeneration rate", diff --git a/src/app/pages/ErrorDetails.jsx b/src/app/pages/ErrorDetails.jsx index 5c68b8c9..4d96f93e 100644 --- a/src/app/pages/ErrorDetails.jsx +++ b/src/app/pages/ErrorDetails.jsx @@ -45,6 +45,9 @@ export default class ErrorDetails extends React.Component { return

Jameson, we have a problem..

{error.message}

+ Import Error handling has been improved, but still isn't perfect.
MOST Import failures are a result of missing modules in Coriolis,
OR incorrect import strings generated by third party apps. If you're seeing this page, we may have failed to handle the errors in your import correctly. Please see the data output below, specifically the 'scriptUrl:' section if it's there and then if you feel confident enough, please check the github issues page linked below and see if there is a similar issue already logged. If not, please create a new issue with the data below. If you're not confident, please ask for help on the Coriolis Channel of the EDCD Discord server. +
+

{importerror ?
If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.
: null }
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index dbbdc09c..7df7f44e 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -439,6 +439,15 @@ export default class Module { return this.get('integrity', modified); } + /** + * Get the info of this module + * @param {Boolean} [modified=false] Whether to take modifications into account + * @return {String} the info of this module + */ + getInfo(modified = false) { + return (modified && this.getModValue('info')) || this.info; + } + /** * Get the mass of this module * @param {Boolean} [modified=true] Whether to take modifications into account diff --git a/src/app/shipyard/ModuleUtils.js b/src/app/shipyard/ModuleUtils.js index 276de3c1..209e24b7 100755 --- a/src/app/shipyard/ModuleUtils.js +++ b/src/app/shipyard/ModuleUtils.js @@ -322,6 +322,15 @@ export function isShieldGenerator(g) { return g == 'sg' || g == 'psg' || g == 'bsg'; } +/** + * Determine if a module group is a missing module + * @param {String} info Module Group name + * @return {Boolean} True if the group is a missing module + */ +export function isMissingModule(info) { + return info == 'Not in Coriolis yet. Check GitHub issues. Add Issue if needed.'; +} + /** * Creates a new ModuleSet that contains all available modules * that the specified ship is eligible to use. diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js index 80259bea..3365c05c 100644 --- a/src/app/utils/BlueprintFunctions.js +++ b/src/app/utils/BlueprintFunctions.js @@ -1,435 +1,435 @@ -import React from 'react'; -import { Modifications } from 'coriolis-data/dist'; -import { STATS_FORMATTING } from '../shipyard/StatsFormatting'; - -/** - * Generate a tooltip with details of a blueprint's specials - * @param {Object} translate The translate object - * @param {Object} blueprint The blueprint at the required grade - * @param {string} grp The group of the module - * @param {Object} m The module to compare with - * @param {string} specialName The name of the special - * @returns {Object} The react components - */ -export function specialToolTip(translate, blueprint, grp, m, specialName) { - const effects = []; - if (!blueprint || !blueprint.features) { - return undefined; - } - if (m) { - // We also add in any benefits from specials that aren't covered above - if (m.blueprint) { - for (const feature in Modifications.modifierActions[specialName]) { - // if (!blueprint.features[feature] && !m.mods.feature) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature) - m.getModValue(feature, true); - if (featureDef.type === 'percentage') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - } - - return ( -
- - - {effects} - -
-
- ); -} - -/** - * Generate a tooltip with details of a blueprint's effects - * @param {Object} translate The translate object - * @param {Object} blueprint The blueprint at the required grade - * @param {Array} engineers The engineers supplying this blueprint - * @param {string} grp The group of the module - * @param {Object} m The module to compare with - * @returns {Object} The react components - */ -export function blueprintTooltip(translate, blueprint, engineers, grp, m) { - const effects = []; - if (!blueprint || !blueprint.features) { - return undefined; - } - for (const feature in blueprint.features) { - const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); - const featureDef = Modifications.modifications[feature]; - if (!featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let lowerBound = blueprint.features[feature][0]; - let upperBound = blueprint.features[feature][1]; - if (featureDef.type === 'percentage') { - lowerBound = Math.round(lowerBound * 1000) / 10; - upperBound = Math.round(upperBound * 1000) / 10; - } - const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); - const upperIsBeneficial = isValueBeneficial(feature, upperBound); - if (m) { - // We have a module - add in the current value - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} - {lowerBound}{symbol} - {current}{symbol} - {upperBound}{symbol} - - ); - } else { - // We do not have a module, no value - effects.push( - - {translate(feature, grp)} - {lowerBound}{symbol} - {upperBound}{symbol} - - ); - } - } - } - if (m) { - // Because we have a module add in any benefits that aren't part of the primary blueprint - for (const feature in m.mods) { - if (!blueprint.features[feature]) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - - // We also add in any benefits from specials that aren't covered above - if (m.blueprint && m.blueprint.special) { - for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { - if (!blueprint.features[feature] && !m.mods.feature) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - } - } - - let components; - if (!m) { - components = []; - for (const component in blueprint.components) { - components.push( - - {translate(component)} - {blueprint.components[component]} - - ); - } - } - - let engineersList; - if (engineers) { - engineersList = []; - for (const engineer of engineers) { - engineersList.push( - - {engineer} - - ); - } - } - - return ( -
- - - - - - {m ? : null } - - - - - {effects} - -
{translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
- { components ? - - - - - - - - {components} - -
{translate('component')}{translate('amount')}
: null } - { engineersList ? - - - - - - - {engineersList} - -
{translate('engineers')}
: null } -
- ); -} - -/** - * Is this blueprint feature beneficial? - * @param {string} feature The name of the feature - * @param {array} values The value of the feature - * @returns {boolean} True if this feature is beneficial - */ -export function isBeneficial(feature, values) { - const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); - if (Modifications.modifications[feature].higherbetter) { - return !fact; - } else { - return fact; - } -} - -/** - * Is this feature value beneficial? - * @param {string} feature The name of the feature - * @param {number} value The value of the feature - * @returns {boolean} True if this value is beneficial - */ -export function isValueBeneficial(feature, value) { - if (Modifications.modifications[feature].higherbetter) { - return value > 0; - } else { - return value < 0; - } -} - -/** - * Is the change as shown beneficial? - * @param {string} feature The name of the feature - * @param {number} value The value of the feature as percentage change - * @returns True if the value is beneficial - */ -export function isChangeValueBeneficial(feature, value) { - let changeHigherBetter = STATS_FORMATTING[feature].higherbetter; - if (changeHigherBetter === undefined) { - return isValueBeneficial(feature, value); - } - - if (changeHigherBetter) { - return value > 0; - } else { - return value < 0; - } -} - -/** - * Get a blueprint with a given name and an optional module - * @param {string} name The name of the blueprint - * @param {Object} module The module for which to obtain this blueprint - * @returns {Object} The matching blueprint - */ -export function getBlueprint(name, module) { - // Start with a copy of the blueprint - const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); - const found = Modifications.blueprints[findMod(name)]; - if (!found || !found.fdname) { - return {}; - } - const blueprint = JSON.parse(JSON.stringify(found)); - return blueprint; -} - -/** - * Provide 'percent' primary modifications - * @param {Object} ship The ship for which to perform the modifications - * @param {Object} m The module for which to perform the modifications - * @param {Number} percent The percent to set values to of full. - */ -export function setPercent(ship, m, percent) { - ship.clearModifications(m); - // Pick given value as multiplier - const mult = percent / 100; - setQualityCB(m.blueprint, mult, (featureName, value) => ship.setModification(m, featureName, value)); -} - -/** - * Sets the blueprint quality and fires a callback for each property affected. - * @param {Object} blueprint The ship for which to perform the modifications - * @param {Number} quality The quality to apply - float number 0 to 1. - * @param {Function} cb The Callback to run for each property. Function (featureName, value) - */ -export function setQualityCB(blueprint, quality, cb) { - // Pick given value as multiplier - const features = blueprint.grades[blueprint.grade].features; - for (const featureName in features) { - let value; - if (Modifications.modifications[featureName].higherbetter) { - // Higher is better, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); - } else { - value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); - } - } else { - // Higher is worse, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); - } else { - value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); - } - } - - if (Modifications.modifications[featureName].type == 'percentage') { - value = value * 10000; - } else if (Modifications.modifications[featureName].type == 'numeric') { - value = value * 100; - } - - cb(featureName, value); - } -} - -/** - * Provide 'random' primary modifications - * @param {Object} ship The ship for which to perform the modifications - * @param {Object} m The module for which to perform the modifications - */ -export function setRandom(ship, m) { - // Pick a single value for our randomness - setPercent(ship, m, Math.random() * 100); -} - -/** - * Provide 'percent' primary query - * @param {Object} m The module for which to perform the query - * @returns {Number} percent The percentage indicator of current applied values. - */ -export function getPercent(m) { - let result = null; - const features = m.blueprint.grades[m.blueprint.grade].features; - for (const featureName in features) { - if (features[featureName][0] === features[featureName][1]) { - continue; - } - - let value = _getValue(m, featureName); - let mult; - if (Modifications.modifications[featureName].higherbetter) { - // Higher is better, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); - } else { - mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); - } - } else { - // Higher is worse, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); - } else { - mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); - } - } - - if (result && result != mult) { - return null; - } else if (result != mult) { - result = mult; - } - } - - return result; -} - -/** - * Query a feature value - * @param {Object} m The module for which to perform the query - * @param {string} featureName The feature being queried - * @returns {number} The value of the modification as a % - */ -function _getValue(m, featureName) { - if (Modifications.modifications[featureName].type == 'percentage') { - return m.getModValue(featureName, true) / 10000; - } else if (Modifications.modifications[featureName].type == 'numeric') { - return m.getModValue(featureName, true) / 100; - } else { - return m.getModValue(featureName, true); - } -} +import React from 'react'; +import { Modifications } from 'coriolis-data/dist'; +import { STATS_FORMATTING } from '../shipyard/StatsFormatting'; + +/** + * Generate a tooltip with details of a blueprint's specials + * @param {Object} translate The translate object + * @param {Object} blueprint The blueprint at the required grade + * @param {string} grp The group of the module + * @param {Object} m The module to compare with + * @param {string} specialName The name of the special + * @returns {Object} The react components + */ +export function specialToolTip(translate, blueprint, grp, m, specialName) { + const effects = []; + if (!blueprint || !blueprint.features) { + return undefined; + } + if (m) { + // We also add in any benefits from specials that aren't covered above + if (m.blueprint) { + for (const feature in Modifications.modifierActions[specialName]) { + // if (!blueprint.features[feature] && !m.mods.feature) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature) - m.getModValue(feature, true); + if (featureDef.type === 'percentage') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + } + + return ( +
+ + + {effects} + +
+
+ ); +} + +/** + * Generate a tooltip with details of a blueprint's effects + * @param {Object} translate The translate object + * @param {Object} blueprint The blueprint at the required grade + * @param {Array} engineers The engineers supplying this blueprint + * @param {string} grp The group of the module + * @param {Object} m The module to compare with + * @returns {Object} The react components + */ +export function blueprintTooltip(translate, blueprint, engineers, grp, m) { + const effects = []; + if (!blueprint || !blueprint.features) { + return undefined; + } + for (const feature in blueprint.features) { + const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); + const featureDef = Modifications.modifications[feature]; + if (!featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let lowerBound = blueprint.features[feature][0]; + let upperBound = blueprint.features[feature][1]; + if (featureDef.type === 'percentage') { + lowerBound = Math.round(lowerBound * 1000) / 10; + upperBound = Math.round(upperBound * 1000) / 10; + } + const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); + const upperIsBeneficial = isValueBeneficial(feature, upperBound); + if (m) { + // We have a module - add in the current value + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} + {lowerBound}{symbol} + {current}{symbol} + {upperBound}{symbol} + + ); + } else { + // We do not have a module, no value + effects.push( + + {translate(feature, grp)} + {lowerBound}{symbol} + {upperBound}{symbol} + + ); + } + } + } + if (m) { + // Because we have a module add in any benefits that aren't part of the primary blueprint + for (const feature in m.mods) { + if (!blueprint.features[feature]) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + + // We also add in any benefits from specials that aren't covered above + if (m.blueprint && m.blueprint.special) { + for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { + if (!blueprint.features[feature] && !m.mods.feature) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + } + } + + let components; + if (!m) { + components = []; + for (const component in blueprint.components) { + components.push( + + {translate(component)} + {blueprint.components[component]} + + ); + } + } + + let engineersList; + if (engineers) { + engineersList = []; + for (const engineer of engineers) { + engineersList.push( + + {engineer} + + ); + } + } + + return ( +
+ + + + + + {m ? : null } + + + + + {effects} + +
{translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
+ { components ? + + + + + + + + {components} + +
{translate('component')}{translate('amount')}
: null } + { engineersList ? + + + + + + + {engineersList} + +
{translate('engineers')}
: null } +
+ ); +} + +/** + * Is this blueprint feature beneficial? + * @param {string} feature The name of the feature + * @param {array} values The value of the feature + * @returns {boolean} True if this feature is beneficial + */ +export function isBeneficial(feature, values) { + const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); + if (Modifications.modifications[feature].higherbetter) { + return !fact; + } else { + return fact; + } +} + +/** + * Is this feature value beneficial? + * @param {string} feature The name of the feature + * @param {number} value The value of the feature + * @returns {boolean} True if this value is beneficial + */ +export function isValueBeneficial(feature, value) { + if (Modifications.modifications[feature].higherbetter) { + return value > 0; + } else { + return value < 0; + } +} + +/** + * Is the change as shown beneficial? + * @param {string} feature The name of the feature + * @param {number} value The value of the feature as percentage change + * @returns True if the value is beneficial + */ +export function isChangeValueBeneficial(feature, value) { + let changeHigherBetter = STATS_FORMATTING[feature].higherbetter; + if (changeHigherBetter === undefined) { + return isValueBeneficial(feature, value); + } + + if (changeHigherBetter) { + return value > 0; + } else { + return value < 0; + } +} + +/** + * Get a blueprint with a given name and an optional module + * @param {string} name The name of the blueprint + * @param {Object} module The module for which to obtain this blueprint + * @returns {Object} The matching blueprint + */ +export function getBlueprint(name, module) { + // Start with a copy of the blueprint + const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); + const found = Modifications.blueprints[findMod(name)]; + if (!found || !found.fdname) { + return {}; + } + const blueprint = JSON.parse(JSON.stringify(found)); + return blueprint; +} + +/** + * Provide 'percent' primary modifications + * @param {Object} ship The ship for which to perform the modifications + * @param {Object} m The module for which to perform the modifications + * @param {Number} percent The percent to set values to of full. + */ +export function setPercent(ship, m, percent) { + ship.clearModifications(m); + // Pick given value as multiplier + const mult = percent / 100; + setQualityCB(m.blueprint, mult, (featureName, value) => ship.setModification(m, featureName, value)); +} + +/** + * Sets the blueprint quality and fires a callback for each property affected. + * @param {Object} blueprint The ship for which to perform the modifications + * @param {Number} quality The quality to apply - float number 0 to 1. + * @param {Function} cb The Callback to run for each property. Function (featureName, value) + */ +export function setQualityCB(blueprint, quality, cb) { + // Pick given value as multiplier + const features = blueprint.grades[blueprint.grade].features; + for (const featureName in features) { + let value; + if (Modifications.modifications[featureName].higherbetter) { + // Higher is better, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); + } else { + value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); + } + } else { + // Higher is worse, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); + } else { + value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); + } + } + + if (Modifications.modifications[featureName].type == 'percentage') { + value = value * 10000; + } else if (Modifications.modifications[featureName].type == 'numeric') { + value = value * 100; + } + + cb(featureName, value); + } +} + +/** + * Provide 'random' primary modifications + * @param {Object} ship The ship for which to perform the modifications + * @param {Object} m The module for which to perform the modifications + */ +export function setRandom(ship, m) { + // Pick a single value for our randomness + setPercent(ship, m, Math.random() * 100); +} + +/** + * Provide 'percent' primary query + * @param {Object} m The module for which to perform the query + * @returns {Number} percent The percentage indicator of current applied values. + */ +export function getPercent(m) { + let result = null; + const features = m.blueprint.grades[m.blueprint.grade].features; + for (const featureName in features) { + if (features[featureName][0] === features[featureName][1]) { + continue; + } + + let value = _getValue(m, featureName); + let mult; + if (Modifications.modifications[featureName].higherbetter) { + // Higher is better, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); + } else { + mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); + } + } else { + // Higher is worse, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); + } else { + mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); + } + } + + if (result && result != mult) { + return null; + } else if (result != mult) { + result = mult; + } + } + + return result; +} + +/** + * Query a feature value + * @param {Object} m The module for which to perform the query + * @param {string} featureName The feature being queried + * @returns {number} The value of the modification as a % + */ +function _getValue(m, featureName) { + if (Modifications.modifications[featureName].type == 'percentage') { + return m.getModValue(featureName, true) / 10000; + } else if (Modifications.modifications[featureName].type == 'numeric') { + return m.getModValue(featureName, true) / 100; + } else { + return m.getModValue(featureName, true); + } +} diff --git a/src/app/utils/JournalUtils.js b/src/app/utils/JournalUtils.js index bb78993d..5b7cf0f5 100644 --- a/src/app/utils/JournalUtils.js +++ b/src/app/utils/JournalUtils.js @@ -6,6 +6,22 @@ import { Modules } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist'; import { getBlueprint, setQualityCB } from './BlueprintFunctions'; +/** + * Check if an imported module is valid + * @param {Object} module the module to check + * @param {Object} moduleType the type of module to check + * @return {boolean} true if the module is valid + */ +function _isValidImportedModule(module, moduleType) { + // First of all, has the _moduleFromFdName function returned 'null'? + if (!module){ + return false + } + else { + return true + } +} + /** * Obtain a module given its FD Name * @param {string} fdname the FD Name of the module @@ -98,49 +114,90 @@ export function shipFromLoadoutJSON(json) { if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'powerplant': - const powerplant = _moduleFromFdName(module.Item); + let powerplant = _moduleFromFdName(module.Item); + // Check the powerplant returned is valid + if (!_isValidImportedModule(powerplant, 'powerplant')) + { + powerplant = _moduleFromFdName('Int_Missing_Powerplant'); + module.Engineering = null; + } ship.use(ship.standard[0], powerplant, true); ship.standard[0].enabled = module.On; ship.standard[0].priority = module.Priority; if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'mainengines': - const thrusters = _moduleFromFdName(module.Item); + let thrusters = _moduleFromFdName(module.Item); + // Check the thrusters returned is valid + if (!_isValidImportedModule(thrusters, 'thrusters')) + { + thrusters = _moduleFromFdName('Int_Missing_Engine'); + module.Engineering = null; + } ship.use(ship.standard[1], thrusters, true); ship.standard[1].enabled = module.On; ship.standard[1].priority = module.Priority; if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'frameshiftdrive': - const frameshiftdrive = _moduleFromFdName(module.Item); + let frameshiftdrive = _moduleFromFdName(module.Item); + // Check the frameshiftdrive returned is valid + if (!_isValidImportedModule(frameshiftdrive, 'frameshiftdrive')) + { + frameshiftdrive = _moduleFromFdName('Int_Missing_Hyperdrive'); + module.Engineering = null; + } ship.use(ship.standard[2], frameshiftdrive, true); ship.standard[2].enabled = module.On; ship.standard[2].priority = module.Priority; if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'lifesupport': - const lifesupport = _moduleFromFdName(module.Item); + let lifesupport = _moduleFromFdName(module.Item); + // Check the lifesupport returned is valid + if (!_isValidImportedModule(lifesupport, 'lifesupport')) + { + lifesupport = _moduleFromFdName('Int_Missing_LifeSupport'); + module.Engineering = null; + } ship.use(ship.standard[3], lifesupport, true); ship.standard[3].enabled = module.On === true; ship.standard[3].priority = module.Priority; if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'powerdistributor': - const powerdistributor = _moduleFromFdName(module.Item); + let powerdistributor = _moduleFromFdName(module.Item); + // Check the powerdistributor returned is valid + if (!_isValidImportedModule(powerdistributor, 'powerdistributor')) + { + powerdistributor = _moduleFromFdName('Int_Missing_PowerDistributor'); + module.Engineering = null; + } ship.use(ship.standard[4], powerdistributor, true); ship.standard[4].enabled = module.On; ship.standard[4].priority = module.Priority; if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'radar': - const sensors = _moduleFromFdName(module.Item); + let sensors = _moduleFromFdName(module.Item); + // Check the sensors returned is valid + if (!_isValidImportedModule(sensors, 'sensors')) + { + sensors = _moduleFromFdName('Int_Missing_Sensors'); + module.Engineering = null; + } ship.use(ship.standard[5], sensors, true); ship.standard[5].enabled = module.On; ship.standard[5].priority = module.Priority; if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'fueltank': - const fueltank = _moduleFromFdName(module.Item); + let fueltank = _moduleFromFdName(module.Item); + // Check the fueltank returned is valid + if (!_isValidImportedModule(fueltank, 'fueltank')) + { + fueltank = _moduleFromFdName('Int_Missing_FuelTank'); + } ship.use(ship.standard[6], fueltank, true); ship.standard[6].enabled = true; ship.standard[6].priority = 0; @@ -170,10 +227,27 @@ export function shipFromLoadoutJSON(json) { // This can happen with old imports that don't contain new hardpoints } else { hardpoint = _moduleFromFdName(hardpointSlot.Item); - ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); - ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; - ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; - modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot }); + // Check the hardpoint module returned is valid + if (!_isValidImportedModule(hardpoint, 'hardpoint')){ + // Check if it's a Utility or Hardpoint + if (hardpointSlot.Slot.toLowerCase().search(/tiny/)) + { + // Use the missing_hardpoint module 'Missing Hardpoint' which will inform the user that the module is missing + hardpoint = _moduleFromFdName('Hpt_Missing_Hardpoint'); + } + else { + // Use the missing_hardpoint module 'Missing Utility' which will inform the user that the module is missing + hardpoint = _moduleFromFdName('Hpt_Missing_Utility'); + } + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; + ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; + } else { + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; + ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; + modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot }); + } } hardpointArrayNum++; } @@ -187,13 +261,17 @@ export function shipFromLoadoutJSON(json) { continue; } const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false; + const isPlanetary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'PlanetaryApproachSuite' : false; - // The internal slot might be a standard or a military slot. Military slots have a different naming system + // The internal slot might be a standard or a military slot, or a planetary slot. Military and Planetary slots have a different naming system let internalSlot = null; if (isMilitary) { const internalName = 'Military0' + militarySlotNum; internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); militarySlotNum++; + } else if (isPlanetary) { + const internalName = 'PlanetaryApproachSuite'; + internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); } else { // Slot numbers are not contiguous so handle skips. for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) { @@ -212,11 +290,22 @@ export function shipFromLoadoutJSON(json) { // This can happen with old imports that don't contain new slots } else { const internalJson = internalSlot; - const internal = _moduleFromFdName(internalJson.Item); - ship.use(ship.internal[i], internal, true); - ship.internal[i].enabled = internalJson.On === true; - ship.internal[i].priority = internalJson.Priority; - modsToAdd.push({ coriolisMod: internal, json: internalSlot }); + let internal = _moduleFromFdName(internalJson.Item); + // Check the internal module returned is valid + if (!_isValidImportedModule(internal, 'internal')) + { + internal = _moduleFromFdName('Int_Missing_Module'); + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.On === true; + ship.internal[i].priority = internalJson.Priority; + //throw 'Unknown internal module: "' + module.Item + '"'; + } + else { + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.On === true; + ship.internal[i].priority = internalJson.Priority; + modsToAdd.push({ coriolisMod: internal, json: internalSlot }); + } } } From 3156b6a53363734c6ded6dce47364000082a7d8c Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 17 May 2024 08:45:15 +0100 Subject: [PATCH 04/42] Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo --- src/app/components/AvailableModulesMenu.jsx | 8 ++++++-- src/app/i18n/en.json | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index de4a6e17..1ae53a44 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -39,13 +39,17 @@ const GRPCAT = { 'ml': 'lasers', 'c': 'projectiles', 'mc': 'projectiles', + 'advmc': 'projectiles', 'axmc': 'experimental', + 'axmce': 'experimental', + 'ntp': 'experimental', 'fc': 'projectiles', 'rfl': 'experimental', 'pa': 'projectiles', 'rg': 'projectiles', 'mr': 'ordnance', 'axmr': 'experimental', + 'axmre': 'experimental', 'rcpl': 'experimental', 'dtl': 'experimental', 'tbsc': 'experimental', @@ -104,7 +108,7 @@ const CATEGORIES = { // Hardpoints 'lasers': ['pl', 'ul', 'bl'], - 'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'], + 'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'], 'ordnance': ['mr', 'tp', 'nl'], // Utilities 'sb': ['sb'], @@ -113,7 +117,7 @@ const CATEGORIES = { 'defence': ['ch', 'po', 'ec'], 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners // Experimental - 'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], + 'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], 'weapon stabilizers': ['ews'], // Guardian 'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'], diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index fd6b3f0b..e232373f 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -109,11 +109,14 @@ "kw": "Kill Warrant Scanner", "ls": "Life Support", "mc": "Multi-cannon", + "advmc": "Multi-cannon (Advanced)", "axmc": "AX Multi-cannon", + "axmce": "AX Multi-cannon (Enhanced)", "ml": "Mining Laser", "mlc": "Multi Limpet Controller", "mr": "Missile Rack", "axmr": "AX Missile Rack", + "axmre": "AX Missile Rack (Enhanced)", "ews": "Experimental Weapon Stabilizer", "mrp": "Module Reinforcement Package", "nl": "Mine Launcher", @@ -159,6 +162,7 @@ "sua": "Supercruise Assist", "t": "thrusters", "tp": "Torpedo Pylon", + "ntp": "Nanite Torpedo Pylon", "ul": "Burst Laser", "Send To EDEngineer": "Send To EDEngineer", "Send To EDOMH": "Send To EDOMH", From 5c8ff57d16448ffd8a805f29ecdf9485ab6ac9ff Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 17 May 2024 15:24:29 +0100 Subject: [PATCH 05/42] Changes as per comments on the PR --- src/app/components/AvailableModulesMenu.jsx | 2 +- src/app/components/StandardSlot.jsx | 1 + src/app/shipyard/ModuleUtils.js | 9 --------- 3 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index 110953cd..3f2637c8 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -312,7 +312,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { let itemsOnThisRow = 0; for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; - if (ModuleUtils.isMissingModule(m.info)) { + if (m.grp == 'mh' || m.grp == 'mm') { // If this is a missing module, skip it continue; } diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 70bb57ae..09819009 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -93,6 +93,7 @@ export default class StandardSlot extends TranslatedComponent { this._modificationsSelected = false; } + // If this is a missing module, therefore has the 'info' field, set the warning value on the module to be true when loaded. if (m.info) { warning = () => true; } diff --git a/src/app/shipyard/ModuleUtils.js b/src/app/shipyard/ModuleUtils.js index 209e24b7..276de3c1 100755 --- a/src/app/shipyard/ModuleUtils.js +++ b/src/app/shipyard/ModuleUtils.js @@ -322,15 +322,6 @@ export function isShieldGenerator(g) { return g == 'sg' || g == 'psg' || g == 'bsg'; } -/** - * Determine if a module group is a missing module - * @param {String} info Module Group name - * @return {Boolean} True if the group is a missing module - */ -export function isMissingModule(info) { - return info == 'Not in Coriolis yet. Check GitHub issues. Add Issue if needed.'; -} - /** * Creates a new ModuleSet that contains all available modules * that the specified ship is eligible to use. From f885fde04f07dacf3a7d7154551c57c898a2d2b1 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 24 May 2024 16:03:03 +0100 Subject: [PATCH 06/42] Fix changed files issue (#3) * Copied de.js contents to new file de-fix.js * Copied de.js contents back from de-fix.js * Copied contents of ko.js to ko-fix.js * Copied ko.js contents back from ko-fix.js * Copied contents from BlueprintFunctions.js to BlueprintFunctions-fix.js * Copied contents back from BlueprintFunctions-fix.js to BlueprintFunctions.js * Copied contents of LineChart.jsx to LineChart-fix.jsx * Copied contents back from LineChart-fix.jsx to LineChart.jsx * Copied contents of PieChart.jsx to PieChart-fix.jsx * Copied contents back from PieChart-fix.jsx to PieChart.jsx * Copied contents from Slider.jsx to Slider-fix.jsx * Copied contents back from Slider-fix.jsx to Slider.jsx * Copied contents from VerticalBarChart.jsx to VerticalBarChart-fix.jsx * Copied contents back from VerticalBarChart-fix.jsx to VerticalBarChart.jsx * Deleting 'fix' files --- src/app/components/LineChart.jsx | 562 +++++++-------- src/app/components/PieChart.jsx | 184 ++--- src/app/components/Slider.jsx | 772 ++++++++++----------- src/app/components/VerticalBarChart.jsx | 160 ++--- src/app/i18n/de.js | 32 +- src/app/i18n/ko.js | 32 +- src/app/utils/BlueprintFunctions.js | 870 ++++++++++++------------ 7 files changed, 1306 insertions(+), 1306 deletions(-) diff --git a/src/app/components/LineChart.jsx b/src/app/components/LineChart.jsx index b4113ee6..76078aa6 100644 --- a/src/app/components/LineChart.jsx +++ b/src/app/components/LineChart.jsx @@ -1,281 +1,281 @@ -import React from 'react'; -import PropTypes from 'prop-types'; -import ContainerDimensions from 'react-container-dimensions'; -import * as d3 from 'd3'; -import TranslatedComponent from './TranslatedComponent'; - -const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 }; - -/** - * Line Chart - */ -export default class LineChart extends TranslatedComponent { - - static defaultProps = { - code: '', - xMin: 0, - yMin: 0, - points: 20, - colors: ['#ff8c0d'], - aspect: 0.5 - }; - - static propTypes = { - func: PropTypes.func.isRequired, - xLabel: PropTypes.string.isRequired, - xMin: PropTypes.number, - xMax: PropTypes.number.isRequired, - xUnit: PropTypes.string.isRequired, - xMark: PropTypes.number, - yLabel: PropTypes.string.isRequired, - yMin: PropTypes.number, - yMax: PropTypes.number.isRequired, - yUnit: PropTypes.string, - series: PropTypes.array, - colors: PropTypes.array, - points: PropTypes.number, - aspect: PropTypes.number, - code: PropTypes.string, - }; - - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ - constructor(props, context) { - super(props); - - this._updateDimensions = this._updateDimensions.bind(this); - this._updateSeries = this._updateSeries.bind(this); - this._tooltip = this._tooltip.bind(this); - this._showTip = this._showTip.bind(this); - this._hideTip = this._hideTip.bind(this); - this._moveTip = this._moveTip.bind(this); - - const series = props.series; - - let xScale = d3.scaleLinear(); - let yScale = d3.scaleLinear(); - let xAxisScale = d3.scaleLinear(); - - this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0); - this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0); - - this.state = { - xScale, - xAxisScale, - yScale, - tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), - }; - } - - /** - * Update tooltip content - * @param {number} xPos x coordinate - * @param {number} width current container width - */ - _tooltip(xPos, width) { - let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; - let { xScale, yScale } = this.state; - let { formats, translate } = this.context.language; - let x0 = xScale.invert(xPos), - y0 = func(x0), - tips = this.tipContainer, - yTotal = 0, - flip = (xPos / width > 0.50), - tipWidth = 0, - tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; - - - xPos = xScale(x0); // Clamp xPos - - tips.selectAll('text.text-tip.y').text(function(d, i) { - let yVal = series ? y0[series[i]] : y0; - yTotal += yVal; - return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal); - }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : ''); - - tips.selectAll('text').each(function() { - if (this.getBBox().width > tipWidth) { - tipWidth = Math.ceil(this.getBBox().width); - } - }); - - let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2)); - - tipWidth += 8; - tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); - tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); - tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit); - tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); - this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); - } - - /** - * Update dimensions based on properties and scale - * @param {Object} props React Component properties - * @param {number} scale size ratio / scale - * @param {number} width current width of the container - * @returns {Object} calculated dimensions - */ - _updateDimensions(props, scale, width) { - const { xMax, xMin, yMin, yMax } = props; - const innerWidth = width - MARGIN.left - MARGIN.right; - const outerHeight = Math.round(width * props.aspect); - const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; - - this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true); - this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true); - this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility - return { innerWidth, outerHeight, innerHeight }; - } - - /** - * Show tooltip - * @param {SyntheticEvent} e Event - */ - _showTip(e) { - e.preventDefault(); - this.tipContainer.style('display', null); - this.markersContainer.style('display', null); - this._moveTip(e); - } - - /** - * Move and update tooltip - * @param {SyntheticEvent} e Event - * @param {number} width current container width - */ - _moveTip(e, width) { - let clientX = e.touches ? e.touches[0].clientX : e.clientX; - this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width); - } - - /** - * Hide tooltip - * @param {SyntheticEvent} e Event - */ - _hideTip(e) { - e.preventDefault(); - this.tipContainer.style('display', 'none'); - this.markersContainer.style('display', 'none'); - } - - /** - * Update series generated from props - * @param {Object} props React Component properties - * @param {Object} state React Component state - */ - _updateSeries(props, state) { - let { func, xMin, xMax, series, points } = props; - let delta = (xMax - xMin) / points; - let seriesData = new Array(points); - - if (delta) { - seriesData = new Array(points); - for (let i = 0, x = xMin; i < points; i++) { - seriesData[i] = [x, func(x)]; - x += delta; - } - seriesData[points - 1] = [xMax, func(xMax)]; - } else { - let yVal = func(xMin); - seriesData = [[0, yVal], [1, yVal]]; - } - - const markerElems = []; - const detailElems = []; - const seriesLines = []; - for (let i = 0, l = series ? series.length : 1; i < l; i++) { - const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]); - seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor)); - detailElems.push(); - markerElems.push(); - } - - const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8)); - - this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight }); - } - - /** - * Update dimensions and series data based on props and context. - */ - componentWillMount() { - this._updateSeries(this.props, this.state); - } - - /** - * Update state based on property and context changes - * @param {Object} nextProps Incoming/Next properties - * @param {Object} nextContext Incoming/Next conext - */ - componentWillReceiveProps(nextProps, nextContext) { - const props = this.props; - - if (props.code != nextProps.code) { - this._updateSeries(nextProps, this.state); - } - } - - /** - * Render the chart - * @return {React.Component} Chart SVG - */ - render() { - return ( - - { ({ width, height }) => { - const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height); - const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; - const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; - const lines = seriesLines.map((line, i) => ).reverse(); - - const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; - const xmark = xMark ? : ''; - return ( -
- - - {xmark} - {lines} - d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> - - {xLabel} - ({xUnit}) - - - d3.select(elem).call(this.yAxis)}> - - {yLabel} - { yUnit && ({yUnit}) } - - - this.tipContainer = d3.select(g)} style={{ display: 'none' }}> - - {detailElems} - - this.markersContainer = d3.select(g)} style={{ display: 'none' }}> - {markerElems} - - this._moveTip(e, width)} - onTouchMove={e => this._moveTip(e, width)} - /> - - -
- ); - }} -
- ); - } -} +import React from 'react'; +import PropTypes from 'prop-types'; +import ContainerDimensions from 'react-container-dimensions'; +import * as d3 from 'd3'; +import TranslatedComponent from './TranslatedComponent'; + +const MARGIN = { top: 15, right: 20, bottom: 35, left: 60 }; + +/** + * Line Chart + */ +export default class LineChart extends TranslatedComponent { + + static defaultProps = { + code: '', + xMin: 0, + yMin: 0, + points: 20, + colors: ['#ff8c0d'], + aspect: 0.5 + }; + + static propTypes = { + func: PropTypes.func.isRequired, + xLabel: PropTypes.string.isRequired, + xMin: PropTypes.number, + xMax: PropTypes.number.isRequired, + xUnit: PropTypes.string.isRequired, + xMark: PropTypes.number, + yLabel: PropTypes.string.isRequired, + yMin: PropTypes.number, + yMax: PropTypes.number.isRequired, + yUnit: PropTypes.string, + series: PropTypes.array, + colors: PropTypes.array, + points: PropTypes.number, + aspect: PropTypes.number, + code: PropTypes.string, + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + this._updateDimensions = this._updateDimensions.bind(this); + this._updateSeries = this._updateSeries.bind(this); + this._tooltip = this._tooltip.bind(this); + this._showTip = this._showTip.bind(this); + this._hideTip = this._hideTip.bind(this); + this._moveTip = this._moveTip.bind(this); + + const series = props.series; + + let xScale = d3.scaleLinear(); + let yScale = d3.scaleLinear(); + let xAxisScale = d3.scaleLinear(); + + this.xAxis = d3.axisBottom(xAxisScale).tickSizeOuter(0); + this.yAxis = d3.axisLeft(yScale).ticks(6).tickSizeOuter(0); + + this.state = { + xScale, + xAxisScale, + yScale, + tipHeight: 2 + (1.2 * (series ? series.length : 0.8)), + }; + } + + /** + * Update tooltip content + * @param {number} xPos x coordinate + * @param {number} width current container width + */ + _tooltip(xPos, width) { + let { xLabel, yLabel, xUnit, yUnit, func, series } = this.props; + let { xScale, yScale } = this.state; + let { formats, translate } = this.context.language; + let x0 = xScale.invert(xPos), + y0 = func(x0), + tips = this.tipContainer, + yTotal = 0, + flip = (xPos / width > 0.50), + tipWidth = 0, + tipHeightPx = tips.selectAll('rect').node().getBoundingClientRect().height; + + + xPos = xScale(x0); // Clamp xPos + + tips.selectAll('text.text-tip.y').text(function(d, i) { + let yVal = series ? y0[series[i]] : y0; + yTotal += yVal; + return (series ? translate(series[i]) : '') + ' ' + formats.f2(yVal); + }).append('tspan').attr('class', 'metric').text(yUnit ? ' ' + yUnit : ''); + + tips.selectAll('text').each(function() { + if (this.getBBox().width > tipWidth) { + tipWidth = Math.ceil(this.getBBox().width); + } + }); + + let tipY = Math.floor(yScale(yTotal / (series ? series.length : 1)) - (tipHeightPx / 2)); + + tipWidth += 8; + tips.attr('transform', 'translate(' + xPos + ',' + tipY + ')'); + tips.selectAll('text.text-tip').attr('x', flip ? -12 : 12).style('text-anchor', flip ? 'end' : 'start'); + tips.selectAll('text.text-tip.x').text(formats.f2(x0)).append('tspan').attr('class', 'metric').text(' ' + xUnit); + tips.selectAll('rect').attr('width', tipWidth + 4).attr('x', flip ? -tipWidth - 12 : 8).attr('y', 0).style('text-anchor', flip ? 'end' : 'start'); + this.markersContainer.selectAll('circle').attr('cx', xPos).attr('cy', (d, i) => yScale(series ? y0[series[i]] : y0)); + } + + /** + * Update dimensions based on properties and scale + * @param {Object} props React Component properties + * @param {number} scale size ratio / scale + * @param {number} width current width of the container + * @returns {Object} calculated dimensions + */ + _updateDimensions(props, scale, width) { + const { xMax, xMin, yMin, yMax } = props; + const innerWidth = width - MARGIN.left - MARGIN.right; + const outerHeight = Math.round(width * props.aspect); + const innerHeight = outerHeight - MARGIN.top - MARGIN.bottom; + + this.state.xScale.range([0, innerWidth]).domain([xMin, xMax || 1]).clamp(true); + this.state.xAxisScale.range([0, innerWidth]).domain([xMin, xMax]).clamp(true); + this.state.yScale.range([innerHeight, 0]).domain([yMin, yMax + (yMax - yMin) * 0.1]); // 10% higher than maximum value for tooltip visibility + return { innerWidth, outerHeight, innerHeight }; + } + + /** + * Show tooltip + * @param {SyntheticEvent} e Event + */ + _showTip(e) { + e.preventDefault(); + this.tipContainer.style('display', null); + this.markersContainer.style('display', null); + this._moveTip(e); + } + + /** + * Move and update tooltip + * @param {SyntheticEvent} e Event + * @param {number} width current container width + */ + _moveTip(e, width) { + let clientX = e.touches ? e.touches[0].clientX : e.clientX; + this._tooltip(Math.round(clientX - e.currentTarget.getBoundingClientRect().left), width); + } + + /** + * Hide tooltip + * @param {SyntheticEvent} e Event + */ + _hideTip(e) { + e.preventDefault(); + this.tipContainer.style('display', 'none'); + this.markersContainer.style('display', 'none'); + } + + /** + * Update series generated from props + * @param {Object} props React Component properties + * @param {Object} state React Component state + */ + _updateSeries(props, state) { + let { func, xMin, xMax, series, points } = props; + let delta = (xMax - xMin) / points; + let seriesData = new Array(points); + + if (delta) { + seriesData = new Array(points); + for (let i = 0, x = xMin; i < points; i++) { + seriesData[i] = [x, func(x)]; + x += delta; + } + seriesData[points - 1] = [xMax, func(xMax)]; + } else { + let yVal = func(xMin); + seriesData = [[0, yVal], [1, yVal]]; + } + + const markerElems = []; + const detailElems = []; + const seriesLines = []; + for (let i = 0, l = series ? series.length : 1; i < l; i++) { + const yAccessor = series ? function(d) { return state.yScale(d[1][this]); }.bind(series[i]) : (d) => state.yScale(d[1]); + seriesLines.push(d3.line().x((d, i) => this.state.xScale(d[0])).y(yAccessor)); + detailElems.push(); + markerElems.push(); + } + + const tipHeight = 2 + (1.2 * (seriesLines ? seriesLines.length : 0.8)); + + this.setState({ markerElems, detailElems, seriesLines, seriesData, tipHeight }); + } + + /** + * Update dimensions and series data based on props and context. + */ + componentWillMount() { + this._updateSeries(this.props, this.state); + } + + /** + * Update state based on property and context changes + * @param {Object} nextProps Incoming/Next properties + * @param {Object} nextContext Incoming/Next conext + */ + componentWillReceiveProps(nextProps, nextContext) { + const props = this.props; + + if (props.code != nextProps.code) { + this._updateSeries(nextProps, this.state); + } + } + + /** + * Render the chart + * @return {React.Component} Chart SVG + */ + render() { + return ( + + { ({ width, height }) => { + const { innerWidth, outerHeight, innerHeight } = this._updateDimensions(this.props, this.context.sizeRatio, width, height); + const { xMin, xMax, xLabel, yLabel, xUnit, yUnit, xMark, colors } = this.props; + const { tipHeight, detailElems, markerElems, seriesData, seriesLines } = this.state; + const lines = seriesLines.map((line, i) => ).reverse(); + + const markX = xMark ? innerWidth * (xMark - xMin) / (xMax - xMin) : 0; + const xmark = xMark ? : ''; + return ( +
+ + + {xmark} + {lines} + d3.select(elem).call(this.xAxis)} transform={`translate(0,${innerHeight})`}> + + {xLabel} + ({xUnit}) + + + d3.select(elem).call(this.yAxis)}> + + {yLabel} + { yUnit && ({yUnit}) } + + + this.tipContainer = d3.select(g)} style={{ display: 'none' }}> + + {detailElems} + + this.markersContainer = d3.select(g)} style={{ display: 'none' }}> + {markerElems} + + this._moveTip(e, width)} + onTouchMove={e => this._moveTip(e, width)} + /> + + +
+ ); + }} +
+ ); + } +} diff --git a/src/app/components/PieChart.jsx b/src/app/components/PieChart.jsx index 1c9ccac8..6aa29fd4 100644 --- a/src/app/components/PieChart.jsx +++ b/src/app/components/PieChart.jsx @@ -1,92 +1,92 @@ -import React, { Component } from 'react'; -import PropTypes from 'prop-types'; -import ContainerDimensions from 'react-container-dimensions'; -import * as d3 from 'd3'; - -const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; -const LABEL_COLOUR = '#000000'; - -/** - * A pie chart - */ -export default class PieChart extends Component { - - static propTypes = { - data : PropTypes.array.isRequired - }; - - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ - constructor(props, context) { - super(props); - - this.pie = d3.pie().value((d) => d.value); - this.colors = CORIOLIS_COLOURS; - this.arc = d3.arc(); - this.arc.innerRadius(0); - } - - - /** - * Generate a slice of the pie chart - * @param {Object} d the data for this slice - * @param {number} i the index of this slice - * @param {number} width the current width of the parent container - * @returns {Object} the SVG for the slice - */ - sliceGenerator(d, i, width) { - if (!d || d.value == 0) { - // Ignore 0 values - return null; - } - - const { data } = this.props; - - // Push the labels further out from the centre of the slice - let [labelX, labelY] = this.arc.centroid(d); - const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`; - - // Put the keys in a line with equal spacing - const nonZeroItems = data.filter(d => d.value != 0).length; - const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1; - const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5); - const keyTranslate = `translate(${keyX}, ${width * 0.45})`; - - return ( - - - {d.value} - {d.data.label} - - ); - } - - /** - * Render the component - * @returns {object} Markup - */ - render() { - return ( - - { ({ width }) => { - const pie = this.pie(this.props.data), - translate = `translate(${width / 2}, ${width * 0.4})`; - - this.arc.outerRadius(width * 0.4); - return ( -
- - - {pie.map((d, i) => this.sliceGenerator(d, i, width))} - - -
- ); - }} -
- ); - } -} +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import ContainerDimensions from 'react-container-dimensions'; +import * as d3 from 'd3'; + +const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; +const LABEL_COLOUR = '#000000'; + +/** + * A pie chart + */ +export default class PieChart extends Component { + + static propTypes = { + data : PropTypes.array.isRequired + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + this.pie = d3.pie().value((d) => d.value); + this.colors = CORIOLIS_COLOURS; + this.arc = d3.arc(); + this.arc.innerRadius(0); + } + + + /** + * Generate a slice of the pie chart + * @param {Object} d the data for this slice + * @param {number} i the index of this slice + * @param {number} width the current width of the parent container + * @returns {Object} the SVG for the slice + */ + sliceGenerator(d, i, width) { + if (!d || d.value == 0) { + // Ignore 0 values + return null; + } + + const { data } = this.props; + + // Push the labels further out from the centre of the slice + let [labelX, labelY] = this.arc.centroid(d); + const labelTranslate = `translate(${labelX * 1.5}, ${labelY * 1.5})`; + + // Put the keys in a line with equal spacing + const nonZeroItems = data.filter(d => d.value != 0).length; + const thisItemIndex = data.slice(0, i + 1).filter(d => d.value != 0).length - 1; + const keyX = -width / 2 + (width / nonZeroItems) * (thisItemIndex + 0.5); + const keyTranslate = `translate(${keyX}, ${width * 0.45})`; + + return ( + + + {d.value} + {d.data.label} + + ); + } + + /** + * Render the component + * @returns {object} Markup + */ + render() { + return ( + + { ({ width }) => { + const pie = this.pie(this.props.data), + translate = `translate(${width / 2}, ${width * 0.4})`; + + this.arc.outerRadius(width * 0.4); + return ( +
+ + + {pie.map((d, i) => this.sliceGenerator(d, i, width))} + + +
+ ); + }} +
+ ); + } +} diff --git a/src/app/components/Slider.jsx b/src/app/components/Slider.jsx index d36ca968..0e5c5696 100644 --- a/src/app/components/Slider.jsx +++ b/src/app/components/Slider.jsx @@ -1,386 +1,386 @@ -import React from 'react'; -import PropTypes from 'prop-types'; - -const MARGIN_LR = 8; // Left/ Right margin - -/** - * Horizontal Slider - */ -export default class Slider extends React.Component { - - static defaultProps = { - axis: false, - min: 0, - max: 1, - scale: 1 // SVG render scale - }; - - static propTypes = { - axis: PropTypes.bool, - axisUnit: PropTypes.string,// units (T, M, etc.) - max: PropTypes.number, - min: PropTypes.number, - onChange: PropTypes.func.isRequired,// function which determins percent value - onResize: PropTypes.func, - percent: PropTypes.number.isRequired,// value of slider - scale: PropTypes.number - }; - - /** - * Constructor - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - this._down = this._down.bind(this); - this._move = this._move.bind(this); - this._up = this._up.bind(this); - this._keyup = this._keyup.bind(this); - this._keydown = this._keydown.bind(this); - this._touchstart = this._touchstart.bind(this); - this._touchend = this._touchend.bind(this); - - this._updatePercent = this._updatePercent.bind(this); - this._updateDimensions = this._updateDimensions.bind(this); - - this.state = { width: 0 }; - } - - /** - * On Mouse/Touch down handler - * @param {SyntheticEvent} event Event - */ - _down(event) { - let rect = event.currentTarget.getBoundingClientRect(); - this.left = rect.left; - this.width = rect.width; - this._move(event); - this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); - } - - /** - * Update the slider percentage on move - * @param {SyntheticEvent} event Event - */ - _move(event) { - if(this.width !== null && this.left != null) { - let clientX = event.touches ? event.touches[0].clientX : event.clientX; - event.preventDefault(); - this._updatePercent(clientX - this.left, this.width); - } - } - - /** - * On Mouse/Touch up handler - * @param {Event} event DOM Event - */ - _up(event) { - this.sliderInputBox.sliderVal.focus(); - clearTimeout(this.touchStartTimer); - event.preventDefault(); - this.left = null; - this.width = null; - } - - - /** - * Key up handler for keyboard. - * display the number field then set focus to it - * when "Enter" key is pressed - * @param {Event} event Keyboard event - */ - _keyup(event) { - switch (event.key) { - case 'Enter': - event.preventDefault(); - this.sliderInputBox._setDisplay('block'); - return; - default: - return; - } - } - /** - * Key down handler - * increment slider position by +/- 1 when right/left arrow key is pressed or held - * @param {Event} event Keyboard even - */ - _keydown(event) { - let newVal = this.props.percent * this.props.max; - switch (event.key) { - case 'ArrowRight': - newVal += 1; - if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max); - return; - case 'ArrowLeft': - newVal -= 1; - if (newVal >= 0) this.props.onChange(newVal / this.props.max); - return; - default: - return; - } - } - - /** - * Touch start handler - * @param {Event} event DOM Event - * - */ - _touchstart(event) { - this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); - } - - /** - * Touch end handler - * @param {Event} event DOM Event - * - */ - _touchend(event) { - this.sliderInputBox.sliderVal.focus(); - clearTimeout(this.touchStartTimer); - } - - /** - * Determine if the user is still dragging - * @param {SyntheticEvent} event Event - */ - _enter(event) { - if(event.buttons !== 1) { - this.left = null; - this.width = null; - } - } - - /** - * Update the slider percentage - * @param {number} pos Slider drag position - * @param {number} width Slider width - * @param {Event} event DOM Event - */ - _updatePercent(pos, width) { - this.props.onChange(Math.min(Math.max(pos / width, 0), 1)); - } - - /** - * Update dimenions from rendered DOM - */ - _updateDimensions() { - this.setState({ - outerWidth: this.node.getBoundingClientRect().width - }); - } - - /** - * Add listeners when about to mount - */ - componentWillMount() { - if (this.props.onResize) { - this.resizeListener = this.props.onResize(this._updateDimensions); - } - } - - /** - * Trigger DOM updates on mount - */ - componentDidMount() { - this._updateDimensions(); - } - - /** - * Remove listeners on unmount - */ - componentWillUnmount() { - if (this.resizeListener) { - this.resizeListener.remove(); - } - } - - /** - * Render the slider - * @return {React.Component} The slider - */ - render() { - let outerWidth = this.state.outerWidth; - let { axis, axisUnit, min, max, scale } = this.props; - let style = { - width: '100%', - height: axis ? '2.5em' : '1.5em', - boxSizing: 'border-box' - }; - if (!outerWidth) { - return this.node = node} />; - } - let margin = MARGIN_LR * scale; - let width = outerWidth - (margin * 2); - let pctPos = width * this.props.percent; - return
this.node = node} tabIndex="0"> - - - - - {axis && - {min + axisUnit} - {(min + max / 2) + axisUnit} - {max + axisUnit} - } - - this.sliderInputBox = tb} - onChange={this.props.onChange} - percent={this.props.percent} - axisUnit={this.props.axisUnit} - scale={this.props.scale} - max={this.props.max} - /> -
; - } -} -/** - * New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android) - **/ -class TextInputBox extends React.Component { - static propTypes = { - axisUnit: PropTypes.string,// units (T, M, etc.) - max: PropTypes.number, - onChange: PropTypes.func.isRequired,// function which determins percent value - percent: PropTypes.number.isRequired,// value of slider - scale: PropTypes.number - }; - /** - * Determine if the user is still dragging - * @param {Object} props React Component properties - */ - constructor(props) { - super(props); - this._handleFocus = this._handleFocus.bind(this); - this._handleBlur = this._handleBlur.bind(this); - this._handleChange = this._handleChange.bind(this); - this._keyup = this._keyup.bind(this); - this.state = this._getInitialState(); - } - /** - * Update input value if slider changes will change props/state - * @param {Object} nextProps React Component properites - * @param {Object} nextState React Component state values - */ - componentWillReceiveProps(nextProps, nextState) { - let nextValue = nextProps.percent * nextProps.max; - // See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form - if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) { - this.setState({ inputValue: nextValue }); - } - } - /** - * Update slider textbox visibility/values if changes are made to slider - * @param {Object} prevProps React Component properites - * @param {Object} prevState React Component state values - */ - componentDidUpdate(prevProps, prevState) { - if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') { - this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10); - } - if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) { - // they chose a different module - this.setState({ inputValue: this.props.max }); - } - if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) { - this.props.onChange(this.state.inputValue / this.props.max); - } - } - /** - * Set initial state for the textbox. - * We may want to rethink this to - * try and make it a stateless component - * @returns {object} React state object with initial values set - */ - _getInitialState() { - return { - divStyle: { display:'none' }, - inputStyle: { width:'4em' }, - labelStyle: { marginLeft: '.1em' }, - maxLength:5, - size:5, - min:0, - tabIndex:-1, - type:'number', - readOnly: true, - inputValue: this.props.percent * this.props.max - }; - } - /** - * - * @param {string} val block or none - */ - _setDisplay(val) { - this.setState({ - divStyle: { display:val } - }); - } - /** - * Update the input value - * when textbox gets focus - */ - _handleFocus() { - this.setState({ - inputValue:this._getValue() - }); - } - /** - * Update inputValue when textbox loses focus - */ - _handleBlur() { - this._setDisplay('none'); - if (this.state.inputValue !== '') { - this.props.onChange(this.state.inputValue / this.props.max); - } else { - this.setState({ - inputValue: this.props.percent * this.props.max - }); - } - } - /** - * Get the value in the text box - * @returns {number} inputValue Value of the input box - */ - _getValue() { - return this.state.inputValue; - } - /** - * Update and set limits on input box - * values depending on what user - * has selected - * - * @param {SyntheticEvent} event ReactJs onChange event - */ - _handleChange(event) { - if (event.target.value < 0) { - this.setState({ inputValue: 0 }); - } else if (event.target.value <= this.props.max) { - this.setState({ inputValue: event.target.value }); - } else { - this.setState({ inputValue: this.props.max }); - } - } - /** - * Key up handler for input field. - * If user hits Enter key, blur/close the input field - * @param {Event} event Keyboard event - */ - _keyup(event) { - switch (event.key) { - case 'Enter': - this.sliderVal.blur(); - return; - default: - return; - } - } - /** - * Get the value in the text box - * @return {React.Component} Text Input component for Slider - */ - render() { - let { axisUnit, onChange, percent, scale } = this.props; - return
{this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/>{this.props.axisUnit}
; - } -} - +import React from 'react'; +import PropTypes from 'prop-types'; + +const MARGIN_LR = 8; // Left/ Right margin + +/** + * Horizontal Slider + */ +export default class Slider extends React.Component { + + static defaultProps = { + axis: false, + min: 0, + max: 1, + scale: 1 // SVG render scale + }; + + static propTypes = { + axis: PropTypes.bool, + axisUnit: PropTypes.string,// units (T, M, etc.) + max: PropTypes.number, + min: PropTypes.number, + onChange: PropTypes.func.isRequired,// function which determins percent value + onResize: PropTypes.func, + percent: PropTypes.number.isRequired,// value of slider + scale: PropTypes.number + }; + + /** + * Constructor + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + this._down = this._down.bind(this); + this._move = this._move.bind(this); + this._up = this._up.bind(this); + this._keyup = this._keyup.bind(this); + this._keydown = this._keydown.bind(this); + this._touchstart = this._touchstart.bind(this); + this._touchend = this._touchend.bind(this); + + this._updatePercent = this._updatePercent.bind(this); + this._updateDimensions = this._updateDimensions.bind(this); + + this.state = { width: 0 }; + } + + /** + * On Mouse/Touch down handler + * @param {SyntheticEvent} event Event + */ + _down(event) { + let rect = event.currentTarget.getBoundingClientRect(); + this.left = rect.left; + this.width = rect.width; + this._move(event); + this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); + } + + /** + * Update the slider percentage on move + * @param {SyntheticEvent} event Event + */ + _move(event) { + if(this.width !== null && this.left != null) { + let clientX = event.touches ? event.touches[0].clientX : event.clientX; + event.preventDefault(); + this._updatePercent(clientX - this.left, this.width); + } + } + + /** + * On Mouse/Touch up handler + * @param {Event} event DOM Event + */ + _up(event) { + this.sliderInputBox.sliderVal.focus(); + clearTimeout(this.touchStartTimer); + event.preventDefault(); + this.left = null; + this.width = null; + } + + + /** + * Key up handler for keyboard. + * display the number field then set focus to it + * when "Enter" key is pressed + * @param {Event} event Keyboard event + */ + _keyup(event) { + switch (event.key) { + case 'Enter': + event.preventDefault(); + this.sliderInputBox._setDisplay('block'); + return; + default: + return; + } + } + /** + * Key down handler + * increment slider position by +/- 1 when right/left arrow key is pressed or held + * @param {Event} event Keyboard even + */ + _keydown(event) { + let newVal = this.props.percent * this.props.max; + switch (event.key) { + case 'ArrowRight': + newVal += 1; + if (newVal <= this.props.max) this.props.onChange(newVal / this.props.max); + return; + case 'ArrowLeft': + newVal -= 1; + if (newVal >= 0) this.props.onChange(newVal / this.props.max); + return; + default: + return; + } + } + + /** + * Touch start handler + * @param {Event} event DOM Event + * + */ + _touchstart(event) { + this.touchStartTimer = setTimeout(() => this.sliderInputBox._setDisplay('block'), 1500); + } + + /** + * Touch end handler + * @param {Event} event DOM Event + * + */ + _touchend(event) { + this.sliderInputBox.sliderVal.focus(); + clearTimeout(this.touchStartTimer); + } + + /** + * Determine if the user is still dragging + * @param {SyntheticEvent} event Event + */ + _enter(event) { + if(event.buttons !== 1) { + this.left = null; + this.width = null; + } + } + + /** + * Update the slider percentage + * @param {number} pos Slider drag position + * @param {number} width Slider width + * @param {Event} event DOM Event + */ + _updatePercent(pos, width) { + this.props.onChange(Math.min(Math.max(pos / width, 0), 1)); + } + + /** + * Update dimenions from rendered DOM + */ + _updateDimensions() { + this.setState({ + outerWidth: this.node.getBoundingClientRect().width + }); + } + + /** + * Add listeners when about to mount + */ + componentWillMount() { + if (this.props.onResize) { + this.resizeListener = this.props.onResize(this._updateDimensions); + } + } + + /** + * Trigger DOM updates on mount + */ + componentDidMount() { + this._updateDimensions(); + } + + /** + * Remove listeners on unmount + */ + componentWillUnmount() { + if (this.resizeListener) { + this.resizeListener.remove(); + } + } + + /** + * Render the slider + * @return {React.Component} The slider + */ + render() { + let outerWidth = this.state.outerWidth; + let { axis, axisUnit, min, max, scale } = this.props; + let style = { + width: '100%', + height: axis ? '2.5em' : '1.5em', + boxSizing: 'border-box' + }; + if (!outerWidth) { + return this.node = node} />; + } + let margin = MARGIN_LR * scale; + let width = outerWidth - (margin * 2); + let pctPos = width * this.props.percent; + return
this.node = node} tabIndex="0"> + + + + + {axis && + {min + axisUnit} + {(min + max / 2) + axisUnit} + {max + axisUnit} + } + + this.sliderInputBox = tb} + onChange={this.props.onChange} + percent={this.props.percent} + axisUnit={this.props.axisUnit} + scale={this.props.scale} + max={this.props.max} + /> +
; + } +} +/** + * New component to add keyboard support for sliders - works on all devices (desktop, iOS, Android) + **/ +class TextInputBox extends React.Component { + static propTypes = { + axisUnit: PropTypes.string,// units (T, M, etc.) + max: PropTypes.number, + onChange: PropTypes.func.isRequired,// function which determins percent value + percent: PropTypes.number.isRequired,// value of slider + scale: PropTypes.number + }; + /** + * Determine if the user is still dragging + * @param {Object} props React Component properties + */ + constructor(props) { + super(props); + this._handleFocus = this._handleFocus.bind(this); + this._handleBlur = this._handleBlur.bind(this); + this._handleChange = this._handleChange.bind(this); + this._keyup = this._keyup.bind(this); + this.state = this._getInitialState(); + } + /** + * Update input value if slider changes will change props/state + * @param {Object} nextProps React Component properites + * @param {Object} nextState React Component state values + */ + componentWillReceiveProps(nextProps, nextState) { + let nextValue = nextProps.percent * nextProps.max; + // See https://stackoverflow.com/questions/32414308/updating-state-on-props-change-in-react-form + if (nextValue !== this.state.inputValue && nextValue <= nextProps.max) { + this.setState({ inputValue: nextValue }); + } + } + /** + * Update slider textbox visibility/values if changes are made to slider + * @param {Object} prevProps React Component properites + * @param {Object} prevState React Component state values + */ + componentDidUpdate(prevProps, prevState) { + if (prevState.divStyle.display == 'none' && this.state.divStyle.display == 'block') { + this.enterTimer = setTimeout(() => this.sliderVal.focus(), 10); + } + if (prevProps.max !== this.props.max && this.state.inputValue > this.props.max) { + // they chose a different module + this.setState({ inputValue: this.props.max }); + } + if (this.state.inputValue != prevState.inputValue && prevProps.max == this.props.max) { + this.props.onChange(this.state.inputValue / this.props.max); + } + } + /** + * Set initial state for the textbox. + * We may want to rethink this to + * try and make it a stateless component + * @returns {object} React state object with initial values set + */ + _getInitialState() { + return { + divStyle: { display:'none' }, + inputStyle: { width:'4em' }, + labelStyle: { marginLeft: '.1em' }, + maxLength:5, + size:5, + min:0, + tabIndex:-1, + type:'number', + readOnly: true, + inputValue: this.props.percent * this.props.max + }; + } + /** + * + * @param {string} val block or none + */ + _setDisplay(val) { + this.setState({ + divStyle: { display:val } + }); + } + /** + * Update the input value + * when textbox gets focus + */ + _handleFocus() { + this.setState({ + inputValue:this._getValue() + }); + } + /** + * Update inputValue when textbox loses focus + */ + _handleBlur() { + this._setDisplay('none'); + if (this.state.inputValue !== '') { + this.props.onChange(this.state.inputValue / this.props.max); + } else { + this.setState({ + inputValue: this.props.percent * this.props.max + }); + } + } + /** + * Get the value in the text box + * @returns {number} inputValue Value of the input box + */ + _getValue() { + return this.state.inputValue; + } + /** + * Update and set limits on input box + * values depending on what user + * has selected + * + * @param {SyntheticEvent} event ReactJs onChange event + */ + _handleChange(event) { + if (event.target.value < 0) { + this.setState({ inputValue: 0 }); + } else if (event.target.value <= this.props.max) { + this.setState({ inputValue: event.target.value }); + } else { + this.setState({ inputValue: this.props.max }); + } + } + /** + * Key up handler for input field. + * If user hits Enter key, blur/close the input field + * @param {Event} event Keyboard event + */ + _keyup(event) { + switch (event.key) { + case 'Enter': + this.sliderVal.blur(); + return; + default: + return; + } + } + /** + * Get the value in the text box + * @return {React.Component} Text Input component for Slider + */ + render() { + let { axisUnit, onChange, percent, scale } = this.props; + return
{this._handleBlur();}} onFocus={() => {this._handleFocus();}} type={this.state.type} ref={(ip) => this.sliderVal = ip}/>{this.props.axisUnit}
; + } +} + diff --git a/src/app/components/VerticalBarChart.jsx b/src/app/components/VerticalBarChart.jsx index ad77233c..74011e0a 100644 --- a/src/app/components/VerticalBarChart.jsx +++ b/src/app/components/VerticalBarChart.jsx @@ -1,80 +1,80 @@ -import TranslatedComponent from './TranslatedComponent'; -import React, { PropTypes } from 'react'; -import ContainerDimensions from 'react-container-dimensions'; -import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts'; - -const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; -const LABEL_COLOUR = '#000000'; -const AXIS_COLOUR = '#C06400'; - -const ASPECT = 1; - -const merge = function(one, two) { - return Object.assign({}, one, two); -}; - -/** - * A vertical bar chart - */ -export default class VerticalBarChart extends TranslatedComponent { - static propTypes = { - data : PropTypes.array.isRequired, - yMax : PropTypes.number - }; - - /** - * Constructor - * @param {Object} props React Component properties - * @param {Object} context React Component context - */ - constructor(props, context) { - super(props); - - this._termtip = this._termtip.bind(this); - } - - /** - * Render the bar chart - * @returns {Object} the markup - */ - render() { - const { tooltip, termtip } = this.context; - - // Calculate maximum for Y - let dataMax = Math.max(...this.props.data.map(d => d.value)); - if (dataMax == -Infinity) dataMax = 0; - let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0; - const localMax = Math.max(dataMax, yMax); - - return ( - - { ({ width }) => ( -
- - - - - - - -
- )} -
- ); - } - - /** - * Generate a term tip - * @param {Object} d the data - * @param {number} i the index - * @param {Object} e the event - * @returns {Object} termtip markup - */ - _termtip(d, i, e) { - if (this.props.data[i].tooltip) { - return this.context.termtip(this.props.data[i].tooltip, e); - } else { - return null; - } - } -} +import TranslatedComponent from './TranslatedComponent'; +import React, { PropTypes } from 'react'; +import ContainerDimensions from 'react-container-dimensions'; +import { BarChart, Bar, XAxis, YAxis, LabelList } from 'recharts'; + +const CORIOLIS_COLOURS = ['#FF8C0D', '#1FB0FF', '#71A052', '#D5D54D']; +const LABEL_COLOUR = '#000000'; +const AXIS_COLOUR = '#C06400'; + +const ASPECT = 1; + +const merge = function(one, two) { + return Object.assign({}, one, two); +}; + +/** + * A vertical bar chart + */ +export default class VerticalBarChart extends TranslatedComponent { + static propTypes = { + data : PropTypes.array.isRequired, + yMax : PropTypes.number + }; + + /** + * Constructor + * @param {Object} props React Component properties + * @param {Object} context React Component context + */ + constructor(props, context) { + super(props); + + this._termtip = this._termtip.bind(this); + } + + /** + * Render the bar chart + * @returns {Object} the markup + */ + render() { + const { tooltip, termtip } = this.context; + + // Calculate maximum for Y + let dataMax = Math.max(...this.props.data.map(d => d.value)); + if (dataMax == -Infinity) dataMax = 0; + let yMax = this.props.yMax ? Math.round(this.props.yMax) : 0; + const localMax = Math.max(dataMax, yMax); + + return ( + + { ({ width }) => ( +
+ + + + + + + +
+ )} +
+ ); + } + + /** + * Generate a term tip + * @param {Object} d the data + * @param {number} i the index + * @param {Object} e the event + * @returns {Object} termtip markup + */ + _termtip(d, i, e) { + if (this.props.data[i].tooltip) { + return this.context.termtip(this.props.data[i].tooltip, e); + } else { + return null; + } + } +} diff --git a/src/app/i18n/de.js b/src/app/i18n/de.js index b1ec1c33..5261a9cb 100644 --- a/src/app/i18n/de.js +++ b/src/app/i18n/de.js @@ -1,16 +1,16 @@ -export const formats = { - decimal: ',', - thousands: '.', - grouping: [3], - currency: ['', ' €'], - dateTime: '%A, der %e. %B %Y, %X', - date: '%d.%m.%Y', - time: '%H:%M:%S', - periods: ['AM', 'PM'], // unused - days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], - shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], - months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], - shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] -}; - -export { default as terms } from './de.json'; \ No newline at end of file +export const formats = { + decimal: ',', + thousands: '.', + grouping: [3], + currency: ['', ' €'], + dateTime: '%A, der %e. %B %Y, %X', + date: '%d.%m.%Y', + time: '%H:%M:%S', + periods: ['AM', 'PM'], // unused + days: ['Sonntag', 'Montag', 'Dienstag', 'Mittwoch', 'Donnerstag', 'Freitag', 'Samstag'], + shortDays: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'], + months: ['Januar', 'Februar', 'März', 'April', 'Mai', 'Juni', 'Juli', 'August', 'September', 'Oktober', 'November', 'Dezember'], + shortMonths: ['Jan', 'Feb', 'Mrz', 'Apr', 'Mai', 'Jun', 'Jul', 'Aug', 'Sep', 'Okt', 'Nov', 'Dez'] +}; + +export { default as terms } from './de.json'; diff --git a/src/app/i18n/ko.js b/src/app/i18n/ko.js index c2a2887e..ded3d7d1 100644 --- a/src/app/i18n/ko.js +++ b/src/app/i18n/ko.js @@ -1,16 +1,16 @@ -export const formats = { - decimal: '.', - thousands: ',', - grouping: [3], - currency: ['₩', ''], - dateTime: '%a %b %e %X %Y', - date: '%Y/%m/%d', - time: '%H:%M:%S', - periods: ['오전', '오후'], - days: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'], - shortDays: ['일', '월', '화', '수', '목', '금', '토'], - months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], - shortMonths: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'] -}; - -export { default as terms } from './ko.json'; +export const formats = { + decimal: '.', + thousands: ',', + grouping: [3], + currency: ['₩', ''], + dateTime: '%a %b %e %X %Y', + date: '%Y/%m/%d', + time: '%H:%M:%S', + periods: ['오전', '오후'], + days: ['일요일', '월요일', '화요일', '수요일', '목요일', '금요일', '토요일'], + shortDays: ['일', '월', '화', '수', '목', '금', '토'], + months: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'], + shortMonths: ['1월', '2월', '3월', '4월', '5월', '6월', '7월', '8월', '9월', '10월', '11월', '12월'] +}; + +export { default as terms } from './ko.json'; diff --git a/src/app/utils/BlueprintFunctions.js b/src/app/utils/BlueprintFunctions.js index 80259bea..3365c05c 100644 --- a/src/app/utils/BlueprintFunctions.js +++ b/src/app/utils/BlueprintFunctions.js @@ -1,435 +1,435 @@ -import React from 'react'; -import { Modifications } from 'coriolis-data/dist'; -import { STATS_FORMATTING } from '../shipyard/StatsFormatting'; - -/** - * Generate a tooltip with details of a blueprint's specials - * @param {Object} translate The translate object - * @param {Object} blueprint The blueprint at the required grade - * @param {string} grp The group of the module - * @param {Object} m The module to compare with - * @param {string} specialName The name of the special - * @returns {Object} The react components - */ -export function specialToolTip(translate, blueprint, grp, m, specialName) { - const effects = []; - if (!blueprint || !blueprint.features) { - return undefined; - } - if (m) { - // We also add in any benefits from specials that aren't covered above - if (m.blueprint) { - for (const feature in Modifications.modifierActions[specialName]) { - // if (!blueprint.features[feature] && !m.mods.feature) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature) - m.getModValue(feature, true); - if (featureDef.type === 'percentage') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - } - - return ( -
- - - {effects} - -
-
- ); -} - -/** - * Generate a tooltip with details of a blueprint's effects - * @param {Object} translate The translate object - * @param {Object} blueprint The blueprint at the required grade - * @param {Array} engineers The engineers supplying this blueprint - * @param {string} grp The group of the module - * @param {Object} m The module to compare with - * @returns {Object} The react components - */ -export function blueprintTooltip(translate, blueprint, engineers, grp, m) { - const effects = []; - if (!blueprint || !blueprint.features) { - return undefined; - } - for (const feature in blueprint.features) { - const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); - const featureDef = Modifications.modifications[feature]; - if (!featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let lowerBound = blueprint.features[feature][0]; - let upperBound = blueprint.features[feature][1]; - if (featureDef.type === 'percentage') { - lowerBound = Math.round(lowerBound * 1000) / 10; - upperBound = Math.round(upperBound * 1000) / 10; - } - const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); - const upperIsBeneficial = isValueBeneficial(feature, upperBound); - if (m) { - // We have a module - add in the current value - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} - {lowerBound}{symbol} - {current}{symbol} - {upperBound}{symbol} - - ); - } else { - // We do not have a module, no value - effects.push( - - {translate(feature, grp)} - {lowerBound}{symbol} - {upperBound}{symbol} - - ); - } - } - } - if (m) { - // Because we have a module add in any benefits that aren't part of the primary blueprint - for (const feature in m.mods) { - if (!blueprint.features[feature]) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - - // We also add in any benefits from specials that aren't covered above - if (m.blueprint && m.blueprint.special) { - for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { - if (!blueprint.features[feature] && !m.mods.feature) { - const featureDef = Modifications.modifications[feature]; - if (featureDef && !featureDef.hidden) { - let symbol = ''; - if (feature === 'jitter') { - symbol = '°'; - } else if (featureDef.type === 'percentage') { - symbol = '%'; - } - let current = m.getModValue(feature); - if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { - current = Math.round(current / 10) / 10; - } else if (featureDef.type === 'numeric') { - current /= 100; - } - const currentIsBeneficial = isValueBeneficial(feature, current); - effects.push( - - {translate(feature, grp)} -   - {current}{symbol} -   - - ); - } - } - } - } - } - - let components; - if (!m) { - components = []; - for (const component in blueprint.components) { - components.push( - - {translate(component)} - {blueprint.components[component]} - - ); - } - } - - let engineersList; - if (engineers) { - engineersList = []; - for (const engineer of engineers) { - engineersList.push( - - {engineer} - - ); - } - } - - return ( -
- - - - - - {m ? : null } - - - - - {effects} - -
{translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
- { components ? - - - - - - - - {components} - -
{translate('component')}{translate('amount')}
: null } - { engineersList ? - - - - - - - {engineersList} - -
{translate('engineers')}
: null } -
- ); -} - -/** - * Is this blueprint feature beneficial? - * @param {string} feature The name of the feature - * @param {array} values The value of the feature - * @returns {boolean} True if this feature is beneficial - */ -export function isBeneficial(feature, values) { - const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); - if (Modifications.modifications[feature].higherbetter) { - return !fact; - } else { - return fact; - } -} - -/** - * Is this feature value beneficial? - * @param {string} feature The name of the feature - * @param {number} value The value of the feature - * @returns {boolean} True if this value is beneficial - */ -export function isValueBeneficial(feature, value) { - if (Modifications.modifications[feature].higherbetter) { - return value > 0; - } else { - return value < 0; - } -} - -/** - * Is the change as shown beneficial? - * @param {string} feature The name of the feature - * @param {number} value The value of the feature as percentage change - * @returns True if the value is beneficial - */ -export function isChangeValueBeneficial(feature, value) { - let changeHigherBetter = STATS_FORMATTING[feature].higherbetter; - if (changeHigherBetter === undefined) { - return isValueBeneficial(feature, value); - } - - if (changeHigherBetter) { - return value > 0; - } else { - return value < 0; - } -} - -/** - * Get a blueprint with a given name and an optional module - * @param {string} name The name of the blueprint - * @param {Object} module The module for which to obtain this blueprint - * @returns {Object} The matching blueprint - */ -export function getBlueprint(name, module) { - // Start with a copy of the blueprint - const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); - const found = Modifications.blueprints[findMod(name)]; - if (!found || !found.fdname) { - return {}; - } - const blueprint = JSON.parse(JSON.stringify(found)); - return blueprint; -} - -/** - * Provide 'percent' primary modifications - * @param {Object} ship The ship for which to perform the modifications - * @param {Object} m The module for which to perform the modifications - * @param {Number} percent The percent to set values to of full. - */ -export function setPercent(ship, m, percent) { - ship.clearModifications(m); - // Pick given value as multiplier - const mult = percent / 100; - setQualityCB(m.blueprint, mult, (featureName, value) => ship.setModification(m, featureName, value)); -} - -/** - * Sets the blueprint quality and fires a callback for each property affected. - * @param {Object} blueprint The ship for which to perform the modifications - * @param {Number} quality The quality to apply - float number 0 to 1. - * @param {Function} cb The Callback to run for each property. Function (featureName, value) - */ -export function setQualityCB(blueprint, quality, cb) { - // Pick given value as multiplier - const features = blueprint.grades[blueprint.grade].features; - for (const featureName in features) { - let value; - if (Modifications.modifications[featureName].higherbetter) { - // Higher is better, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); - } else { - value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); - } - } else { - // Higher is worse, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); - } else { - value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); - } - } - - if (Modifications.modifications[featureName].type == 'percentage') { - value = value * 10000; - } else if (Modifications.modifications[featureName].type == 'numeric') { - value = value * 100; - } - - cb(featureName, value); - } -} - -/** - * Provide 'random' primary modifications - * @param {Object} ship The ship for which to perform the modifications - * @param {Object} m The module for which to perform the modifications - */ -export function setRandom(ship, m) { - // Pick a single value for our randomness - setPercent(ship, m, Math.random() * 100); -} - -/** - * Provide 'percent' primary query - * @param {Object} m The module for which to perform the query - * @returns {Number} percent The percentage indicator of current applied values. - */ -export function getPercent(m) { - let result = null; - const features = m.blueprint.grades[m.blueprint.grade].features; - for (const featureName in features) { - if (features[featureName][0] === features[featureName][1]) { - continue; - } - - let value = _getValue(m, featureName); - let mult; - if (Modifications.modifications[featureName].higherbetter) { - // Higher is better, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); - } else { - mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); - } - } else { - // Higher is worse, but is this making it better or worse? - if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { - mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); - } else { - mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); - } - } - - if (result && result != mult) { - return null; - } else if (result != mult) { - result = mult; - } - } - - return result; -} - -/** - * Query a feature value - * @param {Object} m The module for which to perform the query - * @param {string} featureName The feature being queried - * @returns {number} The value of the modification as a % - */ -function _getValue(m, featureName) { - if (Modifications.modifications[featureName].type == 'percentage') { - return m.getModValue(featureName, true) / 10000; - } else if (Modifications.modifications[featureName].type == 'numeric') { - return m.getModValue(featureName, true) / 100; - } else { - return m.getModValue(featureName, true); - } -} +import React from 'react'; +import { Modifications } from 'coriolis-data/dist'; +import { STATS_FORMATTING } from '../shipyard/StatsFormatting'; + +/** + * Generate a tooltip with details of a blueprint's specials + * @param {Object} translate The translate object + * @param {Object} blueprint The blueprint at the required grade + * @param {string} grp The group of the module + * @param {Object} m The module to compare with + * @param {string} specialName The name of the special + * @returns {Object} The react components + */ +export function specialToolTip(translate, blueprint, grp, m, specialName) { + const effects = []; + if (!blueprint || !blueprint.features) { + return undefined; + } + if (m) { + // We also add in any benefits from specials that aren't covered above + if (m.blueprint) { + for (const feature in Modifications.modifierActions[specialName]) { + // if (!blueprint.features[feature] && !m.mods.feature) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature) - m.getModValue(feature, true); + if (featureDef.type === 'percentage') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + } + + return ( +
+ + + {effects} + +
+
+ ); +} + +/** + * Generate a tooltip with details of a blueprint's effects + * @param {Object} translate The translate object + * @param {Object} blueprint The blueprint at the required grade + * @param {Array} engineers The engineers supplying this blueprint + * @param {string} grp The group of the module + * @param {Object} m The module to compare with + * @returns {Object} The react components + */ +export function blueprintTooltip(translate, blueprint, engineers, grp, m) { + const effects = []; + if (!blueprint || !blueprint.features) { + return undefined; + } + for (const feature in blueprint.features) { + const featureIsBeneficial = isBeneficial(feature, blueprint.features[feature]); + const featureDef = Modifications.modifications[feature]; + if (!featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let lowerBound = blueprint.features[feature][0]; + let upperBound = blueprint.features[feature][1]; + if (featureDef.type === 'percentage') { + lowerBound = Math.round(lowerBound * 1000) / 10; + upperBound = Math.round(upperBound * 1000) / 10; + } + const lowerIsBeneficial = isValueBeneficial(feature, lowerBound); + const upperIsBeneficial = isValueBeneficial(feature, upperBound); + if (m) { + // We have a module - add in the current value + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} + {lowerBound}{symbol} + {current}{symbol} + {upperBound}{symbol} + + ); + } else { + // We do not have a module, no value + effects.push( + + {translate(feature, grp)} + {lowerBound}{symbol} + {upperBound}{symbol} + + ); + } + } + } + if (m) { + // Because we have a module add in any benefits that aren't part of the primary blueprint + for (const feature in m.mods) { + if (!blueprint.features[feature]) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + + // We also add in any benefits from specials that aren't covered above + if (m.blueprint && m.blueprint.special) { + for (const feature in Modifications.modifierActions[m.blueprint.special.edname]) { + if (!blueprint.features[feature] && !m.mods.feature) { + const featureDef = Modifications.modifications[feature]; + if (featureDef && !featureDef.hidden) { + let symbol = ''; + if (feature === 'jitter') { + symbol = '°'; + } else if (featureDef.type === 'percentage') { + symbol = '%'; + } + let current = m.getModValue(feature); + if (featureDef.type === 'percentage' || featureDef.name === 'burst' || featureDef.name === 'burstrof') { + current = Math.round(current / 10) / 10; + } else if (featureDef.type === 'numeric') { + current /= 100; + } + const currentIsBeneficial = isValueBeneficial(feature, current); + effects.push( + + {translate(feature, grp)} +   + {current}{symbol} +   + + ); + } + } + } + } + } + + let components; + if (!m) { + components = []; + for (const component in blueprint.components) { + components.push( + + {translate(component)} + {blueprint.components[component]} + + ); + } + } + + let engineersList; + if (engineers) { + engineersList = []; + for (const engineer of engineers) { + engineersList.push( + + {engineer} + + ); + } + } + + return ( +
+ + + + + + {m ? : null } + + + + + {effects} + +
{translate('feature')}{translate('worst')}{translate('current')}{translate('best')}
+ { components ? + + + + + + + + {components} + +
{translate('component')}{translate('amount')}
: null } + { engineersList ? + + + + + + + {engineersList} + +
{translate('engineers')}
: null } +
+ ); +} + +/** + * Is this blueprint feature beneficial? + * @param {string} feature The name of the feature + * @param {array} values The value of the feature + * @returns {boolean} True if this feature is beneficial + */ +export function isBeneficial(feature, values) { + const fact = (values[0] < 0 || (values[0] === 0 && values[1] < 0)); + if (Modifications.modifications[feature].higherbetter) { + return !fact; + } else { + return fact; + } +} + +/** + * Is this feature value beneficial? + * @param {string} feature The name of the feature + * @param {number} value The value of the feature + * @returns {boolean} True if this value is beneficial + */ +export function isValueBeneficial(feature, value) { + if (Modifications.modifications[feature].higherbetter) { + return value > 0; + } else { + return value < 0; + } +} + +/** + * Is the change as shown beneficial? + * @param {string} feature The name of the feature + * @param {number} value The value of the feature as percentage change + * @returns True if the value is beneficial + */ +export function isChangeValueBeneficial(feature, value) { + let changeHigherBetter = STATS_FORMATTING[feature].higherbetter; + if (changeHigherBetter === undefined) { + return isValueBeneficial(feature, value); + } + + if (changeHigherBetter) { + return value > 0; + } else { + return value < 0; + } +} + +/** + * Get a blueprint with a given name and an optional module + * @param {string} name The name of the blueprint + * @param {Object} module The module for which to obtain this blueprint + * @returns {Object} The matching blueprint + */ +export function getBlueprint(name, module) { + // Start with a copy of the blueprint + const findMod = val => Object.keys(Modifications.blueprints).find(elem => elem.toString().toLowerCase().search(val.toString().toLowerCase().replace(/(OutfittingFieldType_|persecond)/igm, '')) >= 0); + const found = Modifications.blueprints[findMod(name)]; + if (!found || !found.fdname) { + return {}; + } + const blueprint = JSON.parse(JSON.stringify(found)); + return blueprint; +} + +/** + * Provide 'percent' primary modifications + * @param {Object} ship The ship for which to perform the modifications + * @param {Object} m The module for which to perform the modifications + * @param {Number} percent The percent to set values to of full. + */ +export function setPercent(ship, m, percent) { + ship.clearModifications(m); + // Pick given value as multiplier + const mult = percent / 100; + setQualityCB(m.blueprint, mult, (featureName, value) => ship.setModification(m, featureName, value)); +} + +/** + * Sets the blueprint quality and fires a callback for each property affected. + * @param {Object} blueprint The ship for which to perform the modifications + * @param {Number} quality The quality to apply - float number 0 to 1. + * @param {Function} cb The Callback to run for each property. Function (featureName, value) + */ +export function setQualityCB(blueprint, quality, cb) { + // Pick given value as multiplier + const features = blueprint.grades[blueprint.grade].features; + for (const featureName in features) { + let value; + if (Modifications.modifications[featureName].higherbetter) { + // Higher is better, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); + } else { + value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); + } + } else { + // Higher is worse, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + value = features[featureName][0] + ((features[featureName][1] - features[featureName][0]) * quality); + } else { + value = features[featureName][1] + ((features[featureName][0] - features[featureName][1]) * quality); + } + } + + if (Modifications.modifications[featureName].type == 'percentage') { + value = value * 10000; + } else if (Modifications.modifications[featureName].type == 'numeric') { + value = value * 100; + } + + cb(featureName, value); + } +} + +/** + * Provide 'random' primary modifications + * @param {Object} ship The ship for which to perform the modifications + * @param {Object} m The module for which to perform the modifications + */ +export function setRandom(ship, m) { + // Pick a single value for our randomness + setPercent(ship, m, Math.random() * 100); +} + +/** + * Provide 'percent' primary query + * @param {Object} m The module for which to perform the query + * @returns {Number} percent The percentage indicator of current applied values. + */ +export function getPercent(m) { + let result = null; + const features = m.blueprint.grades[m.blueprint.grade].features; + for (const featureName in features) { + if (features[featureName][0] === features[featureName][1]) { + continue; + } + + let value = _getValue(m, featureName); + let mult; + if (Modifications.modifications[featureName].higherbetter) { + // Higher is better, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); + } else { + mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); + } + } else { + // Higher is worse, but is this making it better or worse? + if (features[featureName][0] < 0 || (features[featureName][0] === 0 && features[featureName][1] < 0)) { + mult = Math.round((value - features[featureName][0]) / (features[featureName][1] - features[featureName][0]) * 100); + } else { + mult = Math.round((value - features[featureName][1]) / (features[featureName][0] - features[featureName][1]) * 100); + } + } + + if (result && result != mult) { + return null; + } else if (result != mult) { + result = mult; + } + } + + return result; +} + +/** + * Query a feature value + * @param {Object} m The module for which to perform the query + * @param {string} featureName The feature being queried + * @returns {number} The value of the modification as a % + */ +function _getValue(m, featureName) { + if (Modifications.modifications[featureName].type == 'percentage') { + return m.getModValue(featureName, true) / 10000; + } else if (Modifications.modifications[featureName].type == 'numeric') { + return m.getModValue(featureName, true) / 100; + } else { + return m.getModValue(featureName, true); + } +} From cd68199a4163f7f283b1ae4c6d101e04a707d904 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 17 May 2024 12:31:32 +0100 Subject: [PATCH 07/42] Adding workflow for autodeploy --- .github/workflows/autodeploy.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .github/workflows/autodeploy.yml diff --git a/.github/workflows/autodeploy.yml b/.github/workflows/autodeploy.yml new file mode 100644 index 00000000..f150f835 --- /dev/null +++ b/.github/workflows/autodeploy.yml @@ -0,0 +1,29 @@ +# This is a basic deployment workflow triggered by pushes to the alpha branch. + +name: Auto-Deploy + +# Controls when the action will run. Workflow runs when the alpha branch receives a push event +on: + workflow_dispatch: + push: + branches: + - alpha + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + downloadcode: + runs-on: self-hosted + steps: + - shell: bash + run: | + git clone https://github.com/alex-williams/coriolis-data.git + cd coriolis-data + git checkout alpha + npm install + cd ../ + git clone https://github.com/alex-williams/coriolis.git + cd ./coriolis + git checkout alpha + npm install + npm run build + sudo -u www-data cp -r ./build/* /var/www/newdisk/coriolis.brighter-applications.co.uk/ \ No newline at end of file From fbd9c3d282bd1d242f23667a347657e52117a95b Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 17 May 2024 12:44:07 +0100 Subject: [PATCH 08/42] Improving workflow --- .github/workflows/autodeploy.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.github/workflows/autodeploy.yml b/.github/workflows/autodeploy.yml index f150f835..4cc9fbe7 100644 --- a/.github/workflows/autodeploy.yml +++ b/.github/workflows/autodeploy.yml @@ -16,11 +16,13 @@ jobs: steps: - shell: bash run: | + rm -Rf ./coriolis-data git clone https://github.com/alex-williams/coriolis-data.git cd coriolis-data git checkout alpha npm install cd ../ + rm -Rf ./coriolis git clone https://github.com/alex-williams/coriolis.git cd ./coriolis git checkout alpha From 4283b0b839fc3d6bd434460152eabbb93095096f Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 24 May 2024 17:38:47 +0100 Subject: [PATCH 09/42] Changed deployment ordering --- .github/workflows/autodeploy.yml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autodeploy.yml b/.github/workflows/autodeploy.yml index 4cc9fbe7..9ef7e497 100644 --- a/.github/workflows/autodeploy.yml +++ b/.github/workflows/autodeploy.yml @@ -16,15 +16,14 @@ jobs: steps: - shell: bash run: | + rm -Rf ./coriolis rm -Rf ./coriolis-data + git clone https://github.com/alex-williams/coriolis.git git clone https://github.com/alex-williams/coriolis-data.git cd coriolis-data git checkout alpha npm install - cd ../ - rm -Rf ./coriolis - git clone https://github.com/alex-williams/coriolis.git - cd ./coriolis + cd ../coriolis git checkout alpha npm install npm run build From 0d749202e27041799c3b024652933d16ffe92eff Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 24 May 2024 17:41:27 +0100 Subject: [PATCH 10/42] Changing to clone single branch for deployment, not the whole repo --- .github/workflows/autodeploy.yml | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/workflows/autodeploy.yml b/.github/workflows/autodeploy.yml index 9ef7e497..8f28cc84 100644 --- a/.github/workflows/autodeploy.yml +++ b/.github/workflows/autodeploy.yml @@ -18,13 +18,11 @@ jobs: run: | rm -Rf ./coriolis rm -Rf ./coriolis-data - git clone https://github.com/alex-williams/coriolis.git - git clone https://github.com/alex-williams/coriolis-data.git + git clone https://github.com/alex-williams/coriolis.git --single-branch --branch alpha + git clone https://github.com/alex-williams/coriolis-data.git --single-branch --branch alpha cd coriolis-data - git checkout alpha npm install cd ../coriolis - git checkout alpha npm install npm run build sudo -u www-data cp -r ./build/* /var/www/newdisk/coriolis.brighter-applications.co.uk/ \ No newline at end of file From ee92f2f2e4e98a617c8be93b1be43c728f9a7c6f Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 24 May 2024 17:56:20 +0100 Subject: [PATCH 11/42] Adds the Advanced MC's, AX MC's, AX MR's and Nanite Torpedo (#4) --- src/app/components/AvailableModulesMenu.jsx | 8 ++++++-- src/app/i18n/en.json | 4 ++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index de4a6e17..1ae53a44 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -39,13 +39,17 @@ const GRPCAT = { 'ml': 'lasers', 'c': 'projectiles', 'mc': 'projectiles', + 'advmc': 'projectiles', 'axmc': 'experimental', + 'axmce': 'experimental', + 'ntp': 'experimental', 'fc': 'projectiles', 'rfl': 'experimental', 'pa': 'projectiles', 'rg': 'projectiles', 'mr': 'ordnance', 'axmr': 'experimental', + 'axmre': 'experimental', 'rcpl': 'experimental', 'dtl': 'experimental', 'tbsc': 'experimental', @@ -104,7 +108,7 @@ const CATEGORIES = { // Hardpoints 'lasers': ['pl', 'ul', 'bl'], - 'projectiles': ['mc', 'c', 'fc', 'pa', 'rg'], + 'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'], 'ordnance': ['mr', 'tp', 'nl'], // Utilities 'sb': ['sb'], @@ -113,7 +117,7 @@ const CATEGORIES = { 'defence': ['ch', 'po', 'ec'], 'scanners': ['sc', 'ss', 'cs', 'kw', 'ws'], // Overloaded with internal scanners // Experimental - 'experimental': ['axmc', 'axmr', 'rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], + 'experimental': ['axmc', 'axmce', 'axmr', 'axmre', 'ntp','rfl', 'tbrfl', 'tbsc', 'tbem', 'xs', 'sfn', 'rcpl', 'dtl', 'rsl', 'mahr',], 'weapon stabilizers': ['ews'], // Guardian 'guardian': ['gpp', 'gpd', 'gpc', 'ggc', 'gsrp', 'gfsb', 'ghrp', 'gmrp', 'gsc'], diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index fd6b3f0b..e232373f 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -109,11 +109,14 @@ "kw": "Kill Warrant Scanner", "ls": "Life Support", "mc": "Multi-cannon", + "advmc": "Multi-cannon (Advanced)", "axmc": "AX Multi-cannon", + "axmce": "AX Multi-cannon (Enhanced)", "ml": "Mining Laser", "mlc": "Multi Limpet Controller", "mr": "Missile Rack", "axmr": "AX Missile Rack", + "axmre": "AX Missile Rack (Enhanced)", "ews": "Experimental Weapon Stabilizer", "mrp": "Module Reinforcement Package", "nl": "Mine Launcher", @@ -159,6 +162,7 @@ "sua": "Supercruise Assist", "t": "thrusters", "tp": "Torpedo Pylon", + "ntp": "Nanite Torpedo Pylon", "ul": "Burst Laser", "Send To EDEngineer": "Send To EDEngineer", "Send To EDOMH": "Send To EDOMH", From 6c34a2627341badcc7c6155234798058849d7fe1 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Fri, 24 May 2024 18:23:04 +0100 Subject: [PATCH 12/42] Issue 754 imports need to be more graceful (#5) * Adds valid module checking to all types of modules on import * Changes as per comments on the PR --- src/app/components/AvailableModulesMenu.jsx | 27 ++++- src/app/components/HardpointSlot.jsx | 1 + src/app/components/InternalSlot.jsx | 1 + src/app/components/Slot.jsx | 15 ++- src/app/components/StandardSlot.jsx | 10 +- src/app/i18n/en.json | 4 + src/app/pages/ErrorDetails.jsx | 3 + src/app/shipyard/Module.js | 9 ++ src/app/utils/JournalUtils.js | 123 +++++++++++++++++--- 9 files changed, 167 insertions(+), 26 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index 1ae53a44..e5f54030 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -217,16 +217,30 @@ export default class AvailableModulesMenu extends TranslatedComponent { if (categories.length === 1) { // Show category header instead of group header if (m && grp == m.grp) { - list.push(
this.groupElem = elem} key={category} + // If this is a missing module/weapon, skip it + if (m.grp == "mh" || m.grp == "mm"){ + continue; + } else { + list.push(
this.groupElem = elem} key={category} className={'select-category upp'}>{translate(category)}
); + } } else { - list.push(
{translate(category)}
); + if (category == "mh"){ + continue; + } else { + list.push(
{translate(category)}
); + } } } else { // Show category header as well as group header if (!categoryHeader) { - list.push(
{translate(category)}
); - categoryHeader = true; + if (category == "mh"){ + continue; + } + else { + list.push(
{translate(category)}
); + categoryHeader = true; + } } if (m && grp == m.grp) { list.push(
this.groupElem = elem} key={grp} @@ -302,6 +316,10 @@ export default class AvailableModulesMenu extends TranslatedComponent { let itemsOnThisRow = 0; for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; + if (m.grp == 'mh' || m.grp == 'mm') { + // If this is a missing module, skip it + continue; + } let mount = null; let disabled = false; prevName = m.name; @@ -316,6 +334,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length; } let active = mountedModule && mountedModule.id === m.id; + let classes = cn(m.name ? 'lc' : 'c', { warning: !disabled && warningFunc && warningFunc(m), active, diff --git a/src/app/components/HardpointSlot.jsx b/src/app/components/HardpointSlot.jsx index 724d9fd5..e9edda43 100644 --- a/src/app/components/HardpointSlot.jsx +++ b/src/app/components/HardpointSlot.jsx @@ -136,6 +136,7 @@ export default class HardpointSlot extends Slot { {showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null} {m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null} + {m.getInfo() ?
{translate(m.getInfo())}
: null} {m && validMods.length > 0 ?
this.modButton = modButton}>
: null }
; diff --git a/src/app/components/Slot.jsx b/src/app/components/Slot.jsx index 8c04dbab..1594cad6 100644 --- a/src/app/components/Slot.jsx +++ b/src/app/components/Slot.jsx @@ -99,6 +99,7 @@ export default class Slot extends TranslatedComponent { let translate = language.translate; let { ship, m, enabled, dropClass, dragOver, onOpen, onChange, selected, eligible, onSelect, warning, availableModules } = this.props; let slotDetails, modificationsMarker, menu; + let missing = false; if (!selected) { // If not selected then sure that modifications flag is unset @@ -108,6 +109,11 @@ export default class Slot extends TranslatedComponent { if (m) { slotDetails = this._getSlotDetails(m, enabled, translate, language.formats, language.units); // Must be implemented by sub classes modificationsMarker = JSON.stringify(m); + if(typeof m.grp !== 'undefined' || m.grp !== null) { + if(m.grp == "mh" || m.grp == "mm") { + missing = true; + } + } } else { slotDetails =
{translate(eligible ? 'emptyrestricted' : 'empty')}
; modificationsMarker = ''; @@ -138,13 +144,16 @@ export default class Slot extends TranslatedComponent { } // TODO: implement touch dragging - + return (
this.slotDiv = slotDiv}> -
+ { + // If missing module/hardpoint, set the div container to warning status. + } +
{this._getMaxClassLabel(translate)}
{slotDetails} -
+
{menu}
); diff --git a/src/app/components/StandardSlot.jsx b/src/app/components/StandardSlot.jsx index 1e810982..09819009 100644 --- a/src/app/components/StandardSlot.jsx +++ b/src/app/components/StandardSlot.jsx @@ -93,6 +93,11 @@ export default class StandardSlot extends TranslatedComponent { this._modificationsSelected = false; } + // If this is a missing module, therefore has the 'info' field, set the warning value on the module to be true when loaded. + if (m.info) { + warning = () => true; + } + const modificationsMarker = JSON.stringify(m); if (selected) { @@ -124,7 +129,7 @@ export default class StandardSlot extends TranslatedComponent {
{m.grp == 'bh' ? m.name.charAt(0) : slot.maxClass}
-
{classRating} {translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
+
{classRating} {m.getInfo() ? translate(m.ukName) : translate(m.name || m.grp)}{m.mods && Object.keys(m.mods).length > 0 ? : null }
{formats.round(mass)}{units.T}
@@ -144,7 +149,8 @@ export default class StandardSlot extends TranslatedComponent { { showModuleResistances && m.getKineticResistance() ?
{translate('kinres')}: {formats.pct(m.getKineticResistance())}
: null } { showModuleResistances && m.getThermalResistance() ?
{translate('thermres')}: {formats.pct(m.getThermalResistance())}
: null } { m.getIntegrity() ?
{translate('integrity')}: {formats.int(m.getIntegrity())}
: null } - { validMods.length > 0 ?
this.modButton = modButton }>
: null } + { m.getInfo() ?
{translate(m.getInfo())}
: null } + { m.getInfo() ?
: validMods.length > 0 ?
this.modButton = modButton }>
: null }
diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index e232373f..db5c6e4f 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -83,6 +83,7 @@ "HELP_MODIFICATIONS_MENU": "Click on a number to enter a new value, or drag along the bar for small changes", "PHRASE_FAIL_EDENGINEER": "Failed to send to EDEngineer (Launch EDEngineer and make sure the API is started then refresh the page.)", "PHRASE_FIREFOX_EDENGINEER": "Sending to EDEngineer is not compatible with Firefox's security settings. Please try again with Chrome.", + "MISSING_MODULES": "Missing Modules", "am": "Auto Field-Maintenance Unit", "bh": "Bulkheads", "bl": "Beam Laser", @@ -109,6 +110,8 @@ "kw": "Kill Warrant Scanner", "ls": "Life Support", "mc": "Multi-cannon", + "mh": "Missing Weapon/Utility", + "mm": "Missing Module", "advmc": "Multi-cannon (Advanced)", "axmc": "AX Multi-cannon", "axmce": "AX Multi-cannon (Enhanced)", @@ -216,6 +219,7 @@ "boost interval": "Boost interval", "total": "Total", "ammo": "Ammunition maximum", + "info": "Info", "boot": "Boot time", "hacktime": "Hack time", "brokenregen": "Broken regeneration rate", diff --git a/src/app/pages/ErrorDetails.jsx b/src/app/pages/ErrorDetails.jsx index 5c68b8c9..4d96f93e 100644 --- a/src/app/pages/ErrorDetails.jsx +++ b/src/app/pages/ErrorDetails.jsx @@ -45,6 +45,9 @@ export default class ErrorDetails extends React.Component { return

Jameson, we have a problem..

{error.message}

+ Import Error handling has been improved, but still isn't perfect.
MOST Import failures are a result of missing modules in Coriolis,
OR incorrect import strings generated by third party apps. If you're seeing this page, we may have failed to handle the errors in your import correctly. Please see the data output below, specifically the 'scriptUrl:' section if it's there and then if you feel confident enough, please check the github issues page linked below and see if there is a similar issue already logged. If not, please create a new issue with the data below. If you're not confident, please ask for help on the Coriolis Channel of the EDCD Discord server. +
+

{importerror ?
If you are attempting to import a ship from EDDI or EDMC and are seeing a 'Z_BUF_ERROR' it means that the URL has not been provided correctly. This is a common problem when using Microsoft Internet Explorer or Microsoft Edge, and you should use another browser instead.
: null }
diff --git a/src/app/shipyard/Module.js b/src/app/shipyard/Module.js index dbbdc09c..7df7f44e 100755 --- a/src/app/shipyard/Module.js +++ b/src/app/shipyard/Module.js @@ -439,6 +439,15 @@ export default class Module { return this.get('integrity', modified); } + /** + * Get the info of this module + * @param {Boolean} [modified=false] Whether to take modifications into account + * @return {String} the info of this module + */ + getInfo(modified = false) { + return (modified && this.getModValue('info')) || this.info; + } + /** * Get the mass of this module * @param {Boolean} [modified=true] Whether to take modifications into account diff --git a/src/app/utils/JournalUtils.js b/src/app/utils/JournalUtils.js index bb78993d..5b7cf0f5 100644 --- a/src/app/utils/JournalUtils.js +++ b/src/app/utils/JournalUtils.js @@ -6,6 +6,22 @@ import { Modules } from 'coriolis-data/dist'; import { Modifications } from 'coriolis-data/dist'; import { getBlueprint, setQualityCB } from './BlueprintFunctions'; +/** + * Check if an imported module is valid + * @param {Object} module the module to check + * @param {Object} moduleType the type of module to check + * @return {boolean} true if the module is valid + */ +function _isValidImportedModule(module, moduleType) { + // First of all, has the _moduleFromFdName function returned 'null'? + if (!module){ + return false + } + else { + return true + } +} + /** * Obtain a module given its FD Name * @param {string} fdname the FD Name of the module @@ -98,49 +114,90 @@ export function shipFromLoadoutJSON(json) { if (module.Engineering) _addModifications(ship.bulkheads.m, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'powerplant': - const powerplant = _moduleFromFdName(module.Item); + let powerplant = _moduleFromFdName(module.Item); + // Check the powerplant returned is valid + if (!_isValidImportedModule(powerplant, 'powerplant')) + { + powerplant = _moduleFromFdName('Int_Missing_Powerplant'); + module.Engineering = null; + } ship.use(ship.standard[0], powerplant, true); ship.standard[0].enabled = module.On; ship.standard[0].priority = module.Priority; if (module.Engineering) _addModifications(powerplant, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'mainengines': - const thrusters = _moduleFromFdName(module.Item); + let thrusters = _moduleFromFdName(module.Item); + // Check the thrusters returned is valid + if (!_isValidImportedModule(thrusters, 'thrusters')) + { + thrusters = _moduleFromFdName('Int_Missing_Engine'); + module.Engineering = null; + } ship.use(ship.standard[1], thrusters, true); ship.standard[1].enabled = module.On; ship.standard[1].priority = module.Priority; if (module.Engineering) _addModifications(thrusters, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'frameshiftdrive': - const frameshiftdrive = _moduleFromFdName(module.Item); + let frameshiftdrive = _moduleFromFdName(module.Item); + // Check the frameshiftdrive returned is valid + if (!_isValidImportedModule(frameshiftdrive, 'frameshiftdrive')) + { + frameshiftdrive = _moduleFromFdName('Int_Missing_Hyperdrive'); + module.Engineering = null; + } ship.use(ship.standard[2], frameshiftdrive, true); ship.standard[2].enabled = module.On; ship.standard[2].priority = module.Priority; if (module.Engineering) _addModifications(frameshiftdrive, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'lifesupport': - const lifesupport = _moduleFromFdName(module.Item); + let lifesupport = _moduleFromFdName(module.Item); + // Check the lifesupport returned is valid + if (!_isValidImportedModule(lifesupport, 'lifesupport')) + { + lifesupport = _moduleFromFdName('Int_Missing_LifeSupport'); + module.Engineering = null; + } ship.use(ship.standard[3], lifesupport, true); ship.standard[3].enabled = module.On === true; ship.standard[3].priority = module.Priority; if (module.Engineering) _addModifications(lifesupport, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'powerdistributor': - const powerdistributor = _moduleFromFdName(module.Item); + let powerdistributor = _moduleFromFdName(module.Item); + // Check the powerdistributor returned is valid + if (!_isValidImportedModule(powerdistributor, 'powerdistributor')) + { + powerdistributor = _moduleFromFdName('Int_Missing_PowerDistributor'); + module.Engineering = null; + } ship.use(ship.standard[4], powerdistributor, true); ship.standard[4].enabled = module.On; ship.standard[4].priority = module.Priority; if (module.Engineering) _addModifications(powerdistributor, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'radar': - const sensors = _moduleFromFdName(module.Item); + let sensors = _moduleFromFdName(module.Item); + // Check the sensors returned is valid + if (!_isValidImportedModule(sensors, 'sensors')) + { + sensors = _moduleFromFdName('Int_Missing_Sensors'); + module.Engineering = null; + } ship.use(ship.standard[5], sensors, true); ship.standard[5].enabled = module.On; ship.standard[5].priority = module.Priority; if (module.Engineering) _addModifications(sensors, module.Engineering.Modifiers, module.Engineering.Quality, module.Engineering.BlueprintName, module.Engineering.Level, module.Engineering.ExperimentalEffect); break; case 'fueltank': - const fueltank = _moduleFromFdName(module.Item); + let fueltank = _moduleFromFdName(module.Item); + // Check the fueltank returned is valid + if (!_isValidImportedModule(fueltank, 'fueltank')) + { + fueltank = _moduleFromFdName('Int_Missing_FuelTank'); + } ship.use(ship.standard[6], fueltank, true); ship.standard[6].enabled = true; ship.standard[6].priority = 0; @@ -170,10 +227,27 @@ export function shipFromLoadoutJSON(json) { // This can happen with old imports that don't contain new hardpoints } else { hardpoint = _moduleFromFdName(hardpointSlot.Item); - ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); - ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; - ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; - modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot }); + // Check the hardpoint module returned is valid + if (!_isValidImportedModule(hardpoint, 'hardpoint')){ + // Check if it's a Utility or Hardpoint + if (hardpointSlot.Slot.toLowerCase().search(/tiny/)) + { + // Use the missing_hardpoint module 'Missing Hardpoint' which will inform the user that the module is missing + hardpoint = _moduleFromFdName('Hpt_Missing_Hardpoint'); + } + else { + // Use the missing_hardpoint module 'Missing Utility' which will inform the user that the module is missing + hardpoint = _moduleFromFdName('Hpt_Missing_Utility'); + } + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; + ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; + } else { + ship.use(ship.hardpoints[hardpointArrayNum], hardpoint, true); + ship.hardpoints[hardpointArrayNum].enabled = hardpointSlot.On; + ship.hardpoints[hardpointArrayNum].priority = hardpointSlot.Priority; + modsToAdd.push({ coriolisMod: hardpoint, json: hardpointSlot }); + } } hardpointArrayNum++; } @@ -187,13 +261,17 @@ export function shipFromLoadoutJSON(json) { continue; } const isMilitary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'Military' : false; + const isPlanetary = isNaN(shipTemplate.slots.internal[i]) ? shipTemplate.slots.internal[i].name == 'PlanetaryApproachSuite' : false; - // The internal slot might be a standard or a military slot. Military slots have a different naming system + // The internal slot might be a standard or a military slot, or a planetary slot. Military and Planetary slots have a different naming system let internalSlot = null; if (isMilitary) { const internalName = 'Military0' + militarySlotNum; internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); militarySlotNum++; + } else if (isPlanetary) { + const internalName = 'PlanetaryApproachSuite'; + internalSlot = json.Modules.find(elem => elem.Slot.toLowerCase() === internalName.toLowerCase()); } else { // Slot numbers are not contiguous so handle skips. for (; internalSlot === null && internalSlotNum < 99; internalSlotNum++) { @@ -212,11 +290,22 @@ export function shipFromLoadoutJSON(json) { // This can happen with old imports that don't contain new slots } else { const internalJson = internalSlot; - const internal = _moduleFromFdName(internalJson.Item); - ship.use(ship.internal[i], internal, true); - ship.internal[i].enabled = internalJson.On === true; - ship.internal[i].priority = internalJson.Priority; - modsToAdd.push({ coriolisMod: internal, json: internalSlot }); + let internal = _moduleFromFdName(internalJson.Item); + // Check the internal module returned is valid + if (!_isValidImportedModule(internal, 'internal')) + { + internal = _moduleFromFdName('Int_Missing_Module'); + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.On === true; + ship.internal[i].priority = internalJson.Priority; + //throw 'Unknown internal module: "' + module.Item + '"'; + } + else { + ship.use(ship.internal[i], internal, true); + ship.internal[i].enabled = internalJson.On === true; + ship.internal[i].priority = internalJson.Priority; + modsToAdd.push({ coriolisMod: internal, json: internalSlot }); + } } } From 634be1f197718b3aa174aae54e06c857346c5d9d Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Mon, 27 May 2024 15:43:19 +0100 Subject: [PATCH 13/42] Added 'special' field to certain modules to allow for clearer appearance in search results that they are the special type of module. Updated English descriptions of Advanced Modules and Special Modules --- src/app/components/AvailableModulesMenu.jsx | 11 ++++++++--- src/app/i18n/en.json | 1 + 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index e5f54030..e1ea9230 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -48,6 +48,7 @@ const GRPCAT = { 'pa': 'projectiles', 'rg': 'projectiles', 'mr': 'ordnance', + 'amr': 'ordnance', 'axmr': 'experimental', 'axmre': 'experimental', 'rcpl': 'experimental', @@ -109,7 +110,7 @@ const CATEGORIES = { // Hardpoints 'lasers': ['pl', 'ul', 'bl'], 'projectiles': ['mc', 'advmc', 'c', 'fc', 'pa', 'rg'], - 'ordnance': ['mr', 'tp', 'nl'], + 'ordnance': ['mr', 'amr', 'tp', 'nl'], // Utilities 'sb': ['sb'], 'hs': ['hs'], @@ -259,7 +260,11 @@ export default class AvailableModulesMenu extends TranslatedComponent { } else if (i.mount === 'T') { mount = 'Turreted'; } - const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)}` }; + let special = ''; + if (typeof(i.special) !== 'undefined') { + special = `(${translate(i.special)})`; + } + const fuzz = { grp, m: i, name: `${i.class}${i.rating}${mount ? ' ' + mount : ''} ${translate(grp)} ${translate(special)}` }; fuzzy.push(fuzz); } } @@ -334,7 +339,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length; } let active = mountedModule && mountedModule.id === m.id; - + let classes = cn(m.name ? 'lc' : 'c', { warning: !disabled && warningFunc && warningFunc(m), active, diff --git a/src/app/i18n/en.json b/src/app/i18n/en.json index db5c6e4f..09ed79d1 100644 --- a/src/app/i18n/en.json +++ b/src/app/i18n/en.json @@ -118,6 +118,7 @@ "ml": "Mining Laser", "mlc": "Multi Limpet Controller", "mr": "Missile Rack", + "amr": "Missile Rack (Advanced)", "axmr": "AX Missile Rack", "axmre": "AX Missile Rack (Enhanced)", "ews": "Experimental Weapon Stabilizer", From aa620be1137ff1a6c4841c8ef58ede77c1b58b12 Mon Sep 17 00:00:00 2001 From: leonardofelin <33718368+leonardofelin@users.noreply.github.com> Date: Mon, 27 May 2024 19:45:44 -0300 Subject: [PATCH 14/42] Update PT-BR translations Added translated strings for coriolis-data PRs 106 & 107 --- src/app/i18n/pt.json | 21 +++++++++++++++++++-- 1 file changed, 19 insertions(+), 2 deletions(-) diff --git a/src/app/i18n/pt.json b/src/app/i18n/pt.json index bfd10920..11c3522e 100644 --- a/src/app/i18n/pt.json +++ b/src/app/i18n/pt.json @@ -537,11 +537,15 @@ "kw": "Scanner de Registro Criminal", "ls": "Suporte de Vida", "mc": "Canhão de Repetição", + "advmc": "Canhão de Repetição Avançado", "axmc": "Canhão de Repetição AX", + "axmce": "Canhão de Repetição AX Melhorado", + "axmr": "Estante de Mísseis AX", + "axmre": "Estante de Mísseis AX Melhorado", + "ntp": "Pilone de Torpedo de Nanite", "ml": "Laser de Mineração", "mr": "Estante de Mísseis", - "axmr": "Estante de Mísseis AX", - "Advanced Missile Rack": "Estante de Mísseis Av.", + "Advanced Missile Rack": "Estante de Mísseis Avançado", "mrp": "Pacote de Reforço de Módulo", "nl": "Lança Minas", "shock mine launcher": "Lança Minas de Impacto", @@ -616,6 +620,19 @@ "Operations Multi Limpet Controller": "Controlador de Drones Operacionais", "Rescue Multi Limpet Controller": "Controlador de Drones de Resgate", "Xeno Multi Limpet Controller": "Controlador de Drones Xeno", + "mm": "Módulo Desconhecido", + "mh": "Encaixe Desconhecido", + "Unrecognised Module": "Módulo Desconhecido", + "Unrecognised Utility": "Utilidade Desconhecida", + "Unrecognised Weapon": "Arma Desconhecida", + "Unrecognised Power Plant": "Gerador de Energia Desconhecido", + "Unrecognised Thrusters": "Propulsores Desconhecido", + "Unrecognised Frame Shift Drive": "Motor de Distorção de Fase Desconhecido", + "Unrecognised Life Support": "Suporte de Vida Desconhecido", + "Unrecognised Power Distributor": "Distribuidor de Energia Desconhecido", + "Unrecognised Sensors": "Sensores Desconhecido", + "Unrecognised Fuel Tank": "Tanque de Combustível Desconhecido", + "Not in Coriolis yet. Check GitHub issues. Add Issue if needed.": "Não adicionado ao Coriolis. Verifique Issues existentes ou adicione um no GitHub.", "Lightweight": "Leve", "Heavy duty": "Pesado", From 9ef054c27180b510fd9189bc245eddac37c7b8a7 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Sun, 2 Jun 2024 20:03:12 +0100 Subject: [PATCH 15/42] Fixed 'Missing Module' category showing up in Optional Selection drop-down and fixed 'Missing Power Plant', 'Missing Power Distributor' and 'Missing Frameshift Drive' showing up in the Selection drop-downs for those module slots. --- src/app/components/AvailableModulesMenu.jsx | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index e5f54030..89818439 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -225,7 +225,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { className={'select-category upp'}>{translate(category)}
); } } else { - if (category == "mh"){ + if (category == "mh" || category == "mm"){ continue; } else { list.push(
{translate(category)}
); @@ -234,7 +234,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { } else { // Show category header as well as group header if (!categoryHeader) { - if (category == "mh"){ + if (category == "mh" || category == "mm"){ continue; } else { @@ -316,7 +316,8 @@ export default class AvailableModulesMenu extends TranslatedComponent { let itemsOnThisRow = 0; for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; - if (m.grp == 'mh' || m.grp == 'mm') { + // If m.grp is mh or mm, or m.symbol contains 'Missing' skip it + if (m.grp == 'mh' || m.grp == 'mm' || m.symbol.includes("Missing")) { // If this is a missing module, skip it continue; } @@ -334,7 +335,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { disabled = 1 <= ship.internal.filter(o => o.m && o.m.grp === 'mlc').length; } let active = mountedModule && mountedModule.id === m.id; - + let classes = cn(m.name ? 'lc' : 'c', { warning: !disabled && warningFunc && warningFunc(m), active, From f86ecede9b024a71c4c1c9ea0e63e8397f0f749a Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Thu, 6 Jun 2024 22:39:30 +0100 Subject: [PATCH 16/42] Fixing bug introduced by the previous PR for ISSUE_764. The previous fix introduced a bug which caused Armour Selection to error, due to Armour modules being completely different to other modules of any other type --- src/app/components/AvailableModulesMenu.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/AvailableModulesMenu.jsx b/src/app/components/AvailableModulesMenu.jsx index 815cab51..87f188b6 100644 --- a/src/app/components/AvailableModulesMenu.jsx +++ b/src/app/components/AvailableModulesMenu.jsx @@ -322,7 +322,7 @@ export default class AvailableModulesMenu extends TranslatedComponent { for (let i = 0; i < sortedModules.length; i++) { let m = sortedModules[i]; // If m.grp is mh or mm, or m.symbol contains 'Missing' skip it - if (m.grp == 'mh' || m.grp == 'mm' || m.symbol.includes("Missing")) { + if (m.grp == 'mh' || m.grp == 'mm' || (typeof(m.symbol) !== 'undefined' && m.symbol.includes("Missing"))) { // If this is a missing module, skip it continue; } From 2a6ae0f2ffdbb82170d3614586dd7deb46110228 Mon Sep 17 00:00:00 2001 From: Alex Williams Date: Wed, 29 May 2024 17:42:59 +0100 Subject: [PATCH 17/42] Modified export to EDOMH/EDEngineer page to be less 'bodged', allow EDOMH button to be clickable without checking for EDEngineer API (If they have EDOMH, they probably don't have EDEngineer...) and added a workaround for Coriolis sending bogus data for bulkheads. --- src/app/components/ModalShoppingList.jsx | 61 +++++++++++++++++++----- src/app/i18n/en.json | 2 + src/less/app.less | 2 +- src/less/modal.less | 9 ++++ 4 files changed, 60 insertions(+), 14 deletions(-) diff --git a/src/app/components/ModalShoppingList.jsx b/src/app/components/ModalShoppingList.jsx index cd2e2ec3..5900d8ce 100644 --- a/src/app/components/ModalShoppingList.jsx +++ b/src/app/components/ModalShoppingList.jsx @@ -90,8 +90,10 @@ export default class ModalShoppingList extends TranslatedComponent { request .get('http://localhost:44405/commanders') .end((err, res) => { + this.display = 'block'; if (err) { console.log(err); + this.display = 'none'; return this.setState({ failed: true }); } const cmdrs = JSON.parse(res.text); @@ -178,8 +180,34 @@ export default class ModalShoppingList extends TranslatedComponent { if (g < module.m.blueprint.grade) { continue; } + let item = ""; + // If the module blueprint fdname contains "Armour_" it's a bulkhead and we need to pre-populate the item field with the correct name from the ship object + if (module.m.blueprint.fdname.includes("Armour_")) { + switch (ship.bulkheads.m.name){ + case "Lightweight Alloy": + item = ship.id + "_Armour_Grade1"; + break; + case "Reinforced Alloy": + item = ship.id + "_Armour_Grade2"; + break; + case "Military Grade Composite": + item = ship.id + "_Armour_Grade3"; + break; + case "Mirrored Surface Composite": + item = ship.id + "_Armour_Mirrored"; + break; + case "Reactive Surface Composite": + item = ship.id + "_Armour_Reactive"; + break; + } + console.log(item); + } + else { + item = module.m.symbol; + } + blueprints.push({ - "item": module.m.symbol, + "item": item, "blueprint": module.m.blueprint.fdname, "grade": module.m.blueprint.grade, "highestGradePercentage":1.0 @@ -193,11 +221,12 @@ export default class ModalShoppingList extends TranslatedComponent { "version":1, "name":ship.name, // TO-DO: Import build name and put that here correctly "items": blueprints - } - + } + let JSONString = JSON.stringify(baseJson) + console.log(JSONString) let deflated = zlib.deflateSync(JSONString) - + //actually encode let link = base64url.encode(deflated) link = "edomh://coriolis/?" + link; @@ -320,16 +349,22 @@ export default class ModalShoppingList extends TranslatedComponent {