AstroCol/src/pages/game/fleet.astro

721 lines
22 KiB
Plaintext

---
import LoggedIn from '../../layouts/LoggedIn.astro';
import locationManager from '../../lib/classes/managers/LocationManager';
import { Planet } from '../../lib/classes/managers/PlanetManager';
import SystemManager from '../../lib/classes/managers/SystemManager';
import { getAllFleetByUser } from '../../lib/db/fleet';
import { getAllShips } from '../../lib/db/ships';
import { getUserSpyReports } from '../../lib/db/users';
import { getName } from '../../lib/utils/langDriver';
const { token, user, lang } = Astro.locals;
const active: SystemManager | Planet = Astro.locals.active;
const activeId = active instanceof SystemManager ? active.data._id : active._id;
const ships = await getAllShips();
const url = Astro.url.origin;
if(Astro.request.method === "POST") {
const form = await Astro.request.formData();
const cargo: Array<{ id: string, amount: number }> = [];
form.forEach((v, k) => {
if(k.match(/cargo\[\d\]\[id\]/)) {
const amount = parseInt(form.get(`${k.substring(0, k.indexOf("]"))}][amount]`)?.toString() ?? "0");
if(amount === 0 || isNaN(amount)) return;
cargo.push({
id: v.toString(),
amount
});
}
});
const destination = form.get('mission') === "EXPEDITION" || form.get('mission') === "COLONIZE" ?
form.get('toSystem') ?
form.get('destination-system')?.toString() :
form.get('destination-sector')?.toString()
:
form.get('toSystem') ?
form.get('destination-system')?.toString() :
form.get('destination-planet')?.toString();
const fleetData = {
source: active instanceof SystemManager ? active.data._id : active._id,
destination,
mission: form.get('mission')?.toString() ?? "NULL",
ships: ships.map(ship => {
const amount = parseInt(form.get(`ship-amount-${ship.id}`)?.toString() ?? "0");
if(amount === 0) return null;
return {
id: ship.id,
amount
}
}).filter(s => s !== null),
cargo
}
const response = await fetch(`${Astro.url.origin}/api/fleet/send`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
body: JSON.stringify(fleetData)
});
console.log(await response.json());
return Astro.redirect('/game/fleet');
}
await locationManager.updateFleet();
const fleet = (await getAllFleetByUser(user.id)).filter(f => new Date().getTime() - f.arrivalTime.getTime() < 0);
fleet.sort((a, b) => a.departureTime.getTime() - b.departureTime.getTime());
const galaxies = locationManager.galaxies;
let own = 0;
let friendly = 0;
let enemy = 0;
for(const f of fleet) {
const source = locationManager.findId(f.source);
if(source !== null) {
if(source instanceof SystemManager) {
if(source.data.ownedBy.id.equals(user.id)) own++;
else if(!f.returning) {
if(f.mission === "SPY" || f.mission === "ATTACK") enemy++;
else friendly++;
}
} else {
if(source.system.data.ownedBy.id.equals(user.id)) own++;
else if(!f.returning) {
if(f.mission === "SPY" || f.mission === "ATTACK") enemy++;
else friendly++;
}
}
}
}
const discoveredSectors = await getUserSpyReports(user.id);
const sectorsList = galaxies.map(galaxy => {
return {
_id: galaxy._id,
name: galaxy.name,
sectors: galaxy.sectors.map(sector => {
return {
_id: sector._id,
name: sector.name,
systems: sector.systems.map(system => {
const discovered = discoveredSectors.find(s => s.systemId.equals(system.data._id));
if(!discovered && !system.data.ownedBy.id.equals(user.id)) return {
_id: system.data._id,
name: `${system.data.name} (not discovered)`,
planets: []
}
return {
_id: system.data._id,
name: system.data.name,
planets: system.planets.map(planet => {
return {
_id: planet._id,
name: planet.name
}
})
}
})
}
})
}
});
---
<LoggedIn id="fleet" title="Fleet">
<label for="fleet-toggle">
<input type="checkbox" id="fleet-toggle">
<div class="fleet-status">
<h2>Fleet status ({fleet.length} total | {own} own | {friendly} friendly | {enemy} enemy)</h2>
<ul>
{fleet.map(f => {
const source = locationManager.findId(f.source);
const destination = locationManager.findId(f.destination);
return (<li>
<div class="ship-cards">
<span style={`${source instanceof SystemManager && "color:red;"}`}>{(source instanceof SystemManager ? source.data.name : source?.name) ?? "?"}</span> =&gt; <span style={`${destination instanceof SystemManager && "color:red;"}`}>{(destination instanceof SystemManager ? destination.data.name : destination?.name) ?? "?"}</span> | {f.mission}{f.returning ? " (R)" : ""}<br />
Containing: {f.ships.map(s => {
const ship = ships.find(ship => ship.id === s.id);
return `${getName(lang, 'ships', s.id)} x${s.amount}`;
}).join(', ')} <br />
Cargo: {typeof f.cargo === "undefined" || f.cargo.length === 0 ? <>None</> : f.cargo.map(c => `${c.amount} ${getName(lang, 'resources', c.id)}`).join(', ')} <br />
Departured at: {f.departureTime.toISOString()} <br />
Arrives: {f.arrivalTime.toISOString()} ({Math.floor((f.arrivalTime.getTime() - new Date().getTime()) / 1000)}) <br />
<button class="revert" style={`${f.returning && "display: none;"}`} data-id={f._id.toString()}>Revert</button>
</div>
</li>)
})}
</ul>
</div>
</label>
<div class="fleet-send-container">
<form method="post" id="send-fleet">
<h1>Sending fleet from {active instanceof SystemManager ? active.data.name : active.name}</h1>
<hr />
<h2>Ships</h2>
<div class="fleet-send-ships">
{active.ships.ships.map(ship => <div class="fleet-ship-card">
<h3>{getName(lang, 'ships', ship.data.id)} - {ship.amount}</h3>
<input type="number" value="0" min="0" max={ship.amount} id={ship.data.id} name={`ship-amount-${ship.data.id}`} class="ship-amount-inputs" />
</div>)}
</div>
<hr />
<h2>Mission</h2>
<label for="attack"><input type="radio" name="mission" value="ATTACK" id="attack" required />Attack</label>
<label for="transport"><input type="radio" name="mission" value="TRANSPORT" id="transport" />Transport</label>
<label for="transfer"><input type="radio" name="mission" value="TRANSFER" id="transfer" />Transfer</label>
<label for="expedition"><input type="radio" name="mission" value="EXPEDITION" id="expedition" />Expedition</label>
<label for="spy"><input type="radio" name="mission" value="SPY" id="spy" />Spying</label>
<label for="colonize"><input type="radio" name="mission" value="COLONIZE" id="colonize" />Colonize</label>
<label><input type="checkbox" name="toSystem" />Send to system</label>
<hr />
<h2>Send to</h2>
<div class="fleet-destination">
<div>
<h3>Galaxy</h3>
<select id="destination-galaxy" name="destination-galaxy">
{galaxies.map(galaxy => <option value={galaxy._id.toString()}>{galaxy.name}</option>)}
</select>
</div>
<div>
<h3>Sector</h3>
<select id="destination-sector" name="destination-sector"></select>
</div>
<div>
<h3>System</h3>
<select id="destination-system" name="destination-system"></select>
<div id="destination-system-error"></div>
</div>
<div>
<h3>Planet</h3>
<select id="destination-planet" name="destination-planet"></select>
<div id="destination-planet-error"></div>
</div>
</div>
<hr />
<h2>Cargo</h2>
<div id="cargo-available">
Solid: <span id="available-solid">0</span><br />
Liquid: <span id="available-liquid">0</span><br />
Gas: <span id="available-gas">0</span><br />
</div>
<div class="cargo-container" id="cargo-container">
<div class="cargo-item">
<select name="cargo[0][id]" class="select">
<option value="coal">Coal</option>
<option value="iron">Iron</option>
<option value="gold">Gold</option>
<option value="water">Water</option>
<option value="sulfuric-acid">Sulfuric Acid</option>
<option value="liquid-nitrogen">Liquid Nitrogen</option>
<option value="hydrogen">Hydrogen</option>
<option value="oxygen">Oxygen</option>
<option value="helium-3">Helium-3</option>
</select>
<input type="number" name="cargo[0][amount]" class="input" />
<button type="button" class="cargo-sendall" name="cargo[0][all]">All</button>
</div>
</div>
<div id="cargo-add-new">Add new +</div>
<hr />
<h2>Fuel consumption</h2>
<div>Base usage from ships: <span id="fuel-consumption">0</span></div>
<div>Distance: <span id="fuel-consumption-distance">0</span></div>
<div><span id="fuel-consumption-required">0</span> hydrogen will be added to cargo as fuel</div>
<hr />
<h2>Estimates</h2>
<div>Fleet will arrive at destination at: <span id="arrival-time"></span></div>
<div>Fleet will return at: <span id="return-time"></span></div>
<hr />
<button type="submit">Send fleet</button>
</form>
</div>
</LoggedIn>
<style>
* {
color: white;
}
.ship-cards {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 40px;
column-gap: 2%;
}
#ship-modal-background {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background: rgba(0, 0, 0, 0.5);
z-index: 100;
}
#ship-modal-details {
display: none;
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 80%;
max-width: 800px;
background: rgba(0, 0, 0, 0.9);
border-radius: 8px;
padding: 1rem;
z-index: 101;
}
.fleet-status {
margin-top: 40px;
background-color: gray;
border-radius: 10px;
padding: 2px;
}
.fleet-status ul {
list-style-type: none;
padding: 0;
opacity: 0;
max-height: 0;
overflow: hidden;
transition-property: max-height;
transition-duration: 0.5s;
transition-timing-function: ease;
}
#fleet-toggle {
position: absolute;
top: -9999px;
left: -9999px;
}
label {
-webkit-appearance: push-button;
-moz-appearance: button;
margin: 60px 0 10px 0;
cursor: pointer;
}
#fleet-toggle:checked ~ .fleet-status ul {
opacity: 1;
height: auto;
max-height: 1000px;
}
.fleet-send-container {
margin-top: 40px;
background-color: gray;
border-radius: 10px;
padding: 2px;
}
.fleet-send-ships {
display: flex;
flex-direction: row;
flex-wrap: wrap;
row-gap: 40px;
column-gap: 2%;
margin-top: 40px;
}
.fleet-ship-card {
background-color: gray;
border-radius: 10px;
padding: 2px;
}
.fleet-ship-card h3 {
margin: 0;
}
.fleet-ship-card input {
width: 10rem;
color: black;
}
.fleet-destination {
display: flex;
flex-direction: row;
column-gap: 5%;
}
.fleet-destination select {
width: 10rem;
height: 3rem;
color: black;
background-color: darkgray;
}
.cargo-add-new {
background-color: #aaaaaa;
border-radius: 10px;
padding: 2px;
width: fit-content;
color: black;
}
.select, .input {
color: black;
}
</style>
<script define:vars={{ lang, sectorsList, url, ships, activeId }}>
const revertButtons = document.querySelectorAll('.revert');
const destinationGalaxy = document.getElementById('destination-galaxy');
const destinationSector = document.getElementById('destination-sector');
const destinationSystem = document.getElementById('destination-system');
const destinationPlanet = document.getElementById('destination-planet');
const toSystemCheckbox = document.querySelector('input[name="toSystem"]');
const sendAllButtons = document.querySelectorAll('.cargo-sendall');
const shipInputs = document.querySelectorAll('.ship-amount-inputs');
const sendFleetForm = document.getElementById('send-fleet');
if(!revertButtons || !destinationGalaxy || !destinationSector || !destinationSystem || !destinationPlanet || !toSystemCheckbox || !sendAllButtons || !shipInputs || !sendFleetForm) {
console.error('Could not find all elements');
return;
};
revertButtons.forEach(revBut => revBut.addEventListener('click', async evt => {
await fetch(`${url}/api/fleet/revert`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
id: evt.target.dataset.id
})
});
window.location.reload();
}));
destinationGalaxy.addEventListener('change', () => {
const galaxy = sectorsList.find(galaxy => galaxy._id === destinationGalaxy.value);
if(!galaxy) return;
destinationSector.innerHTML = '';
for(const sector of galaxy.sectors) {
const opt = document.createElement('option');
opt.value = sector._id;
opt.innerText = sector.name;
destinationSector.appendChild(opt);
}
if(destinationSector.children.length === 0) {
const opt = document.createElement('option');
opt.value = '';
opt.innerText = 'No sectors';
destinationSector.appendChild(opt);
}
destinationSector.dispatchEvent(new Event('change'));
});
destinationSector.addEventListener('change', () => {
const galaxy = sectorsList.find(galaxy => galaxy._id === destinationGalaxy.value);
if(!galaxy) return;
const sector = galaxy.sectors.find(sector => sector._id === destinationSector.value);
if(!sector) return;
destinationSystem.innerHTML = '';
document.getElementById('destination-system-error').innerHTML = '';
for(const system of sector.systems) {
const opt = document.createElement('option');
opt.value = system._id;
opt.innerText = system.name;
destinationSystem.appendChild(opt);
}
if(destinationSystem.children.length === 0) {
const opt = document.createElement('option');
opt.value = '';
opt.innerText = 'No systems';
destinationSystem.appendChild(opt);
document.getElementById('destination-system-error').innerHTML = "No systems";
}
destinationSystem.dispatchEvent(new Event('change'));
});
destinationSystem.addEventListener('change', () => {
const galaxy = sectorsList.find(galaxy => galaxy._id === destinationGalaxy.value);
if(!galaxy) return;
const sector = galaxy.sectors.find(sector => sector._id === destinationSector.value);
if(!sector) return;
const system = sector.systems.find(system => system._id === destinationSystem.value);
if(!system) {
destinationPlanet.innerHTML = '';
const opt = document.createElement('option');
opt.value = '';
opt.innerText = 'No planets';
destinationPlanet.appendChild(opt);
document.getElementById('destination-planet-error').innerHTML = "No planets";
return;
}
destinationPlanet.innerHTML = '';
document.getElementById('destination-planet-error').innerHTML = '';
for(const planet of system.planets) {
const opt = document.createElement('option');
opt.value = planet._id;
opt.innerText = planet.name;
destinationPlanet.appendChild(opt);
}
if(destinationPlanet.children.length === 0) {
const opt = document.createElement('option');
opt.value = '';
opt.innerText = 'No planets';
destinationPlanet.appendChild(opt);
}
destinationPlanet.dispatchEvent(new Event('change'));
});
destinationPlanet.addEventListener('change', calculateDistance);
destinationGalaxy.dispatchEvent(new Event('change'));
toSystemCheckbox.addEventListener('change', () => {
destinationPlanet.disabled = toSystemCheckbox.checked;
calculateDistance();
});
sendAllButtons.forEach(button => {
button.addEventListener('click', () => handleAllClick(button));
});
const addNew = document.getElementById('cargo-add-new');
addNew.addEventListener('click', () => {
const cargoContainer = document.querySelector('.cargo-container');
const cargoItem = document.createElement('div');
cargoItem.classList.add('cargo-item');
const selectMenu = document.createElement('select');
selectMenu.name = `cargo[${cargoContainer.children.length}][id]`;
selectMenu.classList.add('select');
for(const res of ['coal', 'iron', 'gold', 'water', 'sulfuric-acid', 'liquid-nitrogen', 'hydrogen', 'oxygen', 'helium-3']) {
const opt = document.createElement('option');
opt.value = res;
opt.innerText = res;
selectMenu.appendChild(opt);
}
cargoItem.appendChild(selectMenu);
const input = document.createElement('input');
input.type = 'number';
input.name = `cargo[${cargoContainer.children.length}][amount]`;
input.classList.add('input');
cargoItem.appendChild(input);
const button = document.createElement('button');
button.type = 'button';
button.addEventListener('click', () => handleAllClick(button));
button.classList.add('cargo-sendall');
button.name = `cargo[${cargoContainer.children.length}][all]`;
button.innerText = 'All';
cargoItem.appendChild(button);
cargoContainer.appendChild(cargoItem);
});
shipInputs.forEach(input => {
input.addEventListener('input', () => {
const fuelConsumption = Array.from(shipInputs).reduce((acc, cur) => {
const ship = ships.find(ship => ship.id === cur.id);
if(!ship) return acc;
return acc + ship.fuelConsumption * parseInt(cur.value);
}, 0);
document.getElementById('fuel-consumption').innerText = fuelConsumption;
const availableSolid = Array.from(shipInputs).reduce((acc, cur) => {
const ship = ships.find(ship => ship.id === cur.id);
if(!ship) return acc;
return acc + ship.capacity.solid * parseInt(cur.value);
}, 0);
const availableLiquid = Array.from(shipInputs).reduce((acc, cur) => {
const ship = ships.find(ship => ship.id === cur.id);
if(!ship) return acc;
return acc + ship.capacity.liquid * parseInt(cur.value);
}, 0);
const availableGas = Array.from(shipInputs).reduce((acc, cur) => {
const ship = ships.find(ship => ship.id === cur.id);
if(!ship) return acc;
return acc + ship.capacity.gas * parseInt(cur.value);
}, 0);
document.getElementById('available-solid').innerText = availableSolid;
document.getElementById('available-liquid').innerText = availableLiquid;
document.getElementById('available-gas').innerText = availableGas;
calculateRequiredFuel();
});
});
sendFleetForm.addEventListener('submit', evt => {
evt.preventDefault();
// at least one ship
if(Array.from(shipInputs).reduce((acc, cur) => acc + parseInt(cur.value), 0) === 0) {
alert('You need to send at least one ship');
return;
}
// source === destination
const destinationId = document.querySelector('input[name="toSystem"]').checked ? document.getElementById('destination-system').value : document.getElementById('destination-planet').value;
if(destinationId === activeId) {
alert('Source and destination cannot be the same');
return;
}
// check fuel
const requiredFuel = parseInt(document.getElementById('fuel-consumption-required').innerText);
const storedFuel = parseInt(document.getElementById('resource-hydrogen').dataset.resAmount);
if(storedFuel < requiredFuel) {
alert('Not enough fuel in storage');
return;
}
const cargoSolidStored = Array.from(document.getElementById('cargo-container').children).reduce((acc, cur) => {
const resId = cur.children.item(0).value;
if(resId === "coal" || resId === "iron" || resId === "gold") {
const amount = cur.children.item(1).value === "" ? 0 : parseInt(cur.children.item(1).value);
return acc + amount;
}
return acc;
}, 0);
const cargoLiquidStored = Array.from(document.getElementById('cargo-container').children).reduce((acc, cur) => {
const resId = cur.children.item(0).value;
if(resId === "water" || resId === "sulfuric-acid" || resId === "liquid-nitrogen") {
const amount = cur.children.item(1).value === "" ? 0 : parseInt(cur.children.item(1).value);
return acc + amount;
}
return acc;
}, 0);
const cargoGasStored = Array.from(document.getElementById('cargo-container').children).reduce((acc, cur) => {
const resId = cur.children.item(0).value;
if(resId === "hydrogen" || resId === "oxygen" || resId === "helium-3") {
const amount = cur.children.item(1).value === "" ? 0 : parseInt(cur.children.item(1).value);
return acc + amount;
}
return acc;
}, 0);
const cargoSolidCapacity = document.getElementById('available-solid').innerText;
const cargoLiquidCapacity = document.getElementById('available-liquid').innerText;
const cargoGasCapacity = document.getElementById('available-gas').innerText;
if(cargoSolidStored > cargoSolidCapacity) {
alert('Not enough solid capacity in ships');
return;
}
if(cargoLiquidStored > cargoLiquidCapacity) {
alert('Not enough liquid capacity in ships');
return;
}
if(cargoGasStored > cargoGasCapacity) {
alert('Not enough gas capacity in ships');
return;
}
if((cargoGasStored + requiredFuel) > cargoGasCapacity) {
alert('Not enough gas capacity in ships for fuel');
return;
}
// add hydrogen to cargo
// const hydrogenInCargo = Array.from(document.getElementById('cargo-container').children).find(c => c.children.item(0).value === "hydrogen")?.children.item(1).value ?? 0;
// const formData = new FormData(evt.target);
sendFleetForm.submit();
});
async function calculateDistance() {
const source = activeId;
const destination = document.querySelector('input[name="toSystem"]').checked ? document.getElementById('destination-system').value : document.getElementById('destination-planet').value;
if(!source || !destination) return;
const response = await fetch(`${url}/api/fleet/getDistance?source=${source}&destination=${destination}`);
const data = await response.json();
if(data.error) {
document.getElementById('fuel-consumption-distance').innerText = "Error";
return;
}
const distance = data.distance;
document.getElementById('fuel-consumption-distance').innerText = distance;
document.getElementById('arrival-time').innerText = new Date(new Date().getTime() + distance * 1000).toLocaleString();
document.getElementById('return-time').innerText = new Date(new Date().getTime() + distance * 1000 * 2).toLocaleString();
calculateRequiredFuel();
}
function calculateRequiredFuel() {
const fuelConsumption = parseInt(document.getElementById('fuel-consumption').innerText);
const distance = parseInt(document.getElementById('fuel-consumption-distance').innerText);
if(isNaN(fuelConsumption) || isNaN(distance)) {
document.getElementById('fuel-consumption-required').innerText = "Error";
return;
}
if(distance === 0) {
document.getElementById('fuel-consumption-required').innerText = 0;
return;
}
const requiredFuel = Math.ceil(Math.log(fuelConsumption * distance) / Math.log(1.01));
document.getElementById('fuel-consumption-required').innerText = requiredFuel;
}
function handleAllClick(button) {
const avaliableResources = [];
for(const resItem of document.querySelectorAll('.resourcebar-item')) {
avaliableResources.push({
id: resItem.dataset.resId,
amount: parseInt(resItem.dataset.resAmount)
});
}
const resourceId = button.parentElement.children.item(0).value;
const amount = avaliableResources.find(res => res.id === resourceId)?.amount ?? 0;
button.parentElement.children.item(1).value = amount;
}
</script>