Add Catenary DC electrification layer, CommunityMapLocation fixes, and CI/CD
Deploy & Release / build-and-deploy (push) Successful in 1m3s

This commit is contained in:
m4xx1m committed 2026-10-21 04:55:00 +03:00
1 parent 8252fd6ba9
commit 68d5058106
11 files changed
+660 -20

No files matched your search

+197 -5
View File
@@ -88,11 +88,30 @@ export let view = {
}
}
export let layers = {};
export let layers = {
catenary: true,
catenaryWires: true,
catenarySubstations: true,
catenaryNeutrals: true,
catenaryInterruptions: true,
};
export let catenaryData = null;
export let catenaryPolylines = [];
export let catenarySubstations = [];
export let catenaryNeutrals = [];
export let catenaryInterruptions = [];
export let catenarySigns = [];
export let catenaryColorMode = 'Voltage';
export function reset(){
railTracks = [];
markers = [];
catenaryPolylines = [];
catenarySubstations = [];
catenaryNeutrals = [];
catenaryInterruptions = [];
catenarySigns = [];
}
export function sortTracks(){
@@ -572,6 +591,56 @@ export function testTooltip(cursorX, cursorY){
}
if(finalTooltip != '') return finalTooltip;
if(layers.catenary && layers.catenarySubstations){
for(const sub of catenarySubstations){
let radius = 24 / view.scale;
if(Math.pow(sub.x - cursorX, 2) + Math.pow(sub.z - cursorY, 2) <= radius * radius){
return sub.tooltip;
}
}
}
if(layers.catenary && layers.catenaryNeutrals){
for(const n of catenaryNeutrals){
let radius = 35 / view.scale;
if(Math.pow(n.center.x - cursorX, 2) + Math.pow(n.center.z - cursorY, 2) <= radius * radius){
return n.tooltip;
}
}
}
if(layers.catenary && layers.catenaryInterruptions){
for(const inter of catenaryInterruptions){
let radius = 24 / view.scale;
if(Math.pow(inter.center.x - cursorX, 2) + Math.pow(inter.center.z - cursorY, 2) <= radius * radius){
return inter.tooltip;
}
}
}
if(layers.catenary && layers.catenaryWires){
let bestWireDistSq = Infinity;
let wireTooltip = '';
let wireBoundsPadding = 25 / view.scale;
for(const poly of catenaryPolylines){
let b = poly.bounds;
if(cursorX >= b[0] - wireBoundsPadding && cursorX <= b[2] + wireBoundsPadding && cursorY >= b[1] - wireBoundsPadding && cursorY <= b[3] + wireBoundsPadding){
let pts = poly.points;
for(let i = 0; i < pts.length - 1; i++){
let d2 = distToSegmentSquared(cursorX, cursorY, pts[i][0], pts[i][1], pts[i+1][0], pts[i+1][1]);
if(d2 < bestWireDistSq){
bestWireDistSq = d2;
wireTooltip = poly.tooltip;
}
}
}
}
let maxWireDist = Math.min(Math.max(1.2, 12 / view.scale), 80);
if(bestWireDistSq <= maxWireDist * maxWireDist){
return wireTooltip;
}
}
let bestDistance = Infinity;
let test = {};
let boundsPadding = 40/view.scale;
@@ -684,16 +753,139 @@ export function setTrackColorMode(mode){
view.dirty = true;
}
export function distToSegmentSquared(px, pz, x1, z1, x2, z2) {
let dx = x2 - x1;
let dz = z2 - z1;
let l2 = dx * dx + dz * dz;
if (l2 === 0) {
let ex = px - x1;
let ez = pz - z1;
return ex * ex + ez * ez;
}
let t = ((px - x1) * dx + (pz - z1) * dz) / l2;
t = Math.max(0, Math.min(1, t));
let nx = x1 + t * dx;
let nz = z1 + t * dz;
let rx = px - nx;
let rz = pz - nz;
return rx * rx + rz * rz;
}
export function addCatenaryData(data){
catenaryData = data;
catenaryPolylines = data.polylines || [];
catenarySubstations = data.substations || [];
catenaryNeutrals = data.neutral_sections || [];
catenaryInterruptions = data.interruptions || [];
catenarySigns = data.signs || [];
for(const sub of catenarySubstations){
sub.tooltip = `<h1>⚡ Тяговая подстанция ${sub.id}</h1>` +
`Номинальное напряжение: ${sub.nominal_voltage} В DC\n` +
`Питающее напряжение: ${sub.supply_voltage} В DC\n` +
`Максимальная мощность: ${sub.maximum_load} кВт\n` +
`Инвертор рекуперации: ${sub.has_inverter ? 'Есть (возврат энергии в сеть)' : 'Нет'}`;
}
for(const n of catenaryNeutrals){
n.tooltip = `<h1>⛔ Токораздел (Нейтральная вставка): ${n.id}</h1>` +
`Напряжение: 0 В (Обесточенный участок)\n` +
`Длина вставки: ${n.total_length} м\n` +
`Проводов: ${n.wire_count}\n` +
`Знаки: ${(n.signs || []).map(s => s.type).join(', ')}\n` +
`Внимание: Отключить тягу перед прохождением!`;
}
for(const inter of catenaryInterruptions){
inter.tooltip = `<h1>⤓ Прерывание контактной сети</h1>` +
`Знаки: ${(inter.signs || []).join(', ')}\n` +
`Внимание: Опустить токоприемники (Drop Pantographs)!`;
}
for(const p of catenaryPolylines){
let catDesc = 'Одинарная подвеска (Single 6.0м)';
if(p.category === 'dual') catDesc = 'Двойная подвеска (Dual 6.0м, повышенная токоотдача)';
else if(p.category === 'quad') catDesc = 'Усиленная 4-проводная (Quad 6.0м, горный перевал)';
else if(p.category === 'trolley') catDesc = 'Трамвайная/простая подвеска (Trolley 6.0м, маневровые)';
else if(p.category === 'side_rail') catDesc = 'Боковой 3-й контактный рельс (Side Rail 4.5м, шахты)';
p.tooltip = `<h1>⚡ Контактная сеть: ${p.is_neutral ? 'Токораздел (0 В)' : p.voltage + ' В DC'}</h1>` +
`Напряжение: ${p.voltage} В DC\n` +
`Тип подвески: ${catDesc}\n` +
`Тяговая подстанция: ${p.substation}\n` +
`Длина участка: ${p.length} м (${p.wire_count} звеньев)`;
}
setCatenaryColorMode(catenaryColorMode);
}
export function setCatenaryColorMode(mode){
catenaryColorMode = mode;
for(const p of catenaryPolylines){
if(p.is_neutral){
p.color = '#e63946';
continue;
}
switch(mode){
case 'Substation':
p.color = Config.catenarySubstationColors[p.substation] || '#3a86ff';
break;
case 'Wire Type':
p.color = Config.catenaryWireTypeColors[p.category] || '#48cae4';
break;
case 'Voltage':
default:
p.color = Config.catenaryVoltageColors[String(p.voltage)] || '#00d2ff';
break;
}
}
updateCatenaryLegend();
view.dirty = true;
}
export function updateCatenaryLegend(){
const elem = document.getElementById('legendCatenaryColors');
if(!elem) return;
elem.innerHTML = '';
switch(catenaryColorMode){
case 'Voltage':
elem.innerHTML = `
<span class="colorKey"><span style="background:${Config.catenaryVoltageColors['1500']}"></span> 1500V DC (Магистраль)</span>
<span class="colorKey"><span style="background:${Config.catenaryVoltageColors['1000']}"></span> 1000V DC (Боковой рельс)</span>
<span class="colorKey"><span style="background:${Config.catenaryVoltageColors['750']}"></span> 750V DC (Троллей)</span>
<span class="colorKey"><span style="background:${Config.catenaryVoltageColors['0']}"></span> 0V (Токораздел)</span>
`;
break;
case 'Substation':
let subHtml = '';
for(const [subId, col] of Object.entries(Config.catenarySubstationColors)){
subHtml += `<span class="colorKey"><span style="background:${col}"></span> ${subId}</span> `;
}
elem.innerHTML = subHtml;
break;
case 'Wire Type':
elem.innerHTML = `
<span class="colorKey"><span style="background:${Config.catenaryWireTypeColors['single']}"></span> Single (6.0м)</span>
<span class="colorKey"><span style="background:${Config.catenaryWireTypeColors['dual']}"></span> Dual (6.0м)</span>
<span class="colorKey"><span style="background:${Config.catenaryWireTypeColors['quad']}"></span> Quad (Перевал 6.0м)</span>
<span class="colorKey"><span style="background:${Config.catenaryWireTypeColors['trolley']}"></span> Trolley (6.0м)</span>
<span class="colorKey"><span style="background:${Config.catenaryWireTypeColors['side_rail']}"></span> 3-й рельс (4.5м)</span>
`;
break;
}
}
export function setLocationUpdateRate(rate){
locationUpdateRate = rate;
}
export function connectPlayerLocation(address, status){
if(!address.startsWith('http://')){
address.replaceAll(/.*:\/\//g, '');
address = 'http://' + address;
let cleanAddress = (address || '').trim().replace(/^https?:\/\//i, '').replace(/\/+$/, '');
cleanAddress = 'http://' + cleanAddress;
if(!cleanAddress.endsWith('/location')){
cleanAddress += '/location';
}
address += '/location';
address = cleanAddress;
console.log(`Connecting to game at ${address}`);
let started = false;
const endConnect = () => {