Compare commits

..

3 Commits

Author SHA1 Message Date
Aelita4 3e50e84b0e
Add wiki section 2024-11-08 10:56:44 +01:00
Aelita4 2ad7e77273
Fix bug with invalid access token being assigned 2024-11-04 11:06:21 +01:00
Aelita4 8d34bfe64f
Fix register script 2024-11-04 10:26:05 +01:00
22 changed files with 309 additions and 73 deletions

7
public/css/markdown.css Normal file
View File

@ -0,0 +1,7 @@
.markdown {
line-height: 1.6;
}
.markdown a {
color: #00a8cc;
}

View File

@ -42,6 +42,13 @@ const listOfElements: Array<NavElement> = [{
url: "#", url: "#",
show: "always", show: "always",
position: "top" position: "top"
}, {
id: "wiki",
title: getName(lang, "general", "nav-wiki"),
type: "simple",
url: "/wiki",
show: "always",
position: "top"
}, { }, {
id: "login", id: "login",
title: getName(lang, "general", "nav-login"), title: getName(lang, "general", "nav-login"),

View File

@ -0,0 +1,22 @@
---
import WikiPageEntry from "../types/WikiPageEntry";
const { entry } = Astro.props as { entry: WikiPageEntry };
---
<li>
<a href={entry.url}>{entry.title}</a>
{entry.children.length > 0 && (
<ul>
{entry.children.map((child: WikiPageEntry) => <Astro.self entry={child} />)}
</ul>
)}
</li>
<style>
a {
color: lime;
}
li {
list-style-type: none;
}
</style>

View File

@ -27,6 +27,7 @@ if(!Astro.cookies.has('language')) {
<meta name="description" content="Astro description" /> <meta name="description" content="Astro description" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="stylesheet" href="/css/markdown.css" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>{title}</title> <title>{title}</title>
</head> </head>

105
src/layouts/Wiki.astro Normal file
View File

@ -0,0 +1,105 @@
---
import Layout from '../layouts/Layout.astro';
import NavBar from '../components/NavBar.astro';
import WikiEntry from '../components/WikiEntry.astro';
import WikiPageEntry from '../types/WikiPageEntry';
import { getUserByAccessToken } from '../lib/db/users';
let loggedIn = false;
const loggedToken = Astro.cookies.get('sessionToken')?.value;
const username = Astro.cookies.get('username')?.value;
if(typeof loggedToken !== 'undefined' && typeof username !== 'undefined') {
const checkUser = await getUserByAccessToken(loggedToken);
if(checkUser !== null && checkUser.username === username) loggedIn = true;
}
const wikiPages = await Astro.glob('../pages/wiki/**/*.md');
const pagesArray: Array<WikiPageEntry> = [{
title: 'Home',
url: '/wiki',
depth: 0,
parent: '',
children: []
}];
const tmp = [];
for(const page of wikiPages) {
if(typeof page.url === 'undefined') continue;
if(page.url === '/wiki') continue;
const url = page.url?.split('/');
const title = page.frontmatter.title;
const parent = url.slice(0, url.length - 1).join('/');
const data: WikiPageEntry = {
title,
url: page.url,
depth: url.length - 2,
parent,
children: []
}
tmp.push(data);
}
function findRecursively(tree: Array<WikiPageEntry>, value: string): WikiPageEntry | null {
if(tree.length === 0) return null;
for(const page of tree) {
if(page.url === value) return page;
const res = findRecursively(page.children, value)
if(res !== null) return res;
}
return null;
}
for(const page of tmp.sort((a, b) => a.depth - b.depth)) {
if(page.depth === 1) {
pagesArray.push(page);
} else {
const parent = findRecursively(pagesArray, page.parent);
if(parent !== null) parent.children.push(page);
}
}
const { frontmatter } = Astro.props;
---
<Layout title="Wiki">
<NavBar loggedIn=`${loggedIn}` active="wiki" />
<div class="container">
<div class="sidebar">
<ul>
{pagesArray.map(page => <WikiEntry entry={page} />)}
</ul>
</div>
<div class="content">
<h1>{frontmatter.title}</h1>
<div class="markdown">
<slot />
</div>
</div>
</div>
</Layout>
<style>
* {
color: white;
}
.container {
display: flex;
flex-direction: row;
margin-top: 15px;
}
.sidebar {
width: 30rem;
height: 100%;
border-right: 3px solid rgb(77, 76, 78);
}
.content {
width: calc(100% - 30rem);
margin-left: 8em;
margin-right: 22em;
overflow-wrap: break-word;
}
</style>

View File

@ -10,6 +10,11 @@ export const getPlanetById = async (id: ObjectId) => {
return await (await Planets()).findOne({ _id: id }) as DBPlanet; return await (await Planets()).findOne({ _id: id }) as DBPlanet;
} }
export const createPlanet = async (planet: DBPlanet) => {
const planets = await Planets();
return await planets.insertOne(planet);
}
export const updatePlanetResources = async (planetId: ObjectId, resources: Array<any>) => { export const updatePlanetResources = async (planetId: ObjectId, resources: Array<any>) => {
const planets = await Planets(); const planets = await Planets();
await planets.updateOne({ _id: planetId }, { await planets.updateOne({ _id: planetId }, {

View File

@ -11,3 +11,7 @@ export const getSectorById = async (id: ObjectId) => {
_id: id _id: id
}) as DBSector; }) as DBSector;
} }
export const addSystemToSector = async (sectorId: ObjectId, systemId: ObjectId) => {
return await (await Sectors()).updateOne({ _id: sectorId }, { $push: { systems: systemId } });
}

View File

@ -12,6 +12,11 @@ export const getSystemById = async (id: ObjectId) => {
}) as DBSystem; }) as DBSystem;
} }
export const createSystem = async (system: DBSystem) => {
const systems = await Systems();
return await systems.insertOne(system);
}
export const updateSystemStructures = async (systemId: ObjectId, structures: Array<{ id: string, level: number }>) => { export const updateSystemStructures = async (systemId: ObjectId, structures: Array<{ id: string, level: number }>) => {
const systems = await Systems(); const systems = await Systems();
await systems.updateOne({ _id: systemId }, { await systems.updateOne({ _id: systemId }, {

View File

@ -10,8 +10,9 @@ export const getAllUsers = async () => {
return users.find({}).toArray() as Promise<DBUser[]>; return users.find({}).toArray() as Promise<DBUser[]>;
} }
export const createUser = async (username: string, email: string, password: string) => { export const createUser = async (id: ObjectId, username: string, email: string, password: string, mainPlanet: ObjectId) => {
const user = { const user: DBUser = {
_id: id,
username, username,
email, email,
password: await hash(password), password: await hash(password),
@ -19,13 +20,11 @@ export const createUser = async (username: string, email: string, password: stri
createdAt: new Date(), createdAt: new Date(),
updatedAt: new Date(), updatedAt: new Date(),
research: [], research: [],
mainPlanet
} }
await (await Users()).insertOne(user); await (await Users()).insertOne(user);
const newUser = await getUserByNickOrEmail(username); return user;
if(!newUser) return user;
return newUser;
} }
export const deleteUser = async (id: ObjectId) => { export const deleteUser = async (id: ObjectId) => {

View File

@ -13,12 +13,12 @@ if(loggedToken === null || username === "") return Astro.redirect('/logout');
const checkUser = await getUserByAccessToken(loggedToken); const checkUser = await getUserByAccessToken(loggedToken);
if(checkUser === null || checkUser.username !== username) return Astro.redirect('/logout'); if(checkUser === null || checkUser.username !== username) return Astro.redirect('/logout');
if(currentPlanetId === null) return Astro.redirect('/game/logout');
const currentPlanetId = Astro.cookies.get('currentPlanet')?.value ?? null; const currentPlanetId = Astro.cookies.get('currentPlanet')?.value ?? null;
if(currentPlanetId === null) return Astro.redirect('/logout');
const currentPlanet = locationManager.getPlanet(new ObjectId(currentPlanetId)); const currentPlanet = locationManager.getPlanet(new ObjectId(currentPlanetId));
if(currentPlanet === undefined) { if(currentPlanet === undefined) {
Astro.cookies.delete('planetid'); Astro.cookies.delete('planetid');
return Astro.redirect('/game/logout'); return Astro.redirect('/logout');
} }
--- ---

View File

@ -27,7 +27,7 @@ if(Astro.request.method === "POST") {
if(!user) throw new Error("User not found"); if(!user) throw new Error("User not found");
const sessionTime = config.SESSION_TIME_MINUTES * 60; const sessionTime = config.SESSION_TIME_MINUTES * 60;
const res = await fetch(`https://localhost:4321/api/auth/generateAccessToken`, { const res = await fetch(`${Astro.url.origin}/api/auth/generateAccessToken`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
username, username,

View File

@ -1,10 +1,19 @@
--- ---
import Layout from '../layouts/Layout.astro';
import NavBar from '../components/NavBar.astro'; import NavBar from '../components/NavBar.astro';
import Layout from '../layouts/Layout.astro';
import { createUser } from '../lib/db/users'; import { createUser } from '../lib/db/users';
import { AstroCookieSetOptions } from 'astro';
import { ObjectId } from 'mongodb';
import config from '../../config.json'; import config from '../../config.json';
import { getAllGalaxies } from '../lib/db/galaxies';
import { createPlanet } from '../lib/db/planets';
import { addSystemToSector } from '../lib/db/sectors';
import { createSystem } from '../lib/db/systems';
import DBPlanet from '../types/db/DBPlanet';
import DBSystem from '../types/db/DBSystem';
import locationManager from '../lib/classes/managers/LocationManager';
let error = ""; let error = "";
@ -15,52 +24,67 @@ if(Astro.request.method === "POST") {
const password = data.get("password") as string | ""; const password = data.get("password") as string | "";
const password2 = data.get("password2") as string | ""; const password2 = data.get("password2") as string | "";
if(username === "") { if(username === "") error = "username is required";
error = "username is required"; if(username.match(/^[a-zA-Z0-9]{3,20}$/) === null) error = "username must be between 3 and 20 characters long and can only contain letters and numbers";
Astro.redirect("/register"); if(email === "") error = "email is required";
} if(email.match(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) === null) error = "email is invalid";
if(password === "") error = "password is required";
if(email === "") { if(password2 === "") error = "password2 is required";
error = "email is required"; if(password.length < 8 || password.length > 50) error = "password must be between 8 and 50 characters long";
Astro.redirect("/register"); if(password !== password2) error = "passwords must match";
}
if(password === "") {
error = "password is required";
Astro.redirect("/register");
}
if(password2 === "") {
error = "password2 is required";
Astro.redirect("/register");
}
if(username.length < 3 || username.length > 20) {
error = "username must be between 3 and 20 characters long";
Astro.redirect("/register");
}
if(email.match(/^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/) === null) {
error = "email is invalid";
Astro.redirect("/register");
}
if(password.length < 8 || password.length > 50) {
error = "password must be between 8 and 50 characters long";
Astro.redirect("/register");
}
if(password !== password2) {
error = "passwords must match";
Astro.redirect("/register");
}
if(error === "") { if(error === "") {
const user = await createUser(username, email, password); const userId = new ObjectId();
const planetId = new ObjectId();
const user = await createUser(userId, username, email, password, planetId);
const sessionTime = config.SESSION_TIME_MINUTES * 60; const sessionTime = config.SESSION_TIME_MINUTES * 60;
const res = await fetch(`https://localhost:4321/api/auth/generateAccessToken`, { const galaxyIndex = Math.floor(Math.random() * 4);
const sectorIndex = Math.floor(Math.random() * 8);
const planetData: DBPlanet = {
_id: planetId,
owner: user._id,
name: `${username}'s home planet`,
fields: 100,
buildings: [],
ships: [],
resources: []
}
await createPlanet(planetData);
const systemData: DBSystem = {
_id: new ObjectId(),
name: `${username}'s home system`,
ownedBy: user._id,
structures: [],
planets: [planetData._id],
}
await createSystem(systemData);
const galaxies = await getAllGalaxies();
const sectorId = galaxies[galaxyIndex].sectors[sectorIndex];
await addSystemToSector(sectorId, systemData._id);
const cookieOptions: AstroCookieSetOptions = {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
}
Astro.cookies.set("username", username, cookieOptions);
Astro.cookies.set("userid", user._id.toString() as string, cookieOptions);
Astro.cookies.set("currentPlanet", planetData._id.toString(), cookieOptions);
Astro.cookies.set("currentSystem", systemData._id.toString(), cookieOptions);
await locationManager.init();
const res = await fetch(`${Astro.url.origin}/api/auth/generateAccessToken`, {
method: 'POST', method: 'POST',
body: JSON.stringify({ body: JSON.stringify({
username, username,
@ -72,29 +96,8 @@ if(Astro.request.method === "POST") {
'Authorization': 'Bearer ' + config.MASTER_ACCESSTOKEN 'Authorization': 'Bearer ' + config.MASTER_ACCESSTOKEN
} }
}); });
const token = (await res.json()).accessToken; const token = (await res.json()).accessToken;
Astro.cookies.set("sessionToken", token, cookieOptions);
Astro.cookies.set("sessionToken", token, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
});
Astro.cookies.set("username", username, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
});
Astro.cookies.set("userid", user._id?.toString() as string, {
path: "/",
maxAge: sessionTime,
sameSite: "lax",
secure: true
})
return Astro.redirect("/game"); return Astro.redirect("/game");
} }

View File

@ -0,0 +1,8 @@
---
layout: ../../../layouts/Wiki.astro
title: Fusion reactor
---
Fusion reactor is a type of reactor which uses nuclear fusion to generate heat, which is used for generating electricity. Their goal is to mimic the process ongoing inside the Sun, which theoretically could provide mankind with clean and sustainable energy source, without any radioactive waste.
## Real-life examples
ITER (International Thermonuclear Experimental Reactor) located in France has ongoing research.

View File

@ -0,0 +1,5 @@
---
layout: ../../../layouts/Wiki.astro
title: Buildings
---
There are many types of buildings present in-game, many of which are possible to be built in real life.

View File

@ -0,0 +1,10 @@
---
layout: ../../../layouts/Wiki.astro
title: Fleet
---
Players can send their fleet to any planet for various tasks.
## Mission types
- Attack - fight is initiated on destination. If there's at least one ship remaining after the fight, it will return to its origin
- Transport - transports resources to selected planet/system. After dropping resources the fleet returns to its origin
- Transfer - all ships will stay in destination planet and get added to this planet's available fleet

View File

@ -0,0 +1,7 @@
---
layout: ../../../layouts/Wiki.astro
title: How to play
---
Welcome to AstroColony! To start, create an account [here](/register).
You'll be assigned a single planet, from which you'll have to expand your astronomical empire.

View File

@ -0,0 +1,5 @@
---
layout: ../../../layouts/Wiki.astro
title: System management
---
System manager allows for player to quickly view summary of each planet inside. Player can also switch currently active planet (shown by its name in resource bar). It's also possible to view system-wide [megastructures](/wiki/megastructures), space stations and asteroid manager.

View File

@ -0,0 +1,18 @@
---
layout: ../../../layouts/Wiki.astro
title: Universe structure
---
In-game universe consists of 4 galaxies, each has 8 predefined sectors:
| Luminara | Thalor Prime | Zephyra | Nyxara |
| ---------------- | ------------------ | ---------------- | ----------------- |
| Voidcaller Reach | Dreamweaver Reach | Spectral Veil | Embered Fields |
| Starfire Expanse | Azure Chasm | Galecrest Sector | Obsidian Rift |
| Veil of Echoes | Ironclad Citadel | Sapphire Dunes | Lunar Shroud |
| Crystal Nebula | Frostwing Quadrant | Aurora's Embrace | Phantom Outlands |
| Whispering Abyss | Emberveil Rim | Nebulon Haven | Starlit Labyrinth |
| Radiant Outpost | Shadow Nexus | Celestia's Gate | Thorned Garden |
| Celestial Forge | Verdant Oasis | Mystic Falls | Infernal Depths |
| Twilight Bastion | Cosmic Tides | Ethereal Spire | Echoing Plains |
Each player has their own solar system inside randomly-assigned sector in one of four galaxies.

5
src/pages/wiki/index.md Normal file
View File

@ -0,0 +1,5 @@
---
layout: ../../layouts/Wiki.astro
title: Main page
---
Welcome to AstroColony's built-in wiki! Here, you'll find various tips and tutorials for playing the game, but also a bit of trivia and interesting facts about various technologies described in the game.

View File

@ -0,0 +1,5 @@
---
layout: ../../../layouts/Wiki.astro
title: Dyson sphere
---
Dyson Sphere (named after Freeman Dyson who proposed the idea in 1960) is a megastructure that harvests energy from star by having lots of solar energy-harvesting satellites orbiting it.

View File

@ -0,0 +1,8 @@
---
layout: ../../../layouts/Wiki.astro
title: Megastructures
---
Megastructure is a theoretical construct on a massive scale, designed to achieve specific functions, such as energy collection ([Dyson sphere](/wiki/megastructures/dyson-sphere)), habitat creation ([O'Neill cylinder](/wiki/megastructures/oneill-cylinder)) or planetary engineering. Construction of such megastructures requires astronomical number of resources, time, work and highly advanced technologies.
## Real-life examples
There aren't any megastructures built by mankind yet. However, there's ongoing research into various materials and technologies that could allow creation of such megastructures.

View File

@ -0,0 +1,7 @@
export default interface Page {
title: string;
url: string;
depth: number;
parent: string;
children: Page[];
}