Compare commits
17
Commits
d54a7e51ee
...
bans
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
43430cfbef | ||
|
|
32a454c034 | ||
|
|
07646e8c42 | ||
|
|
174ed834ca | ||
|
|
20dcebbab9 | ||
|
|
c4c17b3adc | ||
|
|
cf758bfe60 | ||
|
|
8c7ec0a237 | ||
|
|
80462218a7 | ||
|
|
26b5f86983 | ||
|
|
ba6cf6d938 | ||
|
|
643545a18a | ||
|
|
b922487d76 | ||
|
|
54eb1ea735 | ||
|
|
9043c774f7 | ||
|
|
43b75b8e74 | ||
|
|
07c77f5485 |
@@ -27,14 +27,15 @@ dependencies {
|
|||||||
implementation(project(":open_api"))
|
implementation(project(":open_api"))
|
||||||
implementation(project(":database"))
|
implementation(project(":database"))
|
||||||
implementation(project(":frontend"))
|
implementation(project(":frontend"))
|
||||||
implementation("org.springframework.boot:spring-boot-starter-web")
|
|
||||||
annotationProcessor("org.projectlombok:lombok")
|
annotationProcessor("org.projectlombok:lombok")
|
||||||
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
|
||||||
implementation("com.mysql:mysql-connector-j:8.0.32")
|
implementation("com.mysql:mysql-connector-j:8.0.32")
|
||||||
implementation("org.mybatis:mybatis:3.5.13")
|
implementation("org.mybatis:mybatis:3.5.13")
|
||||||
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
implementation("org.springframework.boot:spring-boot-starter-web")
|
||||||
implementation("org.springframework.boot:spring-boot-configuration-processor")
|
implementation("org.springframework.boot:spring-boot-configuration-processor")
|
||||||
implementation("org.springframework.boot:spring-boot-starter-hateoas")
|
implementation("org.springframework.boot:spring-boot-starter-hateoas")
|
||||||
|
implementation("org.springframework.boot:spring-boot-starter-security")
|
||||||
|
implementation("org.springframework.security:spring-security-oauth2-resource-server")
|
||||||
|
implementation("org.springframework.security:spring-security-oauth2-jose")
|
||||||
|
|
||||||
//AOP
|
//AOP
|
||||||
implementation("org.aspectj:aspectjrt:1.9.19")
|
implementation("org.aspectj:aspectjrt:1.9.19")
|
||||||
@@ -42,6 +43,8 @@ dependencies {
|
|||||||
implementation("org.springframework:spring-aop")
|
implementation("org.springframework:spring-aop")
|
||||||
implementation("org.springframework:spring-aspects")
|
implementation("org.springframework:spring-aspects")
|
||||||
|
|
||||||
|
testRuntimeOnly("org.junit.platform:junit-platform-launcher")
|
||||||
|
testImplementation("org.springframework.boot:spring-boot-starter-test")
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.compileJava {
|
tasks.compileJava {
|
||||||
|
|||||||
@@ -10,8 +10,8 @@ import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
|||||||
public class AltitudeWebApplication {
|
public class AltitudeWebApplication {
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
SpringApplication.run(AltitudeWebApplication.class, args);
|
|
||||||
Connection.initDatabases();
|
Connection.initDatabases();
|
||||||
|
SpringApplication.run(AltitudeWebApplication.class, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,65 @@
|
|||||||
|
package com.alttd.altitudeweb.config;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.controllers.login.KeyPairService;
|
||||||
|
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
||||||
|
import com.nimbusds.jose.jwk.JWK;
|
||||||
|
import com.nimbusds.jose.jwk.JWKSet;
|
||||||
|
import com.nimbusds.jose.jwk.RSAKey;
|
||||||
|
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
|
||||||
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
||||||
|
import com.nimbusds.jose.proc.SecurityContext;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.security.config.Customizer;
|
||||||
|
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
|
||||||
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
|
import org.springframework.security.config.http.SessionCreationPolicy;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
|
||||||
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
|
|
||||||
|
import java.security.KeyPair;
|
||||||
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
|
import java.security.interfaces.RSAPublicKey;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
@EnableWebSecurity
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SecurityConfig {
|
||||||
|
|
||||||
|
private final KeyPairService keyPairService;
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
|
||||||
|
return http
|
||||||
|
.authorizeHttpRequests(auth -> auth
|
||||||
|
.requestMatchers("/login/userLogin/**", "/login/requestNewUserLogin/**").permitAll()
|
||||||
|
.requestMatchers("/team/**", "/history/**").permitAll()
|
||||||
|
.requestMatchers("/form/**").hasAuthority(PermissionClaimDto.USER.getValue())
|
||||||
|
.requestMatchers("/head_mod/**").hasAuthority(PermissionClaimDto.HEAD_MOD.getValue())
|
||||||
|
.anyRequest().authenticated()
|
||||||
|
)
|
||||||
|
.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))
|
||||||
|
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtEncoder jwtEncoder() {
|
||||||
|
KeyPair keyPair = keyPairService.getJwtSigningKeyPair();
|
||||||
|
JWK jwk = new RSAKey.Builder((RSAPublicKey) keyPair.getPublic())
|
||||||
|
.privateKey((RSAPrivateKey) keyPair.getPrivate())
|
||||||
|
.build();
|
||||||
|
JWKSource<SecurityContext> jwkSource = new ImmutableJWKSet<>(new JWKSet(jwk));
|
||||||
|
return new NimbusJwtEncoder(jwkSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public JwtDecoder jwtDecoder() {
|
||||||
|
KeyPair keyPair = keyPairService.getJwtSigningKeyPair();
|
||||||
|
return NimbusJwtDecoder.withPublicKey((RSAPublicKey) keyPair.getPublic()).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
+36
@@ -0,0 +1,36 @@
|
|||||||
|
package com.alttd.altitudeweb.controllers.application;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.api.AppealsApi;
|
||||||
|
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||||
|
import com.alttd.altitudeweb.model.AppealResponseDto;
|
||||||
|
import com.alttd.altitudeweb.model.DiscordAppealDto;
|
||||||
|
import com.alttd.altitudeweb.model.MinecraftAppealDto;
|
||||||
|
import com.alttd.altitudeweb.model.UpdateMailDto;
|
||||||
|
import org.springframework.http.HttpStatusCode;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RateLimit(limit = 30, timeValue = 1, timeUnit = TimeUnit.HOURS)
|
||||||
|
public class AppealController implements AppealsApi {
|
||||||
|
|
||||||
|
@RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "discordAppeal")
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<MinecraftAppealDto> submitDiscordAppeal(DiscordAppealDto discordAppealDto) {
|
||||||
|
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Discord appeals are not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@RateLimit(limit = 3, timeValue = 1, timeUnit = TimeUnit.HOURS, key = "minecraftAppeal")
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<AppealResponseDto> submitMinecraftAppeal(MinecraftAppealDto minecraftAppealDto) {
|
||||||
|
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Minecraft appeals are not yet supported");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<AppealResponseDto> updateMail(UpdateMailDto updateMailDto) {
|
||||||
|
throw new ResponseStatusException(HttpStatusCode.valueOf(501), "Updating mail is not yet supported");
|
||||||
|
}
|
||||||
|
}
|
||||||
+19
-1
@@ -1,7 +1,7 @@
|
|||||||
package com.alttd.altitudeweb.controllers.history;
|
package com.alttd.altitudeweb.controllers.history;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.api.HistoryApi;
|
import com.alttd.altitudeweb.api.HistoryApi;
|
||||||
import com.alttd.altitudeweb.controllers.limits.RateLimit;
|
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||||
import com.alttd.altitudeweb.model.HistoryCountDto;
|
import com.alttd.altitudeweb.model.HistoryCountDto;
|
||||||
import com.alttd.altitudeweb.model.PunishmentHistoryListDto;
|
import com.alttd.altitudeweb.model.PunishmentHistoryListDto;
|
||||||
import com.alttd.altitudeweb.setup.Connection;
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
@@ -153,6 +153,24 @@ public class HistoryApiController implements HistoryApi {
|
|||||||
return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
|
return ResponseEntity.ok().body(searchResultCountCompletableFuture.join());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<PunishmentHistoryListDto> getAllHistoryForUUID(String uuid) {
|
||||||
|
PunishmentHistoryListDto punishmentHistoryList = new PunishmentHistoryListDto();
|
||||||
|
CompletableFuture<List<HistoryRecord>> historyRecordsCompletableFuture = new CompletableFuture<>();
|
||||||
|
Connection.getConnection(Databases.LITE_BANS).runQuery(sqlSession -> {
|
||||||
|
log.debug("Loading all history for uuid {}", uuid);
|
||||||
|
try {
|
||||||
|
List<HistoryRecord> punishments = sqlSession.getMapper(UUIDHistoryMapper.class)
|
||||||
|
.getAllHistoryForUUID(UUID.fromString(uuid));
|
||||||
|
historyRecordsCompletableFuture.complete(punishments);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load all history for uuid {}", uuid, e);
|
||||||
|
historyRecordsCompletableFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return mapPunishmentHistory(punishmentHistoryList, historyRecordsCompletableFuture);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ResponseEntity<PunishmentHistoryDto> getHistoryById(String type, Integer id) {
|
public ResponseEntity<PunishmentHistoryDto> getHistoryById(String type, Integer id) {
|
||||||
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
HistoryType historyTypeEnum = HistoryType.getHistoryType(type);
|
||||||
|
|||||||
@@ -0,0 +1,102 @@
|
|||||||
|
package com.alttd.altitudeweb.controllers.login;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.KeyPairEntity;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.KeyPairMapper;
|
||||||
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.security.*;
|
||||||
|
import java.security.spec.PKCS8EncodedKeySpec;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Base64;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Slf4j
|
||||||
|
public class KeyPairService {
|
||||||
|
|
||||||
|
private KeyPair cachedKeyPair = null;
|
||||||
|
private static final String RSA_ALGORITHM = "RSA";
|
||||||
|
private static final int RSA_KEY_SIZE = 2048;
|
||||||
|
|
||||||
|
public KeyPair getJwtSigningKeyPair() {
|
||||||
|
if (cachedKeyPair != null) {
|
||||||
|
return cachedKeyPair;
|
||||||
|
}
|
||||||
|
KeyPair keyPair = getOrCreateKeyPair();
|
||||||
|
if (keyPair != null) {
|
||||||
|
cachedKeyPair = keyPair;
|
||||||
|
return cachedKeyPair;
|
||||||
|
}
|
||||||
|
throw new IllegalStateException("Failed to generate or load key pair");
|
||||||
|
}
|
||||||
|
|
||||||
|
public KeyPair getOrCreateKeyPair() {
|
||||||
|
CompletableFuture<KeyPairEntity> keyPairFuture = new CompletableFuture<>();
|
||||||
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
log.debug("Loading key pair");
|
||||||
|
try {
|
||||||
|
KeyPairEntity entity = sqlSession.getMapper(KeyPairMapper.class).getKeyPair();
|
||||||
|
keyPairFuture.complete(entity);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to key pair", e);
|
||||||
|
keyPairFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
KeyPairEntity keyPairEntity = keyPairFuture.join();
|
||||||
|
if (keyPairEntity != null) {
|
||||||
|
try {
|
||||||
|
byte[] privateKeyBytes = Base64.getDecoder().decode(keyPairEntity.getPrivateKey());
|
||||||
|
byte[] publicKeyBytes = Base64.getDecoder().decode(keyPairEntity.getPublicKey());
|
||||||
|
|
||||||
|
KeyFactory keyFactory = KeyFactory.getInstance(RSA_ALGORITHM);
|
||||||
|
PrivateKey privateKey = keyFactory.generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes));
|
||||||
|
PublicKey publicKey = keyFactory.generatePublic(new X509EncodedKeySpec(publicKeyBytes));
|
||||||
|
|
||||||
|
return new KeyPair(publicKey, privateKey);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load key pair from database", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
KeyPair keyPair = generateKeyPair();
|
||||||
|
|
||||||
|
try {
|
||||||
|
KeyPairEntity entity = new KeyPairEntity();
|
||||||
|
entity.setPrivateKey(Base64.getEncoder().encodeToString(keyPair.getPrivate().getEncoded()));
|
||||||
|
entity.setPublicKey(Base64.getEncoder().encodeToString(keyPair.getPublic().getEncoded()));
|
||||||
|
entity.setCreatedAt(Instant.now());
|
||||||
|
|
||||||
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
log.debug("Saving key pair");
|
||||||
|
try {
|
||||||
|
sqlSession.getMapper(KeyPairMapper.class).save(entity);
|
||||||
|
log.info("Generated and saved new key pair");
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to key pair", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to save key pair to database", e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyPair;
|
||||||
|
}
|
||||||
|
|
||||||
|
private KeyPair generateKeyPair() {
|
||||||
|
try {
|
||||||
|
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(RSA_ALGORITHM);
|
||||||
|
keyPairGenerator.initialize(RSA_KEY_SIZE);
|
||||||
|
return keyPairGenerator.generateKeyPair();
|
||||||
|
} catch (NoSuchAlgorithmException e) {
|
||||||
|
throw new IllegalStateException("Error generating key pair", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,172 @@
|
|||||||
|
package com.alttd.altitudeweb.controllers.login;
|
||||||
|
|
||||||
|
import com.alttd.altitudeweb.api.LoginApi;
|
||||||
|
import com.alttd.altitudeweb.model.PermissionClaimDto;
|
||||||
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.PrivilegedUser;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.PrivilegedUserMapper;
|
||||||
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.scheduling.annotation.Scheduled;
|
||||||
|
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import org.springframework.web.server.ResponseStatusException;
|
||||||
|
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.concurrent.CompletableFuture;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.ConcurrentMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@RestController
|
||||||
|
public class LoginController implements LoginApi {
|
||||||
|
|
||||||
|
private final JwtEncoder jwtEncoder;
|
||||||
|
|
||||||
|
@Value("${login.secret:#{null}}")
|
||||||
|
private String loginSecret;
|
||||||
|
|
||||||
|
private record CacheEntry(UUID uuid, Instant expiry) {}
|
||||||
|
|
||||||
|
private static final ConcurrentMap<String, CacheEntry> cache = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
|
@Scheduled(fixedRate = 300000) // 5 minutes in milliseconds
|
||||||
|
private void clearExpiredCacheEntries() {
|
||||||
|
Instant now = Instant.now();
|
||||||
|
int initialCacheSize = cache.size();
|
||||||
|
cache.entrySet().removeIf(entry -> entry.getValue().expiry().isBefore(now));
|
||||||
|
log.info("Cleared {} expired cache entries", initialCacheSize - cache.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@RateLimit(limit = 100, timeValue = 1, timeUnit = TimeUnit.MINUTES, key = "addLogin")
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<String> requestLogin(String authorization, String uuid) {
|
||||||
|
UUID uuidFromString;
|
||||||
|
try {
|
||||||
|
uuidFromString = UUID.fromString(uuid);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (authorization == null || !authorization.startsWith("SECRET ")) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String secret = authorization.substring("SECRET ".length());
|
||||||
|
if (!isValidSecret(secret)) {
|
||||||
|
throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Invalid secret");
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<String> key = cache.entrySet().stream()
|
||||||
|
.filter(entry -> entry.getValue().uuid.equals(uuidFromString))
|
||||||
|
.map(Map.Entry::getKey)
|
||||||
|
.findFirst();
|
||||||
|
|
||||||
|
if (key.isPresent()) {
|
||||||
|
return ResponseEntity.ok(key.get());
|
||||||
|
}
|
||||||
|
|
||||||
|
String loginCode = generateLoginCode(uuidFromString);
|
||||||
|
return ResponseEntity.ok(loginCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@RateLimit(limit = 5, timeValue = 1, timeUnit = TimeUnit.MINUTES, key = "login")
|
||||||
|
@Override
|
||||||
|
public ResponseEntity<String> login(String code) {
|
||||||
|
CacheEntry cacheEntry1 = new CacheEntry(UUID.fromString("55e46bc3-2a29-4c53-850f-dbd944dc5c5f"), Instant.now().plusSeconds(TimeUnit.DAYS.toSeconds(1)));
|
||||||
|
cache.put("23232323", cacheEntry1);
|
||||||
|
if (code == null) {
|
||||||
|
return ResponseEntity.badRequest().build();
|
||||||
|
}
|
||||||
|
|
||||||
|
CacheEntry cacheEntry = cache.get(code);
|
||||||
|
if (cacheEntry == null || cacheEntry.expiry().isBefore(Instant.now())) {
|
||||||
|
return ResponseEntity.status(HttpStatus.FORBIDDEN).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
String token = generateToken(cacheEntry.uuid);
|
||||||
|
|
||||||
|
cache.remove(code);
|
||||||
|
|
||||||
|
return ResponseEntity.ok(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateLoginCode(UUID uuid) {
|
||||||
|
String characters = "ABCDEFGHJKLMNPQRSTUVWXYZ23456789";
|
||||||
|
StringBuilder loginCode = new StringBuilder();
|
||||||
|
for (int i = 0; i < 8; i++) {
|
||||||
|
int index = (int) (Math.random() * characters.length());
|
||||||
|
loginCode.append(characters.charAt(index));
|
||||||
|
}
|
||||||
|
CacheEntry cacheEntry = new CacheEntry(uuid,
|
||||||
|
Instant.now().plusSeconds(TimeUnit.MINUTES.toSeconds(15)));
|
||||||
|
cache.put(loginCode.toString(), cacheEntry);
|
||||||
|
return loginCode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isValidSecret(String secret) {
|
||||||
|
if (loginSecret == null) {
|
||||||
|
log.warn("No login secret set, skipping secret validation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (loginSecret.length() < 16) {
|
||||||
|
log.warn("Login secret is too short, skipping secret validation");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!loginSecret.equals(secret)) {
|
||||||
|
log.info("Received invalid secret attempt");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String generateToken(UUID uuid) {
|
||||||
|
Instant now = Instant.now();
|
||||||
|
//TODO make a JWT for renewing and one for storing permissions for a session (expiry 1 hour)
|
||||||
|
Instant expiryTime = now.plusSeconds(TimeUnit.DAYS.toSeconds(30));
|
||||||
|
CompletableFuture<PrivilegedUser> privilegedUserCompletableFuture = new CompletableFuture<>();
|
||||||
|
List<PermissionClaimDto> claimList = new ArrayList<>();
|
||||||
|
Connection.getConnection(Databases.DEFAULT)
|
||||||
|
.runQuery(sqlSession -> {
|
||||||
|
try {
|
||||||
|
PrivilegedUser privilegedUser = sqlSession.getMapper(PrivilegedUserMapper.class)
|
||||||
|
.getUserByUuid(uuid.toString());
|
||||||
|
|
||||||
|
privilegedUserCompletableFuture.complete(privilegedUser);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("Failed to load user by uuid", e);
|
||||||
|
privilegedUserCompletableFuture.completeExceptionally(e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
PrivilegedUser privilegedUser = privilegedUserCompletableFuture.join();
|
||||||
|
claimList.add(PermissionClaimDto.USER);
|
||||||
|
if (privilegedUser != null) {
|
||||||
|
privilegedUser.getPermissions().forEach(permission -> {
|
||||||
|
try {
|
||||||
|
claimList.add(PermissionClaimDto.valueOf(permission));
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
log.warn("Received invalid permission claim: {}", permission);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
JwtClaimsSet claims = JwtClaimsSet.builder()
|
||||||
|
.issuer("altitudeweb")
|
||||||
|
.claim("authorities", claimList.stream().map(PermissionClaimDto::getValue).toList())
|
||||||
|
.issuedAt(now)
|
||||||
|
.expiresAt(expiryTime)
|
||||||
|
.subject(uuid.toString())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return jwtEncoder.encode(JwtEncoderParameters.from(claims)).getTokenValue();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
package com.alttd.altitudeweb.controllers.team;
|
package com.alttd.altitudeweb.controllers.team;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.api.TeamApi;
|
import com.alttd.altitudeweb.api.TeamApi;
|
||||||
import com.alttd.altitudeweb.controllers.limits.RateLimit;
|
import com.alttd.altitudeweb.services.limits.RateLimit;
|
||||||
import com.alttd.altitudeweb.setup.Connection;
|
import com.alttd.altitudeweb.setup.Connection;
|
||||||
import com.alttd.altitudeweb.database.Databases;
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
import com.alttd.altitudeweb.database.luckperms.Player;
|
import com.alttd.altitudeweb.database.luckperms.Player;
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.alttd.altitudeweb.controllers.limits;
|
package com.alttd.altitudeweb.services.limits;
|
||||||
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.alttd.altitudeweb.controllers.limits;
|
package com.alttd.altitudeweb.services.limits;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
+3
-3
@@ -1,4 +1,4 @@
|
|||||||
package com.alttd.altitudeweb.controllers.limits;
|
package com.alttd.altitudeweb.services.limits;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -26,8 +26,8 @@ public class RateLimitAspect {
|
|||||||
private final InMemoryRateLimiterService rateLimiterService;
|
private final InMemoryRateLimiterService rateLimiterService;
|
||||||
|
|
||||||
@Around("""
|
@Around("""
|
||||||
@annotation(com.alttd.altitudeweb.controllers.limits.RateLimit)
|
@annotation(com.alttd.altitudeweb.services.limits.RateLimit)
|
||||||
|| @within(com.alttd.altitudeweb.controllers.limits.RateLimit)""")
|
|| @within(com.alttd.altitudeweb.services.limits.RateLimit)""")
|
||||||
public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
|
public Object rateLimit(ProceedingJoinPoint joinPoint) throws Throwable {
|
||||||
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
|
||||||
if (requestAttributes == null) {
|
if (requestAttributes == null) {
|
||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
package com.alttd.altitudeweb.controllers.limits;
|
package com.alttd.altitudeweb.services.limits;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.alttd.altitudeweb.services.user;
|
||||||
|
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.security.core.userdetails.User;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
|
import org.springframework.security.core.userdetails.UserDetailsService;
|
||||||
|
import org.springframework.security.core.userdetails.UsernameNotFoundException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class UserDetailsServiceImpl implements UserDetailsService {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public UserDetails loadUserByUsername(String uuid) throws UsernameNotFoundException {
|
||||||
|
try {
|
||||||
|
//Validate uuid
|
||||||
|
UUID.fromString(uuid);
|
||||||
|
return new User(uuid, "", Collections.emptyList());
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
throw new UsernameNotFoundException("Invalid UUID format: " + uuid);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,4 +5,5 @@ database.host=${DB_HOST:localhost}
|
|||||||
database.user=${DB_USER:root}
|
database.user=${DB_USER:root}
|
||||||
database.password=${DB_PASSWORD:root}
|
database.password=${DB_PASSWORD:root}
|
||||||
cors.allowed-origins=${CORS:https://alttd.com}
|
cors.allowed-origins=${CORS:https://alttd.com}
|
||||||
|
login.secret=${LOGIN_SECRET:SET_TOKEN}
|
||||||
logging.level.com.alttd.altitudeweb=INFO
|
logging.level.com.alttd.altitudeweb=INFO
|
||||||
|
|||||||
@@ -80,6 +80,10 @@ public interface UUIDHistoryMapper {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
default List<HistoryRecord> getAllHistoryForUUID(@NotNull UUID uuid) {
|
||||||
|
return getRecentAllHistory(uuid.toString(), "uuid", 100, 0);
|
||||||
|
}
|
||||||
|
|
||||||
private List<HistoryRecord> getRecent(@NotNull String tableName, @NotNull UserType userType,
|
private List<HistoryRecord> getRecent(@NotNull String tableName, @NotNull UserType userType,
|
||||||
@NotNull UUID uuid, int page) {
|
@NotNull UUID uuid, int page) {
|
||||||
int offset = page * PAGE_SIZE;
|
int offset = page * PAGE_SIZE;
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ public interface TeamMemberMapper {
|
|||||||
FROM luckperms_user_permissions AS permissions
|
FROM luckperms_user_permissions AS permissions
|
||||||
INNER JOIN luckperms_players AS players ON players.uuid = permissions.uuid
|
INNER JOIN luckperms_players AS players ON players.uuid = permissions.uuid
|
||||||
WHERE permission = #{groupPermission}
|
WHERE permission = #{groupPermission}
|
||||||
|
AND world = 'global'
|
||||||
""")
|
""")
|
||||||
List<Player> getTeamMembers(@Param("groupPermission") String groupPermission);
|
List<Player> getTeamMembers(@Param("groupPermission") String groupPermission);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.alttd.altitudeweb.database.web_db;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import java.time.Instant;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class KeyPairEntity {
|
||||||
|
private int id;
|
||||||
|
private String privateKey;
|
||||||
|
private String publicKey;
|
||||||
|
private Instant createdAt;
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package com.alttd.altitudeweb.database.web_db;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
public interface KeyPairMapper {
|
||||||
|
|
||||||
|
@Select("SELECT * FROM key_pair ORDER BY id DESC LIMIT 1")
|
||||||
|
KeyPairEntity getKeyPair();
|
||||||
|
|
||||||
|
@Insert("""
|
||||||
|
INSERT INTO key_pair (id, private_key, public_key, created_at)
|
||||||
|
VALUES (#{id}, #{privateKey}, #{publicKey}, #{createdAt})
|
||||||
|
""")
|
||||||
|
void save(KeyPairEntity keyPair);
|
||||||
|
}
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
package com.alttd.altitudeweb.database.web_db;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@NoArgsConstructor
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class PrivilegedUser {
|
||||||
|
private int id;
|
||||||
|
private String uuid;
|
||||||
|
private List<String> permissions;
|
||||||
|
}
|
||||||
+102
@@ -0,0 +1,102 @@
|
|||||||
|
package com.alttd.altitudeweb.database.web_db;
|
||||||
|
|
||||||
|
import org.apache.ibatis.annotations.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface PrivilegedUserMapper {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves a user by their UUID along with their permissions
|
||||||
|
* @param uuid The UUID of the user to retrieve
|
||||||
|
* @return The PrivilegedUser with their permissions, or null if not found
|
||||||
|
*/
|
||||||
|
@Select("""
|
||||||
|
SELECT privileged_users.id, privileged_users.uuid, privileges.privileges as permission
|
||||||
|
FROM privileged_users
|
||||||
|
LEFT JOIN privileges ON privileged_users.id = privileges.user_id
|
||||||
|
WHERE privileged_users.uuid = #{uuid}
|
||||||
|
""")
|
||||||
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
|
@Result(property = "uuid", column = "uuid"),
|
||||||
|
@Result(property = "permissions", column = "id", javaType = List.class,
|
||||||
|
many = @Many(select = "getPermissionsForUser"))
|
||||||
|
})
|
||||||
|
PrivilegedUser getUserByUuid(@Param("uuid") String uuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieves all privileged users with their permissions
|
||||||
|
* @return List of all privileged users with their permissions
|
||||||
|
*/
|
||||||
|
@Select("""
|
||||||
|
SELECT id, uuid
|
||||||
|
FROM privileged_users
|
||||||
|
""")
|
||||||
|
@Results({
|
||||||
|
@Result(property = "id", column = "id"),
|
||||||
|
@Result(property = "uuid", column = "uuid"),
|
||||||
|
@Result(property = "permissions", column = "id", javaType = List.class,
|
||||||
|
many = @Many(select = "getPermissionsForUser"))
|
||||||
|
})
|
||||||
|
List<PrivilegedUser> getAllUsers();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all permissions for a specific user
|
||||||
|
* @param userId The ID of the user
|
||||||
|
* @return List of permission strings
|
||||||
|
*/
|
||||||
|
@Select("""
|
||||||
|
SELECT privileges
|
||||||
|
FROM privileges
|
||||||
|
WHERE user_id = #{userId}
|
||||||
|
""")
|
||||||
|
List<String> getPermissionsForUser(@Param("userId") int userId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new privileged user
|
||||||
|
* @param user The PrivilegedUser object to add
|
||||||
|
* @return The number of rows affected
|
||||||
|
*/
|
||||||
|
@Insert("""
|
||||||
|
INSERT INTO privileged_users (uuid)
|
||||||
|
VALUES (#{user.uuid})
|
||||||
|
""")
|
||||||
|
@Options(useGeneratedKeys = true, keyProperty = "user.id", keyColumn = "id")
|
||||||
|
int addUser(@Param("user") PrivilegedUser user);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a privileged user by their UUID
|
||||||
|
* @param uuid The UUID of the user to delete
|
||||||
|
* @return The number of rows affected
|
||||||
|
*/
|
||||||
|
@Delete("""
|
||||||
|
DELETE FROM privileged_users
|
||||||
|
WHERE uuid = #{uuid}
|
||||||
|
""")
|
||||||
|
int deleteUserByUuid(@Param("uuid") String uuid);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a permission to a user
|
||||||
|
* @param userId The ID of the user
|
||||||
|
* @param permission The permission to add
|
||||||
|
* @return The number of rows affected
|
||||||
|
*/
|
||||||
|
@Insert("""
|
||||||
|
INSERT INTO privileges (user_id, privileges)
|
||||||
|
VALUES (#{userId}, #{permission})
|
||||||
|
""")
|
||||||
|
int addPermissionToUser(@Param("userId") int userId, @Param("permission") String permission);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Removes a permission from a user
|
||||||
|
* @param userId The ID of the user
|
||||||
|
* @param permission The permission to remove
|
||||||
|
* @return The number of rows affected
|
||||||
|
*/
|
||||||
|
@Delete("""
|
||||||
|
DELETE FROM privileges
|
||||||
|
WHERE user_id = #{userId} AND privileges = #{permission}
|
||||||
|
""")
|
||||||
|
int removePermissionFromUser(@Param("userId") int userId, @Param("permission") String permission);
|
||||||
|
}
|
||||||
@@ -87,6 +87,7 @@ public class Connection {
|
|||||||
log.debug("Loaded default database settings {}", databaseSettings);
|
log.debug("Loaded default database settings {}", databaseSettings);
|
||||||
Connection connection = new Connection(databaseSettings, addMappers);
|
Connection connection = new Connection(databaseSettings, addMappers);
|
||||||
log.debug("Created default database connection {}", connection);
|
log.debug("Created default database connection {}", connection);
|
||||||
|
connections.put(Databases.DEFAULT, connection);
|
||||||
return CompletableFuture.completedFuture(connection);
|
return CompletableFuture.completedFuture(connection);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
package com.alttd.altitudeweb.setup;
|
package com.alttd.altitudeweb.setup;
|
||||||
|
|
||||||
import com.alttd.altitudeweb.database.Databases;
|
import com.alttd.altitudeweb.database.Databases;
|
||||||
|
import com.alttd.altitudeweb.database.web_db.KeyPairMapper;
|
||||||
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
|
import com.alttd.altitudeweb.database.web_db.SettingsMapper;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.ibatis.session.SqlSession;
|
import org.apache.ibatis.session.SqlSession;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.sql.SQLException;
|
import java.sql.SQLException;
|
||||||
import java.sql.Statement;
|
import java.sql.Statement;
|
||||||
@@ -12,15 +14,21 @@ import java.sql.Statement;
|
|||||||
public class InitializeWebDb {
|
public class InitializeWebDb {
|
||||||
|
|
||||||
protected static void init() {
|
protected static void init() {
|
||||||
log.info("Initializing LiteBans");
|
log.info("Initializing WebDb");
|
||||||
Connection.getConnection(Databases.DEFAULT, (configuration) -> {
|
Connection.getConnection(Databases.DEFAULT, (configuration) -> {
|
||||||
configuration.addMapper(SettingsMapper.class);
|
configuration.addMapper(SettingsMapper.class);
|
||||||
|
configuration.addMapper(KeyPairMapper.class);
|
||||||
}).join()
|
}).join()
|
||||||
.runQuery(InitializeWebDb::createSettingsTable);
|
.runQuery(SqlSession -> {
|
||||||
log.debug("Initialized LuckPerms");
|
createSettingsTable(SqlSession);
|
||||||
|
createKeyTable(SqlSession);
|
||||||
|
createPrivilegedUsersTable(SqlSession);
|
||||||
|
createPrivilegesTable(SqlSession);
|
||||||
|
});
|
||||||
|
log.debug("Initialized WebDb");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void createSettingsTable(SqlSession sqlSession) {
|
private static void createSettingsTable(@NotNull SqlSession sqlSession) {
|
||||||
String query = """
|
String query = """
|
||||||
CREATE TABLE IF NOT EXISTS db_connection_settings
|
CREATE TABLE IF NOT EXISTS db_connection_settings
|
||||||
(
|
(
|
||||||
@@ -40,4 +48,53 @@ public class InitializeWebDb {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void createKeyTable(@NotNull SqlSession sqlSession) {
|
||||||
|
String query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS key_pair (
|
||||||
|
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
private_key TEXT NOT NULL,
|
||||||
|
public_key TEXT NOT NULL,
|
||||||
|
created_at TIMESTAMP NOT NULL
|
||||||
|
);
|
||||||
|
""";
|
||||||
|
try (Statement statement = sqlSession.getConnection().createStatement()) {
|
||||||
|
statement.execute(query);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createPrivilegedUsersTable(@NotNull SqlSession sqlSession) {
|
||||||
|
String query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS privileged_users (
|
||||||
|
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
uuid VARCHAR(36) NOT NULL
|
||||||
|
);
|
||||||
|
""";
|
||||||
|
try (Statement statement = sqlSession.getConnection().createStatement()) {
|
||||||
|
statement.execute(query);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createPrivilegesTable(@NotNull SqlSession sqlSession) {
|
||||||
|
String query = """
|
||||||
|
CREATE TABLE IF NOT EXISTS privileges (
|
||||||
|
id int NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||||
|
user_id int NOT NULL,
|
||||||
|
privileges VARCHAR(36) NOT NULL,
|
||||||
|
CONSTRAINT fk_privileges_user FOREIGN KEY (user_id)
|
||||||
|
REFERENCES privileged_users(id)
|
||||||
|
ON DELETE CASCADE
|
||||||
|
ON UPDATE CASCADE
|
||||||
|
);
|
||||||
|
""";
|
||||||
|
try (Statement statement = sqlSession.getConnection().createStatement()) {
|
||||||
|
statement.execute(query);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,10 +13,12 @@
|
|||||||
},
|
},
|
||||||
"private": true,
|
"private": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@angular/cdk": "^19.2.18",
|
||||||
"@angular/common": "^19.2.0",
|
"@angular/common": "^19.2.0",
|
||||||
"@angular/compiler": "^19.2.0",
|
"@angular/compiler": "^19.2.0",
|
||||||
"@angular/core": "^19.2.0",
|
"@angular/core": "^19.2.0",
|
||||||
"@angular/forms": "^19.2.0",
|
"@angular/forms": "^19.2.0",
|
||||||
|
"@angular/material": "^19.2.18",
|
||||||
"@angular/platform-browser": "^19.2.0",
|
"@angular/platform-browser": "^19.2.0",
|
||||||
"@angular/platform-browser-dynamic": "^19.2.0",
|
"@angular/platform-browser-dynamic": "^19.2.0",
|
||||||
"@angular/router": "^19.2.0",
|
"@angular/router": "^19.2.0",
|
||||||
|
|||||||
@@ -2,13 +2,15 @@ import {Component} from '@angular/core';
|
|||||||
import {ScrollService} from '../scroll/scroll.service';
|
import {ScrollService} from '../scroll/scroll.service';
|
||||||
import {CommonModule} from '@angular/common';
|
import {CommonModule} from '@angular/common';
|
||||||
import {HeaderComponent} from '../header/header.component';
|
import {HeaderComponent} from '../header/header.component';
|
||||||
|
import {RemoveTrailingPeriodPipe} from "../util/RemoveTrailingPeriodPipe";
|
||||||
|
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'app-about',
|
selector: 'app-about',
|
||||||
standalone: true,
|
standalone: true,
|
||||||
imports: [
|
imports: [
|
||||||
CommonModule,
|
CommonModule,
|
||||||
HeaderComponent
|
HeaderComponent,
|
||||||
|
RemoveTrailingPeriodPipe
|
||||||
],
|
],
|
||||||
templateUrl: './about.component.html',
|
templateUrl: './about.component.html',
|
||||||
styleUrl: './about.component.scss'
|
styleUrl: './about.component.scss'
|
||||||
|
|||||||
@@ -64,7 +64,47 @@ export const routes: Routes = [
|
|||||||
{
|
{
|
||||||
path: 'warps',
|
path: 'warps',
|
||||||
loadComponent: () => import('./warps/warps.component').then(m => m.WarpsComponent)
|
loadComponent: () => import('./warps/warps.component').then(m => m.WarpsComponent)
|
||||||
}
|
},
|
||||||
|
{
|
||||||
|
path: 'skyblock',
|
||||||
|
loadComponent: () => import('./skyblock/skyblock.component').then(m => m.SkyblockComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'customfeatures',
|
||||||
|
loadComponent: () => import('./customfeatures/customfeatures.component').then(m => m.CustomfeaturesComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'guide',
|
||||||
|
loadComponent: () => import('./guide/guide.component').then(m => m.GuideComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'ranks',
|
||||||
|
loadComponent: () => import('./ranks/ranks.component').then(m => m.RanksComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'commandlist',
|
||||||
|
loadComponent: () => import('./commandlist/commandlist.component').then(m => m.CommandlistComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'mapart',
|
||||||
|
loadComponent: () => import('./mapart/mapart.component').then(m => m.MapartComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'lag',
|
||||||
|
loadComponent: () => import('./lag/lag.component').then(m => m.LagComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'staffpowers',
|
||||||
|
loadComponent: () => import('./staffpowers/staffpowers.component').then(m => m.StaffpowersComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'forms/:form',
|
||||||
|
loadComponent: () => import('./forms/forms.component').then(m => m.FormsComponent)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'forms',
|
||||||
|
loadComponent: () => import('./forms/forms.component').then(m => m.FormsComponent)
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -6,57 +6,104 @@
|
|||||||
</div>
|
</div>
|
||||||
</app-header>
|
</app-header>
|
||||||
|
|
||||||
<ng-container *ngIf="punishment === undefined">
|
<main>
|
||||||
<p>Loading...</p>
|
<section class="darkmodeSection">
|
||||||
</ng-container>
|
<section class="columnSection">
|
||||||
|
<div class="detailsBackButton">
|
||||||
|
<ng-container *ngIf="punishment === undefined">
|
||||||
|
<p>Loading...</p>
|
||||||
|
</ng-container>
|
||||||
|
|
||||||
<ng-container *ngIf="punishment">
|
<a [routerLink]="['/bans']">< Back</a>
|
||||||
<table [cellSpacing]="0">
|
</div>
|
||||||
<div>
|
</section>
|
||||||
<p>type: {{ this.historyFormat.getType(punishment) }}</p>
|
<section class="columnSection center">
|
||||||
<p>is active: {{ this.historyFormat.isActive(punishment) }}</p>
|
<ng-container *ngIf="punishment">
|
||||||
<tbody>
|
<div>
|
||||||
<tr>
|
<span class="tag tagInfo"
|
||||||
<td>Player</td>
|
[ngClass]="{
|
||||||
<td>
|
'tagPermanent': this.historyFormat.isPermanent(punishment),
|
||||||
<div class="playerContainer">
|
'tagExpired': !this.historyFormat.isPermanent(punishment)
|
||||||
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(punishment.uuid)" width="25" height="25"
|
}">
|
||||||
alt="{{punishment.username}}'s Minecraft skin">
|
{{ this.historyFormat.getType(punishment) }}
|
||||||
<span class="username">{{ punishment.username }}</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
<div>
|
||||||
</tr>
|
<span
|
||||||
<tr>
|
class="tag tagInfo"
|
||||||
<td>Moderator</td>
|
[ngClass]="{
|
||||||
<td>
|
'tagActive': this.historyFormat.isActive(punishment),
|
||||||
<div class="playerContainer">
|
'tagInactive': !this.historyFormat.isActive(punishment)
|
||||||
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(punishment.punishedByUuid)" width="25"
|
}">
|
||||||
height="25"
|
{{ this.historyFormat.isActive(punishment) ? 'Active' : 'Inactive' }}
|
||||||
alt="{{punishment.punishedBy}}'s Minecraft skin">
|
</span>
|
||||||
<span class="username">{{ punishment.punishedBy }}</span>
|
</div>
|
||||||
</div>
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Reason</td>
|
|
||||||
<td>{{ punishment.reason | removeTrailingPeriod }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Date</td>
|
|
||||||
<td>{{ this.historyFormat.getPunishmentTime(punishment) }}</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
|
||||||
<td>Expires</td>
|
|
||||||
<td>{{ this.historyFormat.getExpiredTime(punishment) }}</td>
|
|
||||||
</tr>
|
|
||||||
<ng-container *ngIf="punishment.removedBy !== undefined && punishment.removedBy.length > 0">
|
|
||||||
<tr>
|
|
||||||
<td>Un{{ this.historyFormat.getType(punishment).toLocaleLowerCase() }} reason</td>
|
|
||||||
<td>{{ punishment.removedReason == null ? 'No reason specified' : punishment.removedReason }}</td>
|
|
||||||
</tr>
|
|
||||||
</ng-container>
|
</ng-container>
|
||||||
</tbody>
|
</section>
|
||||||
</div>
|
<section class="columnSection">
|
||||||
</table>
|
<div class="columnContainer">
|
||||||
</ng-container>
|
<div class="columnParagraph">
|
||||||
|
<ng-container *ngIf="punishment">
|
||||||
|
<div class="playerContainer">
|
||||||
|
<h2>Player</h2>
|
||||||
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(punishment.uuid, '150')"
|
||||||
|
width="150"
|
||||||
|
height="150"
|
||||||
|
alt="{{punishment.username}}'s Minecraft skin"
|
||||||
|
>
|
||||||
|
<h3 class="detailsUsername">{{ punishment.username }}</h3>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<ng-container *ngIf="punishment">
|
||||||
|
<div class="playerContainer">
|
||||||
|
<h2>Moderator</h2>
|
||||||
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(punishment.punishedByUuid, '150')"
|
||||||
|
width="150"
|
||||||
|
height="150"
|
||||||
|
alt="{{punishment.punishedBy}}'s Minecraft skin"
|
||||||
|
>
|
||||||
|
<h3 class="detailsUsername">{{ punishment.punishedBy }}</h3>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<ng-container *ngIf="punishment">
|
||||||
|
<div class="detailsInfo">
|
||||||
|
<h2>Reason</h2>
|
||||||
|
<p>{{ punishment.reason | removeTrailingPeriod }}</p>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<ng-container *ngIf="punishment">
|
||||||
|
<div class="detailsInfo">
|
||||||
|
<h2>Date</h2>
|
||||||
|
<p>{{ this.historyFormat.getPunishmentTime(punishment) }}</p>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
</ng-container>
|
</ng-container>
|
||||||
|
|
||||||
|
|
||||||
|
<section class="columnSection">
|
||||||
|
<ng-container *ngIf="punishment">
|
||||||
|
<span>Expires</span>
|
||||||
|
<span>{{ this.historyFormat.getExpiredTime(punishment) }}</span>
|
||||||
|
<ng-container *ngIf="punishment.removedBy !== undefined && punishment.removedBy.length > 0">
|
||||||
|
<span>Un{{ this.historyFormat.getType(punishment).toLocaleLowerCase() }} reason</span>
|
||||||
|
<span>{{ punishment.removedReason == null ? 'No reason specified' : punishment.removedReason }}</span>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
</section>
|
||||||
|
|||||||
@@ -0,0 +1,68 @@
|
|||||||
|
.detailsBackButton {
|
||||||
|
font-family: open-sans, sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.columnSection {
|
||||||
|
padding-top: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsUsername {
|
||||||
|
font-size: 1.2em;
|
||||||
|
font-family: 'opensans-bold', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsInfo {
|
||||||
|
padding-top: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailsInfo p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tag {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 5px 10px;
|
||||||
|
line-height: 1;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: baseline;
|
||||||
|
border-radius: 0.25rem;
|
||||||
|
font-family: 'opensans-bold', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagInfo {
|
||||||
|
margin: 0 20px;
|
||||||
|
font-size: 1.1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagActive {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #EE5555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagInactive {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #F79720;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagPermanent {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #EE5555;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tagExpired {
|
||||||
|
color: #FFFFFF;
|
||||||
|
background-color: #777777
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import {Component, OnInit} from '@angular/core';
|
import {Component, OnInit} from '@angular/core';
|
||||||
import {HistoryService, PunishmentHistory} from '../../../api';
|
import {HistoryService, PunishmentHistory} from '../../../api';
|
||||||
import {NgIf, NgOptimizedImage} from '@angular/common';
|
import {NgClass, NgIf, NgOptimizedImage} from '@angular/common';
|
||||||
import {RemoveTrailingPeriodPipe} from '../../util/RemoveTrailingPeriodPipe';
|
import {RemoveTrailingPeriodPipe} from '../../util/RemoveTrailingPeriodPipe';
|
||||||
import {HistoryFormatService} from '../history-format.service';
|
import {HistoryFormatService} from '../history-format.service';
|
||||||
import {ActivatedRoute} from '@angular/router';
|
import {ActivatedRoute, RouterLink} from '@angular/router';
|
||||||
import {catchError, map} from 'rxjs';
|
import {catchError, map} from 'rxjs';
|
||||||
import {HeaderComponent} from '../../header/header.component';
|
import {HeaderComponent} from '../../header/header.component';
|
||||||
|
|
||||||
@@ -13,7 +13,9 @@ import {HeaderComponent} from '../../header/header.component';
|
|||||||
NgIf,
|
NgIf,
|
||||||
NgOptimizedImage,
|
NgOptimizedImage,
|
||||||
RemoveTrailingPeriodPipe,
|
RemoveTrailingPeriodPipe,
|
||||||
HeaderComponent
|
HeaderComponent,
|
||||||
|
RouterLink,
|
||||||
|
NgClass
|
||||||
],
|
],
|
||||||
templateUrl: './details.component.html',
|
templateUrl: './details.component.html',
|
||||||
styleUrl: './details.component.scss'
|
styleUrl: './details.component.scss'
|
||||||
|
|||||||
@@ -22,9 +22,19 @@ export class HistoryFormatService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public isActive(entry: PunishmentHistory): boolean {
|
public isActive(entry: PunishmentHistory): boolean {
|
||||||
|
if (entry.removedBy !== null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (entry.expiryTime <= 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
return entry.expiryTime > Date.now();
|
return entry.expiryTime > Date.now();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public isPermanent(entry: PunishmentHistory): boolean {
|
||||||
|
return entry.expiryTime <= 0;
|
||||||
|
}
|
||||||
|
|
||||||
public getType(entry: PunishmentHistory): string {
|
public getType(entry: PunishmentHistory): string {
|
||||||
return entry.type.charAt(0).toUpperCase() + entry.type.slice(1);
|
return entry.type.charAt(0).toUpperCase() + entry.type.slice(1);
|
||||||
}
|
}
|
||||||
@@ -32,7 +42,7 @@ export class HistoryFormatService {
|
|||||||
public getExpiredTime(entry: PunishmentHistory): string {
|
public getExpiredTime(entry: PunishmentHistory): string {
|
||||||
let suffix: string = '';
|
let suffix: string = '';
|
||||||
if (entry.removedBy !== null && entry.removedBy !== undefined) {
|
if (entry.removedBy !== null && entry.removedBy !== undefined) {
|
||||||
suffix = '(' + entry.removedBy + ')';
|
suffix = '(Unbanned by ' + entry.removedBy + ')';
|
||||||
}
|
}
|
||||||
if (entry.expiryTime <= 0) {
|
if (entry.expiryTime <= 0) {
|
||||||
return "Permanent " + this.getType(entry) + " " + suffix;
|
return "Permanent " + this.getType(entry) + " " + suffix;
|
||||||
@@ -48,11 +58,11 @@ export class HistoryFormatService {
|
|||||||
}) + " " + suffix;
|
}) + " " + suffix;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getAvatarUrl(entry: string): string {
|
public getAvatarUrl(entry: string, size: string = '25'): string {
|
||||||
let uuid = entry.replace('-', '');
|
let uuid = entry.replace('-', '');
|
||||||
if (uuid === 'C') {
|
if (uuid === 'C') {
|
||||||
uuid = "f78a4d8dd51b4b3998a3230f2de0c670"
|
uuid = "f78a4d8dd51b4b3998a3230f2de0c670"
|
||||||
}
|
}
|
||||||
return `https://crafatar.com/avatars/${uuid}?size=25&overlay`;
|
return `https://crafatar.com/avatars/${uuid}?size=${size}&overlay`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,14 +22,14 @@
|
|||||||
<td class="historyType" (click)="showDetailedPunishment(entry)">
|
<td class="historyType" (click)="showDetailedPunishment(entry)">
|
||||||
{{ this.historyFormat.getType(entry) }}
|
{{ this.historyFormat.getType(entry) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="historyPlayer" (click)="setSearch(entry.type, entry.username, 'player')">
|
<td class="historyPlayer" (click)="setSearch(entry.username, 'player')">
|
||||||
<div class="playerContainer">
|
<div class="playerContainer">
|
||||||
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(entry.uuid)" width="25" height="25"
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(entry.uuid)" width="25" height="25"
|
||||||
alt="{{entry.username}}'s Minecraft skin">
|
alt="{{entry.username}}'s Minecraft skin">
|
||||||
<span class="username">{{ entry.username }}</span>
|
<span class="username">{{ entry.username }}</span>
|
||||||
</div>
|
</div>
|
||||||
</td>
|
</td>
|
||||||
<td class="historyPlayer" (click)="setSearch(entry.type, entry.punishedBy, 'staff')">
|
<td class="historyPlayer" (click)="setSearch(entry.punishedBy, 'staff')">
|
||||||
<div class="playerContainer">
|
<div class="playerContainer">
|
||||||
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(entry.punishedByUuid)" width="25" height="25"
|
<img class="avatar" [ngSrc]="this.historyFormat.getAvatarUrl(entry.punishedByUuid)" width="25" height="25"
|
||||||
alt="{{entry.punishedBy}}'s Minecraft skin">
|
alt="{{entry.punishedBy}}'s Minecraft skin">
|
||||||
|
|||||||
@@ -93,25 +93,10 @@ export class HistoryComponent implements OnInit, OnChanges {
|
|||||||
).subscribe();
|
).subscribe();
|
||||||
}
|
}
|
||||||
|
|
||||||
setSearch(type: PunishmentHistory.TypeEnum, name: string, userType: 'player' | 'staff') {
|
setSearch(name: string, userType: 'player' | 'staff') {
|
||||||
let punishmentType: 'all' | 'ban' | 'mute' | 'kick' | 'warn' = 'all';
|
|
||||||
switch (type) {
|
|
||||||
case 'ban':
|
|
||||||
punishmentType = 'ban';
|
|
||||||
break;
|
|
||||||
case 'mute':
|
|
||||||
punishmentType = 'mute';
|
|
||||||
break;
|
|
||||||
case 'kick':
|
|
||||||
punishmentType = 'kick';
|
|
||||||
break;
|
|
||||||
case 'warn':
|
|
||||||
punishmentType = 'warn';
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let searchParams: SearchParams = {
|
let searchParams: SearchParams = {
|
||||||
userType: userType,
|
userType: userType,
|
||||||
punishmentType: punishmentType,
|
punishmentType: 'all',
|
||||||
searchTerm: name
|
searchTerm: name
|
||||||
}
|
}
|
||||||
this.selectItem.emit(searchParams);
|
this.selectItem.emit(searchParams);
|
||||||
|
|||||||
@@ -0,0 +1,496 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'commandlist'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Command list</h1>
|
||||||
|
<h2>Looking for a specific command, or the description of one? This is where you find it.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding-bottom: 5px !important;">Claiming</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/claim -</span> Gives you the claim kit</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/buyclaimblocks <amount> -</span> Allows
|
||||||
|
you to buy claimblocks (0.5 in game per block)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/sellclaimblocks <amount> -</span>
|
||||||
|
Allows you to sell claimblocks (0.5 in game per block)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/abandonallclaims -</span> Deletes all your
|
||||||
|
claims
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/abandonclaim -</span> Deletes the
|
||||||
|
claim/subdivision you're currently in
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/delclaim -</span> Deletes the
|
||||||
|
claim/subdivision you're currently in
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/unclaim -</span> Deletes the
|
||||||
|
claim/subdivision you're currently in
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/claimlist -</span> Shows you all your claims
|
||||||
|
and claimblocks
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/accesstrust <username/public> -</span>
|
||||||
|
Gives accesstrust to a user/everyone
|
||||||
|
</li>
|
||||||
|
<li><span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/containertrust <username/public> -</span> Gives
|
||||||
|
containertrust to a user/everyone
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/expandclaim <amount> -</span> Expands
|
||||||
|
claim by the specified amount in the direction you're facing
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/restrictsubclaim -</span> Seperates
|
||||||
|
permissions for the subclaim you are standing in from the main claim
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/subdivide -</span> Turns on subdivide mode
|
||||||
|
(hold golden shovel) to allow you to create subdivisions
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/trustlist -</span> Shows users trusted in the
|
||||||
|
claim/subclaim you're standing it
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/claimnearbytrust -</span> Allows other
|
||||||
|
players to claim within 100 blocks to your claim
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Creative</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot help -</span> Display all creative
|
||||||
|
commands
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot info -</span> Display plot info</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot confirm -</span> Confirm an action</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot claim -</span> Claim the current plot
|
||||||
|
you're standing on
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot auto -</span> Claim a random available
|
||||||
|
plot nearby
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot delete -</span> Delete your plot</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot trust -</span> Allow a player to build
|
||||||
|
in your plot
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot add -</span> Allow a user to build while
|
||||||
|
you are online
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot setowner -</span> Set the plot owner
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot setbiome -</span> Set the plot biome
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot visit -</span> Visit someone (or your
|
||||||
|
own) plot
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot kick -</span> Kick a player from your
|
||||||
|
plot
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot middle -</span> Teleports you to the
|
||||||
|
center of the current plot
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot untrust -</span> Remove a player from
|
||||||
|
your plot
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot merge [auto] -</span> Merge a plot (or
|
||||||
|
multiple)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot unmerge -</span> Unmerge plots</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot sethome -</span> Set the plot home</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/plot clear -</span> Clear a plot</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nightvision -</span> Enables nightvision</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Dynamic Map</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/dynmap -</span> Sends you a link to the
|
||||||
|
Altitude map page
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/dynmap hide -</span> Hides you on the map
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/dynmap show -</span> Shows you on the map
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">McMMO</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mctop <ability> -</span> Shows the top
|
||||||
|
10 players for Power Level or the specified ability
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mcrank -</span> Shows where you are on the
|
||||||
|
mctop per skill
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mcstats -</span> Shows your mcmmo levels in a
|
||||||
|
scoreboard
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mcability -</span> Toggles ability use on or
|
||||||
|
off
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/acrobatics [?] [page] -</span> Shows your
|
||||||
|
stats for Acrobatics. If you add ? it will show the guide for Acrobatics
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/archery [?] [page] -</span> Shows your stats
|
||||||
|
for Archery. If you add ? it will show the guide for Archery
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/axes [?] [page] -</span> Shows your stats for
|
||||||
|
Axes. If you add ? it will show the guide for Axes
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/excavation [?] [page] -</span> Shows your
|
||||||
|
stats for Excavation. If you add ? it will show the guide for Excavation
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/fishing [?] [page] -</span> Shows your stats
|
||||||
|
for Fishing. If you add ? it will show the guide for Fishing
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/herbalism [?] [page] -</span> Shows your
|
||||||
|
stats for Herbalism. If you add ? it will show the guide for Herbalism
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mining [?] [page] -</span> Shows your stats
|
||||||
|
for Mining. If you add ? it will show the guide for Mining
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/repair [?] [page] -</span> Shows your stats
|
||||||
|
for Repair. If you add ? it will show the guide for Repair
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/swords [?] [page] -</span> Shows your stats
|
||||||
|
for Swords. If you add ? it will show the guide for Swords
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/taming [?] [page] -</span> Shows your stats
|
||||||
|
for Taming. If you add ? it will show the guide for Taming
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/unarmed [?] [page] -</span> Shows your stats
|
||||||
|
for Unarmed. If you add ? it will show the guide for Unarmed
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/woodcutting [?] [page] -</span> Shows your
|
||||||
|
stats for Woodcutting. If you add ? it will show the guide for Woodcutting
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/inspect <username> -</span> Shows mcmmo
|
||||||
|
levels for specified user in a scoreboard if they are near you
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">MyPet</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petinfo -</span> Shows information about your
|
||||||
|
MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petname <name> -</span> Gives your
|
||||||
|
current MyPet the specified name
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petrelease <name> -</span> Release the
|
||||||
|
specified MyPet (this will despawn it)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petcall (/petc) -</span> Teleports your
|
||||||
|
current MyPet to you
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/psa -</span> Sends your MyPet away, it can
|
||||||
|
still be called using /petc (does not store your pet)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petswitch (/psw) -</span> Switches between
|
||||||
|
current and stored MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petstore (/pst) -</span> Stores your MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pettrade <username> [price] -</span>
|
||||||
|
Trades current MyPet with a player
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petshop -</span> Opens the MyPet shop</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petskill -</span> Shows information about the
|
||||||
|
skills of your MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petstop -</span> Stops your MyPet from
|
||||||
|
attacking a target if it's not in farm or aggressive behavior modes
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petchooseskilltree (/pcst) -</span> Allows
|
||||||
|
you to select a skilltree for your MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petcapturehelper (/pch) -</span>
|
||||||
|
Enables/disables the CaptureHelper for MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petoptions healthbar -</span> Toggles
|
||||||
|
actionbar healthbar on/off
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petoptions idle-volume <%> -</span>
|
||||||
|
Sets volume percentage for the MyPet
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petinventory (/peti) -</span> Opens the
|
||||||
|
inventory for your current MyPet if it has one
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petpickup (/pp) -</span> Toggles pet pickup
|
||||||
|
on/off if your pet has an inventory
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petbehavior [mode] (/pb) -</span> Switches
|
||||||
|
between pet behaviors or to the specified one
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/petbeacon -</span> Opens the beacon editor
|
||||||
|
for your MyPet if it has one
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding-bottom: 5px !important;">Economy</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/bal -</span> Shows you your balance</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/cheque <amount> -</span> Creates a
|
||||||
|
cheque with the specified amount of money if you're holding a piece of paper
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pay <username> <amount> [-confirmed] -</span>
|
||||||
|
Pays the specified amount to the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/shop -</span> Opens the shop GUI</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/points -</span> Displays your current amount
|
||||||
|
of sell/buy points
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/sell <item> -</span> Tells you if the
|
||||||
|
entered item is sellable at spawn, and at what price
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/buy <item> -</span> Tells you if the
|
||||||
|
entered item is buyable at spawn, and at what price
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/playershop checkstock <radius> [max. stock] -</span>
|
||||||
|
highlights all of your shops which do not meet the minimum amount specified
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Party Chat</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party help -</span> Display the help menu
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party create <partyname> <password> -</span>
|
||||||
|
Creates a party
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party info -</span> Displays information
|
||||||
|
about your current party
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party invite <username> -</span> Invite
|
||||||
|
a user to your party
|
||||||
|
</li>
|
||||||
|
<li><span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/party join <partyname> [password] -</span> Join
|
||||||
|
a users party (password is required only if the party has one)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/p <message> -</span> Changes chat to
|
||||||
|
party chat. If a message is specified it is sent to party chat
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party leave -</span> Leave a party</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party name <newname> -</span> Leave a
|
||||||
|
party
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party owner <username> -</span> Makes
|
||||||
|
the specified user owner of the party
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party password <password> -</span> Sets
|
||||||
|
or changes a password for the party
|
||||||
|
</li>
|
||||||
|
<li><span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/party remove (/party kick) <username> -</span>
|
||||||
|
Kicks the specified user from the party
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/party disband -</span> Disband your party
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Voting</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/vote -</span> Sends a link to our vote page
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/votebest -</span> Shows highest total votes
|
||||||
|
you reached per day/week/month
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/votelast -</span> Shows when you last voted
|
||||||
|
on each of the websites
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/votenext -</span> Shows when you can vote
|
||||||
|
again per website
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/votestreak -</span> Shows your current and
|
||||||
|
best vote streak
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/votetop [page] -</span> Shows the vote top
|
||||||
|
page
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/votetotal -</span> Shows you your total votes
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Warps</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/warps [warp] -</span> Opens the warp GUI or
|
||||||
|
teleports you to the specified warp
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/warp apply -</span> Starts application
|
||||||
|
process for a new warp (costs 25k)
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Altitude</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/queue -</span> Shows what server you are
|
||||||
|
queued for
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/find <username> -</span> Shows if and
|
||||||
|
where this user is online
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/back -</span> Teleports you back to where you
|
||||||
|
last teleported from
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/dback -</span> Brings you back to the last
|
||||||
|
location you died at
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/colors -</span> Shows all color and format
|
||||||
|
codes
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/home [name] -</span> Shows your homes (click
|
||||||
|
to teleport), if a home is specified it will teleport you there
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/list -</span> Lists all online players on the
|
||||||
|
server
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/stafflist -</span> Lists all online staff
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/seen <username> -</span> Shows when a
|
||||||
|
player is/was online
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/playtime -</span> Shows your playtime</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/sit -</span> Allows you to sit down</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/cooldown -</span> Shows the cooldown for the
|
||||||
|
rtp portal
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/hat -</span> Puts the current item on your
|
||||||
|
head
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/discord -</span> Sends you a link to the
|
||||||
|
Altitude Discord server
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/ranks -</span> Shows the ranks on the
|
||||||
|
Altitude server
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/contact -</span> Sends a link to our Contact
|
||||||
|
page
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/report -</span> Sends a link to the report
|
||||||
|
form
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/rules -</span> Sends a link to the rules page
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/store -</span> Sends a link to the Altitude
|
||||||
|
store
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/guide -</span> Sends a link to the guide page
|
||||||
|
for getting started
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/info -</span> Sends some links to our website
|
||||||
|
and Discord
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/se <signline> <text> -</span>
|
||||||
|
While looking at a sign changes text on specified line to specified text.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/editsign -</span> Toggles shift click sign
|
||||||
|
editing
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/kickfromclaim -</span> Kicks specified user
|
||||||
|
from claim
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/kickpet -</span> Toggles kickpet mode, kicks
|
||||||
|
vanilla pets from claims after right clicking it
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/warnings -</span> Shows all active warnings
|
||||||
|
for yourself
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/realname <username/nickname> -</span>
|
||||||
|
Shows the specified users username and nickname
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mapart remove -</span> Deletes map from the
|
||||||
|
database if you own it and are holding it
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mapart save -</span> Saves map on the
|
||||||
|
database if you're holding it
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/sneakclickmending -</span> Allows you to mend
|
||||||
|
your items by right clicking while sneaking
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pu rotateblock -</span> Rotate already
|
||||||
|
placed, direction-specific, blocks
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pu xpcalc <current xp> <goal xp> -</span>
|
||||||
|
Calculates the amount of XP needed to get from level X to level Y
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pu xpcheque <xp> -</span> Creates a
|
||||||
|
'cheque' in the form of an XP bottle with the specified amount of XP
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pt top [daily/weekly/monthly/total]-</span>
|
||||||
|
Displays the players with the highest amount of playtime for the specified time
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/finditem <item> -</span> Enables you to
|
||||||
|
search your chest monster for a specific item
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/local -</span> Toggles local chat, where
|
||||||
|
messages will only be sent to players within a 200 block distance
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/blockitempickup -</span> Prevents picking up
|
||||||
|
items
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="font-family: 'opensans-bold', sans-serif; padding: 25px 0 5px;">Miscellaneous</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/apart -</span> Opens particles GUI</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/cmi armorstand [last/near] -</span> Opens
|
||||||
|
Armorstand editor
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/ping -</span> Shows your ping</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/time -</span> Shows in-game time</li>
|
||||||
|
<li><span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/mail send <username> <message> -</span>
|
||||||
|
Sends a mail to the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/ignore <username> -</span> Ignores a
|
||||||
|
users messages to you and hides their public chat messages from you
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/r <message> -</span> Replies to the
|
||||||
|
last user to send you a message
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/c <message> -</span> Replies to the
|
||||||
|
last user you sent a message to
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/msg <username> <message> -</span>
|
||||||
|
Sends a message to the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/pvp [on/off] -</span> Toggles your pvp on/off
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/sethome <homename> -</span> Sets a home
|
||||||
|
with the specified name
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/delhome <homename> -</span> Shows your
|
||||||
|
homes (click to remove), or removes the specified home
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/spawn -</span> Teleports you to spawn</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/tpa <username> -</span> Sends a
|
||||||
|
teleport request to the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/tpaaccept [username] -</span> Accepts last
|
||||||
|
teleport request, or the teleport request of the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/tpahere <username> -</span> Sends a
|
||||||
|
teleport here request to the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/tpbypass -</span> Used to teleport to a
|
||||||
|
location the plugin marks as unsafe (such as void or lava)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/tpdeny [username] -</span> Denies last
|
||||||
|
teleport request, or the teleport request of the specified user
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/givepet <username> -</span> Gives
|
||||||
|
vanilla pet to specified user after you right click it
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/trapped -</span> Teleports you out of the
|
||||||
|
claim you are in if you do not have build permission there
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/colorsextra -</span> Displays useable RGB
|
||||||
|
colors (shift+lmb to paste in chat)
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/dispose (/trash) -</span> Opens a GUI where
|
||||||
|
you can dispose of items
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
main ul {
|
||||||
|
font-family: 'opensans', sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
color: var(--font-color);
|
||||||
|
transition: 0.5s ease;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CommandlistComponent } from './commandlist.component';
|
||||||
|
|
||||||
|
describe('CommandlistComponent', () => {
|
||||||
|
let component: CommandlistComponent;
|
||||||
|
let fixture: ComponentFixture<CommandlistComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CommandlistComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CommandlistComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-commandlist',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './commandlist.component.html',
|
||||||
|
styleUrl: './commandlist.component.scss'
|
||||||
|
})
|
||||||
|
export class CommandlistComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,79 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'community'" height="460px" background_image="/public/img/backgrounds/community.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Community</h1>
|
||||||
|
<h2>Talented people who help Altitude in more than one way.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="customContainer">
|
||||||
|
<h2>Current Nitro Boosters</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="social" class="darkmodeSectionThree">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Social Media</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<p style="text-align: center;">We're currently not looking for more people to help manage our socials.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="crateTeam" class="darkmodeSection">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Crate Team</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Event Leaders</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<p style="text-align: center;">We're currently not looking for more Event Leaders.</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">Event Team</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<div style="flex-direction: column;">
|
||||||
|
<p style="text-align: center;">We occasionally open applications for the event team.</p>
|
||||||
|
<p style="text-align: center;">If you're interested in joining you simply need to keep an eye on the Discord
|
||||||
|
announcements.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container" style="padding: 50px 0 0 0; justify-content: center;">
|
||||||
|
<h2 class="sectionTitle">YouTubers & Streamers</h2>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<div style="flex-direction: column;">
|
||||||
|
<p style="text-align: center;"><a style="cursor: pointer;" id="reqButton">Show Requirements...</a></p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="req" class="hide" style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<div style="flex-direction: column; justify-content: center; max-width: 800px;">
|
||||||
|
<p style="text-align: center;"><span style="font-family: 'opensans-bold', sans-serif;">Requirements:</span>
|
||||||
|
</p>
|
||||||
|
<p style="text-align: center;">You need to have at least one recent stream/video on Altitude which we can use
|
||||||
|
to gauge if your audience enjoys your content on Altitude.</p>
|
||||||
|
<br>
|
||||||
|
<p style="text-align: center;">Twitch: You need to be affiliate and get at least 5 viewers on average while
|
||||||
|
streaming on Altitude.</p>
|
||||||
|
<p style="text-align: center;">YouTube videos: You need at least 500 subs and have at least 200 views per
|
||||||
|
video within a week on average for Altitude content.</p>
|
||||||
|
<p style="text-align: center;">YouTube streamers: You need at least 500 subs and have at least 5 viewers on
|
||||||
|
average while streaming on Altitude.</p>
|
||||||
|
<br>
|
||||||
|
<p style="text-align: center;"><span style="font-family: 'opensans-bold', sans-serif;">Note:</span> Before
|
||||||
|
accepting or denying you we will watch your latest video/stream on Altitude (so keep your broadcasts public
|
||||||
|
on twitch).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
.customContainer {
|
||||||
|
width: 80%;
|
||||||
|
max-width: 1020px;
|
||||||
|
margin: auto;
|
||||||
|
padding: 80px 0;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CommunityComponent } from './community.component';
|
||||||
|
|
||||||
|
describe('CommunityComponent', () => {
|
||||||
|
let component: CommunityComponent;
|
||||||
|
let fixture: ComponentFixture<CommunityComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CommunityComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CommunityComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-community',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './community.component.html',
|
||||||
|
styleUrl: './community.component.scss'
|
||||||
|
})
|
||||||
|
export class CommunityComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,179 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'skyblock'" height="460px" background_image="/public/img/backgrounds/path.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Custom Features</h1>
|
||||||
|
<h2>A list of custom features created specifically to enhance the survival experience on Altitude.</h2>
|
||||||
|
<h3
|
||||||
|
style="font-family: 'minecraft-text', sans-serif; font-size: 0.8rem; margin-top: 10px; color: #FFFFFF !important;">
|
||||||
|
Commands for the features are listed <a [routerLink]="['/commandlist']">here</a></h3>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Blocks</h2>
|
||||||
|
<p style="padding-bottom: 0 !important;">Beacons can be activated by redstone</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Powering the block above a beacon disables the beam</li>
|
||||||
|
</ul>
|
||||||
|
<p>Glow lichen can be silk touched</p>
|
||||||
|
<p>Vines can be silk touched and bonemealed</p>
|
||||||
|
<p>Coral and coral fans can be bonemealed to grow a new coral tree</p>
|
||||||
|
<p>Mushroom blocks can be stripped with an axe</p>
|
||||||
|
<p>Candles can be placed without a block beneath them</p>
|
||||||
|
<p>Crying obsidian can be used to create nether portals</p>
|
||||||
|
<p style="padding-bottom: 0 !important;">Gravel and sand are craftable in stonecutters</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Cobblestone -> Gravel</li>
|
||||||
|
<li>Gravel -> Sand</li>
|
||||||
|
</ul>
|
||||||
|
<p>Sponges can remove lava - wet sponges remove more (Note: the sponge will be consumed)</p>
|
||||||
|
<p>Item frames can be made invisible using a diamond</p>
|
||||||
|
<p>Rotation of item frames can be locked using a slime ball</p>
|
||||||
|
<p>Copper can be oxidized using a cauldron of water</p>
|
||||||
|
<p>Pink petals can be placed on any surface</p>
|
||||||
|
<p>Wither roses can be bonemealed</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Containers</h2>
|
||||||
|
<p>Dispensers can interact with campfires</p>
|
||||||
|
<p>Dispensers can interact with cauldrons</p>
|
||||||
|
<p>Dispensers can place pumpkins</p>
|
||||||
|
<p>Dispensers can carve pumpkins</p>
|
||||||
|
<p>Barrels have 54 inventory slots</p>
|
||||||
|
<p>Furnaces have a built in item filter, this moves any item they can't process to the output</p>
|
||||||
|
<p>Naming a container or villager "public" makes them publically available</p>
|
||||||
|
<p>Natual loot chests (in dungeons, etc.) are not protected by claims (but can only be broken by the claim
|
||||||
|
owner)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Compostable items</h2>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>leather: 0.10</li>
|
||||||
|
<li>wooden_shovel: 0.10</li>
|
||||||
|
<li>rotten_flesh: 0.15</li>
|
||||||
|
<li>salmon: 0.15</li>
|
||||||
|
<li>cod: 0.15</li>
|
||||||
|
<li>porkchop: 0.15</li>
|
||||||
|
<li>beef: 0.15</li>
|
||||||
|
<li>chicken: 0.15</li>
|
||||||
|
<li>rabbit: 0.15</li>
|
||||||
|
<li>mutton: 0.15</li>
|
||||||
|
<li>bamboo: 0.15</li>
|
||||||
|
<li>wooden_sword: 0.20</li>
|
||||||
|
<li>wooden_hoe: 0.20</li>
|
||||||
|
<li>wooden_pickaxe: 0.30</li>
|
||||||
|
<li>wooden_axe: 0.30</li>
|
||||||
|
<li>poisonous_potato: 0.35</li>
|
||||||
|
<li>leather_boots: 0.40</li>
|
||||||
|
<li>flowering_azalea_leaves: 0.50</li>
|
||||||
|
<li>leather_helmet: 0.50</li>
|
||||||
|
<li>leather_leggings: 0.70</li>
|
||||||
|
<li>leather_chestplate: 0.80</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Ender Dragon</h2>
|
||||||
|
<p>First kill of the month awards a dragon egg</p>
|
||||||
|
<p style="padding-bottom: 0 !important;">Can drop items when killed</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>elytra: 30%</li>
|
||||||
|
<li>dragon_head: 30%</li>
|
||||||
|
<li>dragon_breath: 40%</li>
|
||||||
|
<li>and seperately from the rest, a 0.1% chance for a dragon_egg</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Mobs</h2>
|
||||||
|
<p>Bats can spawn above sea level (bat spawning only enabled during halloween)</p>
|
||||||
|
<p style="padding-bottom: 0 !important;">Mobs can respawn in structures</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Shulker -> End City</li>
|
||||||
|
<li>Piglin Brute, Piglin -> Bastion</li>
|
||||||
|
<li>Evoker, Vindicator, Allay -> Woodland Mansion</li>
|
||||||
|
<li>Allay -> Pillager Outpost</li>
|
||||||
|
<li>Camel -> Desert Village</li>
|
||||||
|
</ul>
|
||||||
|
<p>Naming a mob "muted", "silenced", "silent" or "silence me" silences a mob</p>
|
||||||
|
<p>Naming a mob "muted", "silenced", "silent", "silence me", "protected" or "protect me" prevents it from
|
||||||
|
being damaged by non-trusted players in a claim</p>
|
||||||
|
<p>Naming a mob "baby" prevents it from growing up</p>
|
||||||
|
<p>Wither health bar is removed when silenced</p>
|
||||||
|
<p>Turtles can hatch during daytime</p>
|
||||||
|
<p>Unclaim vanilla player pets (dogs, cats, parrots, horses) if the owner has been offline for 60+ days by
|
||||||
|
right clicking with a lead</p>
|
||||||
|
<p>Fisherman villagers can sell Bucket of Salmon</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Restrictions & disabled vanilla features</h2>
|
||||||
|
<p>Restricted iron golem spawning (10 golems spawning in a radius of 112 blocks in a time span of 1
|
||||||
|
minute)</p>
|
||||||
|
<p>Disabled stacking raids (having multiple raids going on at once)</p>
|
||||||
|
<p>Disabled TNT duping</p>
|
||||||
|
<p>Disabled bat spawning</p>
|
||||||
|
<p>Disabled patrol spawning</p>
|
||||||
|
<p>Zombies don't attack villagers</p>
|
||||||
|
<p>Disabled processing of most things around AFK players</p>
|
||||||
|
<p>Disabled AI for villagers in a 1x1 area</p>
|
||||||
|
<p>Shulkers shooting each other does not spawn a new shulker since they respawn naturally in End Cities</p>
|
||||||
|
<p>Pigmen cannot spawn from portals</p>
|
||||||
|
<p>Allay duplication is disabled since they respawn natually in Mansions and Outposts</p>
|
||||||
|
<p>Players cannot claim land within 100 blocks of another claim without the owner's permission</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Miscellaneous</h2>
|
||||||
|
<p>Grindstones can strip shulker box & beehive NBT data</p>
|
||||||
|
<p>Sneak click mending</p>
|
||||||
|
<p>/iwanttobreakthisblock, required to break some natural generated blocks</p>
|
||||||
|
<p>/sneakclickmending, allows you to mend your items by right clicking while sneaking</p>
|
||||||
|
<p>Swift Sneak enchantment can be found in villager trades</p>
|
||||||
|
<p>Nether portals can be as small as 1x2 (1x2 portal, 3x4 base)</p>
|
||||||
|
<p>Lootchests refill once for every player</p>
|
||||||
|
<p>Empty maps can be duplicated using paper in a cartography table</p>
|
||||||
|
<p>Eating glowberries gives you a glowing effect</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
main ul {
|
||||||
|
font-family: opensans, sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { CustomfeaturesComponent } from './customfeatures.component';
|
||||||
|
|
||||||
|
describe('CustomfeaturesComponent', () => {
|
||||||
|
let component: CustomfeaturesComponent;
|
||||||
|
let fixture: ComponentFixture<CustomfeaturesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [CustomfeaturesComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(CustomfeaturesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,16 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-customfeatures',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './customfeatures.component.html',
|
||||||
|
styleUrl: './customfeatures.component.scss'
|
||||||
|
})
|
||||||
|
export class CustomfeaturesComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<app-forms [currentPage]="'appeal'" [formTitle]="'Minecraft Appeal'">
|
||||||
|
<div form-content>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</app-forms>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { AppealComponent } from './appeal.component';
|
||||||
|
|
||||||
|
describe('AppealComponent', () => {
|
||||||
|
let component: AppealComponent;
|
||||||
|
let fixture: ComponentFixture<AppealComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [AppealComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(AppealComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
import {Component, OnInit} from '@angular/core';
|
||||||
|
import {FormsComponent} from '../forms.component';
|
||||||
|
import {FormControl, FormGroup, Validators} from '@angular/forms';
|
||||||
|
import {AppealsService, MinecraftAppeal} from '../../../api';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-appeal',
|
||||||
|
imports: [
|
||||||
|
FormsComponent
|
||||||
|
],
|
||||||
|
templateUrl: './appeal.component.html',
|
||||||
|
styleUrl: './appeal.component.scss'
|
||||||
|
})
|
||||||
|
export class AppealComponent implements OnInit {
|
||||||
|
|
||||||
|
public form: FormGroup<Appeal>;
|
||||||
|
|
||||||
|
constructor(private appealApi: AppealsService) {
|
||||||
|
this.form = new FormGroup({
|
||||||
|
username: new FormControl('', {nonNullable: true, validators: [Validators.required]}),
|
||||||
|
punishmentId: new FormControl('', {nonNullable: true, validators: [Validators.required]}),
|
||||||
|
email: new FormControl('', {nonNullable: true, validators: [Validators.required, Validators.email]}),
|
||||||
|
appeal: new FormControl('', {nonNullable: true, validators: [Validators.required, Validators.minLength(10)]})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
}
|
||||||
|
|
||||||
|
public onSubmit() {
|
||||||
|
if (this.form === undefined) {
|
||||||
|
console.error('Form is undefined');
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (this.form.valid) {
|
||||||
|
this.sendForm()
|
||||||
|
} else {
|
||||||
|
// Mark all fields as touched to trigger validation display
|
||||||
|
Object.keys(this.form.controls).forEach(field => {
|
||||||
|
const control = this.form!.get(field);
|
||||||
|
if (!(control instanceof FormGroup)) {
|
||||||
|
console.error('Control [' + control + '] is not a FormGroup');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
control.markAsTouched({onlySelf: true});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private sendForm() {
|
||||||
|
const rawValue = this.form.getRawValue();
|
||||||
|
const appeal: MinecraftAppeal = {
|
||||||
|
appeal: rawValue.appeal,
|
||||||
|
email: rawValue.email,
|
||||||
|
punishmentId: parseInt(rawValue.punishmentId),
|
||||||
|
username: rawValue.username,
|
||||||
|
uuid: ''//TODO
|
||||||
|
}
|
||||||
|
this.appealApi.submitMinecraftAppeal(appeal).subscribe()
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Appeal {
|
||||||
|
username: FormControl<string>;
|
||||||
|
punishmentId: FormControl<string>;
|
||||||
|
email: FormControl<string>;
|
||||||
|
appeal: FormControl<string>;
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export enum FormType {
|
||||||
|
APPEAL = 'appeal',
|
||||||
|
STAFF_APPLICATION = 'staff_application',
|
||||||
|
CONTACT = 'contact'
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="currentPage" height="200px" background_image="/public/img/backgrounds/staff.png"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>{{ formTitle }}</h1>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
<ng-container *ngIf="!type">
|
||||||
|
<ng-container *ngFor="let formType of FormType | keyvalue">
|
||||||
|
<button mat-raised-button (click)="setFormType(formType.value)">
|
||||||
|
{{ formType }}
|
||||||
|
</button>
|
||||||
|
</ng-container>
|
||||||
|
</ng-container>
|
||||||
|
<div>
|
||||||
|
<ng-content select="[form-content]"></ng-content>
|
||||||
|
</div>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { FormsComponent } from './forms.component';
|
||||||
|
|
||||||
|
describe('FormsComponent', () => {
|
||||||
|
let component: FormsComponent;
|
||||||
|
let fixture: ComponentFixture<FormsComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [FormsComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(FormsComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
import {Component, Input, OnInit} from '@angular/core';
|
||||||
|
import {HeaderComponent} from '../header/header.component';
|
||||||
|
import {MatDialog} from '@angular/material/dialog';
|
||||||
|
import {ActivatedRoute} from '@angular/router';
|
||||||
|
import {LoginDialogComponent} from '../login/login.component';
|
||||||
|
import {KeyValuePipe, NgForOf, NgIf} from '@angular/common';
|
||||||
|
import {FormType} from './form_type';
|
||||||
|
import {MatButton} from '@angular/material/button';
|
||||||
|
import {AuthService} from '../services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-forms',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
NgIf,
|
||||||
|
NgForOf,
|
||||||
|
MatButton,
|
||||||
|
KeyValuePipe
|
||||||
|
],
|
||||||
|
templateUrl: './forms.component.html',
|
||||||
|
styleUrl: './forms.component.scss'
|
||||||
|
})
|
||||||
|
export class FormsComponent implements OnInit {
|
||||||
|
@Input() formTitle: string = 'Form';
|
||||||
|
@Input() currentPage: string = 'forms';
|
||||||
|
|
||||||
|
public type: FormType | undefined;
|
||||||
|
|
||||||
|
constructor(private authService: AuthService,
|
||||||
|
private dialog: MatDialog,
|
||||||
|
private route: ActivatedRoute,
|
||||||
|
) {
|
||||||
|
this.route.paramMap.subscribe(async params => {
|
||||||
|
const code = params.get('code');
|
||||||
|
|
||||||
|
if (code) {
|
||||||
|
this.authService.login(code).subscribe();
|
||||||
|
} else if (!this.authService.checkAuthStatus()) {
|
||||||
|
const dialogRef = this.dialog.open(LoginDialogComponent, {
|
||||||
|
width: '400px',
|
||||||
|
disableClose: true
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogRef.afterClosed().subscribe();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.route.paramMap.subscribe(params => {
|
||||||
|
switch (params.get('form')) {
|
||||||
|
case FormType.APPEAL:
|
||||||
|
this.type = FormType.APPEAL;
|
||||||
|
this.currentPage = 'appeal';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Error("Invalid type");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
protected readonly FormType = FormType;
|
||||||
|
protected readonly Object = Object;
|
||||||
|
|
||||||
|
public setFormType(formType: FormType) {
|
||||||
|
this.type = formType;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,110 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'guide'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Guide Book</h1>
|
||||||
|
<h2>We aim to be an inclusive community server where players of all ages can find something to enjoy.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Backstory</h2>
|
||||||
|
<p>Altitude is a community-centered survival Minecraft server for {{ ALTITUDE_VERSION }}. Altitude opened
|
||||||
|
in January of 2015 and was originally a private server for a small group of friends. We’ve since opened
|
||||||
|
our doors to welcome anyone and everyone to enjoy the custom survival experience we’ve crafted!</p>
|
||||||
|
<p>The community grew rapidly in the beginning of 1.13 and therefore grew beyond the capacity of a single
|
||||||
|
server, so we opened two survival servers that each offered the same features and plugins, but a different
|
||||||
|
seed so that there is lots of land to explore!. The two servers later became four servers.</p>
|
||||||
|
<p>The Minecraft hype slowly died down and therefore we had to downscale first to two servers for 1.18 and
|
||||||
|
then for 1.20 down to one server again.</p>
|
||||||
|
<p>If you would like to learn more about the history of Altitude, you can visit our <a
|
||||||
|
[routerLink]="['/about']">about page</a>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Claiming Land</h2>
|
||||||
|
<p>You should protect your home by “claiming” it to prevent others stealing your items or damaging your
|
||||||
|
build. This is important because we allow players to loot unclaimed buildings, claim them as their own,
|
||||||
|
and even completely remove them because we otherwise would have a world full of abandoned buildings. This
|
||||||
|
means that if your home is damaged in any way because it wasn’t protected, staff will not be able to
|
||||||
|
restore your items!</p>
|
||||||
|
<p>Claiming land on Altitude is easy. We use the very popular plugin “GriefPrevention”, which allows players
|
||||||
|
to use a golden shovel to make their claim.To get started enter the command <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/claim</span> which will give you the necessary tools.
|
||||||
|
After that you simply click two opposite corners with the golden shovel (can be any golden shovel, not
|
||||||
|
only the one given through /claim) to claim the area between the points.</p>
|
||||||
|
<p>If you need more help with claiming, including trusting others and expanding your claim, visit our <a
|
||||||
|
[routerLink]="['/claiming']">claiming page</a>.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Economy</h2>
|
||||||
|
<p>We offer a collection of economy features to provide a player-focused and player-operated economy.
|
||||||
|
Players can create their own shops with the ChestShop plugin to buy and sell goods with others, apply for
|
||||||
|
a warp so that other players can travel directly to their shop, and pay other players directly with the
|
||||||
|
<b>/pay</b> command.</p>
|
||||||
|
<p>There are also shops in the spawn of each world that sell basic items like raw ores, food, and building
|
||||||
|
blocks. The prices in the spawn shops are designed to be much higher than you would likely find at player
|
||||||
|
shops to encourage trading between players and only using spawn as a last resort.</p>
|
||||||
|
<p>If you would like to learn more about the economy and how to create your own chest shops, see our <a
|
||||||
|
[routerLink]="['/economy']">economy page</a>.</p>
|
||||||
|
<p>If you would like to learn about applying for your own warp, including the criteria you must meet, see
|
||||||
|
our <a [routerLink]="['/warps']">warps page</a>.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Survival Features</h2>
|
||||||
|
<p>On Altitude, we want to provide an authentic survival experience for every player. In a multiplayer
|
||||||
|
environment, this can be challenging! To provide the best experience we possibly can, we’ve kept some
|
||||||
|
vanilla features on that other servers disable:</p>
|
||||||
|
<ul>
|
||||||
|
<li>TNT and creepers do damage! (Claimed land is safe)</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/back</span> to return to your last position
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/dback</span> to return to your death
|
||||||
|
position. (This even works if you have used <b>/back</b> earlier)
|
||||||
|
</li>
|
||||||
|
<li>Loot chests by default cannot be broken. You can enable this by doing <b>/iwanttobreakthisblock <block></b>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>McMMO & MyPet</h2>
|
||||||
|
<p>Our server feature the very popular community-requested plugins: McMMO & MyPet. McMMO introduces an
|
||||||
|
RPG-like experience to the survival game with skill leveling, special abilities, rare loot, and new
|
||||||
|
mechanics. The primary feature of McMMO is the detailed leveling system that breaks down Minecraft into
|
||||||
|
fifteen skills including mining, woodcutting, and herbalism. Each skill will level up the more you do it
|
||||||
|
unlocking powerful abilities related to that skill.</p>
|
||||||
|
<p>If you’d like to learn more about McMMO, <a href="https://mcmmo.org/wiki/Main_Page">visit their wiki
|
||||||
|
here</a>!</p>
|
||||||
|
<p>MyPet allows you to claim nearly any mob in-game as your “pet”. Each pet can be assigned a skilltree that
|
||||||
|
gives it unique abilities. Pets can learn to fight alongside you against mobs, pick up items for you as
|
||||||
|
you mine, and even provide faster travel around the world. Pets will level up in their skilltree the more
|
||||||
|
you play with them to unlock more abilities.</p>
|
||||||
|
<p>If you would like to learn more about MyPet and their abiltiies, visit our <a [routerLink]="['/mypet']">MyPet
|
||||||
|
page</a></p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Creative</h2>
|
||||||
|
<p>Occasionally, you may find yourself with a creative itch, or need a way to experiment and collaborate
|
||||||
|
with other players on the server! We offer a creative server for just that purpose. Players can use as
|
||||||
|
many plots in creative as they need (just ask a staff for more if you reach the default limit) and each
|
||||||
|
plot is 100 by 100 blocks. Players frequently use these plots for testing our survival builds before
|
||||||
|
taking the time to collect the resources in survival to build it.</p>
|
||||||
|
<p>Due to the low traffic that the creative server gets, we do not moderate this server as closely. If you
|
||||||
|
see anything wrong with the server, please inform a staff member so that we can get it resolved!</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>More Help</h2>
|
||||||
|
<p>You are now on your way to becoming a member of the Altitude community! We hope we’ve provided you with
|
||||||
|
the tools necessary to be successful on our servers. If you have any other questions, please reach out to
|
||||||
|
a staff member in-game, or on <a href="https://discordapp.com/invite/TGqpzCJ">our Discord</a>!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
main ul {
|
||||||
|
font-family: opensans, sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { GuideComponent } from './guide.component';
|
||||||
|
|
||||||
|
describe('GuideComponent', () => {
|
||||||
|
let component: GuideComponent;
|
||||||
|
let fixture: ComponentFixture<GuideComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [GuideComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(GuideComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
import {ALTITUDE_VERSION} from '../constant';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-guide',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './guide.component.html',
|
||||||
|
styleUrl: './guide.component.scss'
|
||||||
|
})
|
||||||
|
export class GuideComponent {
|
||||||
|
|
||||||
|
protected readonly ALTITUDE_VERSION = ALTITUDE_VERSION;
|
||||||
|
}
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
</li>
|
</li>
|
||||||
<li class="nav_li">
|
<li class="nav_li">
|
||||||
<span
|
<span
|
||||||
[id]="getCurrentPageId(['guide', 'faq', 'ranks', 'rules', 'commandlist', 'art', 'lag', 'staffpowers',
|
[id]="getCurrentPageId(['guide', 'faq', 'ranks', 'rules', 'commandlist', 'mapart', 'lag', 'staffpowers',
|
||||||
'nicknames', 'bans', 'discord-bans'])" class="nav_link fake_link" [ngClass]="active">Reference</span>
|
'nicknames', 'bans', 'discord-bans'])" class="nav_link fake_link" [ngClass]="active">Reference</span>
|
||||||
<ul class="dropdown">
|
<ul class="dropdown">
|
||||||
<li class="nav_li"><a class="nav_link2" [routerLink]="['/guide']">Guide Book</a></li>
|
<li class="nav_li"><a class="nav_link2" [routerLink]="['/guide']">Guide Book</a></li>
|
||||||
|
|||||||
@@ -0,0 +1,190 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'lag'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Reducing Server Lag</h1>
|
||||||
|
<h2>How can we minimize lag on the server? Together we can create a lag-free survival server, but it takes
|
||||||
|
everyone's help!</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h2>Introduction</h2>
|
||||||
|
<p>Lag is not caused by one person, it is caused by all of us. Some of us cause more than others, but we can
|
||||||
|
all do small things to reduce the amount of lag we cause. Remember: do not ever accuse anyone else of being
|
||||||
|
the problem. If you believe someone’s build or behavior is a large contributor to the lag, please privately
|
||||||
|
tell a staff member so we can look into it. If you have any specific questions, staff are always here to
|
||||||
|
help. This is especially true if you want to make a redstone contraption, but are unsure how to have it
|
||||||
|
automatically shut off when not needed or to make it less taxing on the server.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h2>Mobs</h2>
|
||||||
|
<p style="margin-bottom: 0;">Mobs are the #1 cause of lag on a survival Minecraft server. They can be
|
||||||
|
surprisingly hard on the server and are a cause of lag that everyone contributes to. Because of this, never
|
||||||
|
keep more than you need of any given mob.</p>
|
||||||
|
<p>FarmLimiter is a plugin that introduces hard limits to mobs. The plugin groups mobs by chaining mobs
|
||||||
|
together that are within a minimum radius of each other. Please refer to the example image for a better idea
|
||||||
|
of how mob chaining works. We've provided the settings of this plugin below for your reference, but please
|
||||||
|
do not try to circumvent this system - it is in place for a reason!</p>
|
||||||
|
<ul class="full-page-list" style="margin-bottom: 0;">
|
||||||
|
<li>Villagers should not be allowed to build up from a breeder, and you should cull undesirables as needed
|
||||||
|
to keep your numbers as low as possible. Also think carefully about which villagers you really need and if
|
||||||
|
you can make dual use of some. Only use “infinite” villager breeder designs if you limit their breeding by
|
||||||
|
limiting their food.
|
||||||
|
</li>
|
||||||
|
<li>Villagers used for trading should be put on emerald blocks, emerald ore or smooth stone. This will
|
||||||
|
disable their AI for anything other than trading. This helps server performance a lot and any villagers
|
||||||
|
used for trading found without their AI disabled could be removed by staff if they are causing lag.
|
||||||
|
Villagers placed on these blocks will refresh their trades faster than normal. The following example
|
||||||
|
visualizes the 20 passive mobs within 5 blocks FarmLimiter restriction. <a style="cursor: pointer;"
|
||||||
|
(click)="toggleExceptions()">Show
|
||||||
|
example...</a></li>
|
||||||
|
</ul>
|
||||||
|
<div id="exceptions" [ngClass]="{'hide': !showExceptions}"
|
||||||
|
style="display: flex; justify-content: center; padding-bottom: 30px;">
|
||||||
|
<img ngSrc="/public/img/random/farm_limiter_darker.png" alt="Visualization of the farm limiter"
|
||||||
|
style="width: 70%; padding-top: 7px;" height="375" width="715">
|
||||||
|
</div>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Turtles only need one breeding pair. The breeding mechanic of turtles allows you to breed one pair over
|
||||||
|
and over, as they become fertile immediately upon laying their eggs. Consider keeping only two adult
|
||||||
|
turtles in your farm.
|
||||||
|
</li>
|
||||||
|
<li>Consider not raising pigs at all, as their meat is exactly the same as beef and they have no secondary
|
||||||
|
drops
|
||||||
|
</li>
|
||||||
|
<li>Do not fill your base with more vanilla pets than you need</li>
|
||||||
|
<li>Never circumvent entity cramming. It is there because a lot of entities crammed into a small space is
|
||||||
|
hard on the server.
|
||||||
|
</li>
|
||||||
|
<li>FarmLimiter is a plugin that introduces hard limits to mobs. We've provided the settings of this plugin
|
||||||
|
below for your reference, but please do not try to circumvent this system - it is in place for a reason!
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<div class="highlightedContent">
|
||||||
|
<h3>Farm Limiter Plugin</h3>
|
||||||
|
<p>No more than 25 mobs (of all types) within a radius of 3 blocks (this gets the chickens).</p>
|
||||||
|
<p>No more than 20 passive mobs within a radius of 5 blocks (limits farms - including villagers and
|
||||||
|
excluding chickens).</p>
|
||||||
|
<p>No more than 10 hostile mobs within a single block (limits grinders).</p>
|
||||||
|
<p>No more than 10 turtles within a radius of 5 blocks (turtles cause a LOT of lag).</p>
|
||||||
|
<p>No more than 70 villagers within a radius of 50 blocks.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h2>Minecarts</h2>
|
||||||
|
<p>Minecarts are another type of entity that cause a lot of lag. They are harder on the server than you might
|
||||||
|
think.</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Do not store villagers or other mobs long-term in minecarts. Use them for transportation and drop them
|
||||||
|
off in an enclosed location on the other end.
|
||||||
|
</li>
|
||||||
|
<li>Do not use farm designs that rely on many hopper minecarts. They are convenient because they pick up
|
||||||
|
items through blocks and have a faster throughput than hoppers, but they are unnecessarily hard on the
|
||||||
|
server.
|
||||||
|
</li>
|
||||||
|
<li>Avoid using minecarts for entity cramming purposes when possible and try to design your farm to only
|
||||||
|
have one such killing chamber if it is necessary.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h2>Hoppers</h2>
|
||||||
|
<p>Hoppers cause lag because they are constantly checking for items, even when nothing is in them, so you
|
||||||
|
should minimize your use of them.</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>When bringing items down through a vertical line of hoppers, try alternating them with single chests.
|
||||||
|
This saves you iron and saves the server a bit of lag.
|
||||||
|
</li>
|
||||||
|
<li>Bulk storage systems where random items are dropped in and sent into chests should be avoided because
|
||||||
|
they are likely to cause a situation that is especially taxing. More specifically: A hopper pushing items
|
||||||
|
into a full chest that are not in the chest OR A hopper trying to pull items from a chest to complete
|
||||||
|
stacks but none of the items in the chest match.
|
||||||
|
</li>
|
||||||
|
<li>Myth: Droppers/Furnaces on top of hoppers reduces lag. Droppers on top of hoppers do not reduce lag, and
|
||||||
|
furnaces are a slightly laggy object in their own right, so do not use them unnecessarily.
|
||||||
|
</li>
|
||||||
|
<li>When moving items long distances use water streams. Also try to use water to gather newly farmed items
|
||||||
|
rather than an array of hoppers. However, be aware that water streams have their own downfalls: First, the
|
||||||
|
item entity is at risk to be picked up by a pet with /petpickup turned on. So, do not use them near public
|
||||||
|
areas of your base or for high value items. Second, do not make water streams so long that they flow into
|
||||||
|
unloaded chunks, as the items can back up where chunks unload and create a ticking lag bomb of item
|
||||||
|
entities. Finally, set things up so that, if the storage system at the other end fills up, the extra items
|
||||||
|
get automatically destroyed rather than sitting on full hoppers to despawn.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h2>Redstone Farms</h2>
|
||||||
|
<p>Redstone contraptions are, by their nature, going to contribute some lag. But you can do things to reduce
|
||||||
|
their impact.</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Biggers is not better. Try to make farms a reasonable size for what you need and no larger. Think about
|
||||||
|
how much of a resource you actually need.
|
||||||
|
</li>
|
||||||
|
<li>Think about the design you use. If you have a farm with a lot of pistons to break crops automatically,
|
||||||
|
can you set it up so that the pistons fire in sequence instead of all at once? Can you use a slimeblock
|
||||||
|
push bar so that you only need one piston and one observer for every 12 plants instead of one per each?
|
||||||
|
Minimize how much redstone is running at once.
|
||||||
|
</li>
|
||||||
|
<li>Set up your machines to shut off when not in use.</li>
|
||||||
|
<li>If you use a minecart hopper/chest to transport items, make sure it automatically docks when there are
|
||||||
|
no items to move. You can set up the minecart pickup to only run after a harvest and an auto-smelter that
|
||||||
|
uses minecarts to fill the furnaces does not need to have the minecart running all the time, only when
|
||||||
|
it’s dropping off items.
|
||||||
|
</li>
|
||||||
|
<li>Redstone clocks rarely need to run all the time. For example, an item disposal dropper can be set up to
|
||||||
|
automatically trigger the clock when there are items in the dropper and to shut off the clock when the
|
||||||
|
dropper is empty.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h2>Other Entities</h2>
|
||||||
|
<p>You may have noticed a theme in this list. Everything we've listed is an <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">entity</span>. Entities are the biggest cause of lag on
|
||||||
|
Altitude, and anything we can do to reduce the number of entities on the server will help!</p>
|
||||||
|
<ul class="full-page-list">
|
||||||
|
<li>Block Entities. Any block entity causes more lag than a normal block, but some cause more than others.
|
||||||
|
<a href="https://minecraft.gamepedia.com/Block_entity">minecraft.gamepedia.com/Block_entity</a></li>
|
||||||
|
<li>Signs with text on them are laggier than empty signs, so consider if you need to write that sign. Also,
|
||||||
|
be sure to not write anything on signs used for technical reasons, like to hold back lava or water.
|
||||||
|
</li>
|
||||||
|
<li>Item frames also are a small contributor to lag, but can add up, so please do not use a lot of them for
|
||||||
|
no reason.
|
||||||
|
</li>
|
||||||
|
<li>Brewing Stands cause more lag than you might think, so please do not have more placed than you need.
|
||||||
|
</li>
|
||||||
|
<li>Item Entities. Please pick up item entities when mining, even if you don’t actually want them. You can
|
||||||
|
dispose of them by throwing them in lava or on a cactus. But leaving mass amounts of items to despawn puts
|
||||||
|
an unnecessary strain on the server.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,55 @@
|
|||||||
|
.full-page-list {
|
||||||
|
margin-left: 70px;
|
||||||
|
font-family: 'opensans', sans-serif;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
color: var(--font-color);
|
||||||
|
transition: 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-page-list li {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlightedContent {
|
||||||
|
width: 70%;
|
||||||
|
margin: auto;
|
||||||
|
background: var(--color-quinary);
|
||||||
|
padding: 20px;
|
||||||
|
margin-bottom: 30px;
|
||||||
|
text-align: center;
|
||||||
|
border-radius: 5px;
|
||||||
|
transition: 0.5s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlightedContent p {
|
||||||
|
margin-bottom: 0px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paragraph {
|
||||||
|
width: 100%;
|
||||||
|
margin-bottom: -20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.paragraph p {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hide {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
main .container {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 670px) {
|
||||||
|
main .container {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LagComponent } from './lag.component';
|
||||||
|
|
||||||
|
describe('LagComponent', () => {
|
||||||
|
let component: LagComponent;
|
||||||
|
let fixture: ComponentFixture<LagComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [LagComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LagComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
import {NgClass, NgOptimizedImage} from '@angular/common';
|
||||||
|
import {ScrollService} from '../scroll/scroll.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-lag',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
NgOptimizedImage,
|
||||||
|
NgClass
|
||||||
|
],
|
||||||
|
templateUrl: './lag.component.html',
|
||||||
|
styleUrl: './lag.component.scss'
|
||||||
|
})
|
||||||
|
export class LagComponent {
|
||||||
|
showExceptions: boolean = false;
|
||||||
|
|
||||||
|
constructor(public scrollService: ScrollService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public toggleExceptions() {
|
||||||
|
this.showExceptions = !this.showExceptions;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
<h2 mat-dialog-title>Login</h2>
|
||||||
|
<div mat-dialog-content>
|
||||||
|
<form [formGroup]="loginForm">
|
||||||
|
<mat-form-field appearance="fill" style="width: 100%">
|
||||||
|
<mat-label>Enter your code</mat-label>
|
||||||
|
<input matInput formControlName="code" type="text">
|
||||||
|
<mat-error *ngIf="formHasError()">
|
||||||
|
Code is required
|
||||||
|
</mat-error>
|
||||||
|
</mat-form-field>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
<div mat-dialog-actions align="end">
|
||||||
|
<button mat-button (click)="onCancel()">Cancel</button>
|
||||||
|
<button mat-flat-button color="primary" (click)="onSubmit()" [disabled]="!loginForm.valid">
|
||||||
|
Submit
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
:host {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
max-width: 400px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-content {
|
||||||
|
padding-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mat-dialog-actions {
|
||||||
|
padding: 16px 0;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { LoginComponent } from './login.component';
|
||||||
|
|
||||||
|
describe('LoginComponent', () => {
|
||||||
|
let component: LoginComponent;
|
||||||
|
let fixture: ComponentFixture<LoginComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [LoginComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(LoginComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {MatDialogActions, MatDialogContent, MatDialogRef, MatDialogTitle} from '@angular/material/dialog';
|
||||||
|
import {FormBuilder, FormGroup, ReactiveFormsModule, Validators} from '@angular/forms';
|
||||||
|
import {MatButtonModule} from '@angular/material/button';
|
||||||
|
import {MatInputModule} from '@angular/material/input';
|
||||||
|
import {MatFormFieldModule} from '@angular/material/form-field';
|
||||||
|
import {NgIf} from '@angular/common';
|
||||||
|
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||||
|
import {AuthService} from '../services/auth.service';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-login',
|
||||||
|
standalone: true,
|
||||||
|
imports: [
|
||||||
|
ReactiveFormsModule,
|
||||||
|
MatButtonModule,
|
||||||
|
MatInputModule,
|
||||||
|
MatFormFieldModule,
|
||||||
|
MatDialogTitle,
|
||||||
|
MatDialogContent,
|
||||||
|
MatDialogActions,
|
||||||
|
NgIf
|
||||||
|
],
|
||||||
|
templateUrl: './login.component.html',
|
||||||
|
styleUrl: './login.component.scss'
|
||||||
|
})
|
||||||
|
export class LoginDialogComponent {
|
||||||
|
public loginForm: FormGroup;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
public dialogRef: MatDialogRef<LoginDialogComponent>,
|
||||||
|
private fb: FormBuilder,
|
||||||
|
private authService: AuthService,
|
||||||
|
private snackBar: MatSnackBar
|
||||||
|
) {
|
||||||
|
this.loginForm = this.fb.group({
|
||||||
|
code: ['', [
|
||||||
|
Validators.required,
|
||||||
|
Validators.minLength(8),
|
||||||
|
Validators.maxLength(8),
|
||||||
|
Validators.pattern('^[ABCDEFGHJKLMNPQRSTUVWXYZ23456789]+$')
|
||||||
|
]]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
onCancel(): void {
|
||||||
|
this.dialogRef.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
onSubmit(): void {
|
||||||
|
if (!this.loginForm.valid) {
|
||||||
|
this.snackBar.open('Invalid code', '', {duration: 2000});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.snackBar.open('Logging in...', '', {duration: 2000});
|
||||||
|
this.authService.login(this.loginForm.value.code).subscribe({
|
||||||
|
next: (jwt) => {
|
||||||
|
this.dialogRef.close(jwt);
|
||||||
|
},
|
||||||
|
error: () => {
|
||||||
|
this.loginForm.get('code')?.setErrors({
|
||||||
|
invalid: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public formHasError() {
|
||||||
|
return this.loginForm.get('code')?.hasError('required');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,75 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'mapart'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Mapart</h1>
|
||||||
|
<h2>Altitude features a way to save maparts.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>What is mapart?</h2>
|
||||||
|
<p>Any map is a piece of art, but the common meaning of this is an actual drawing onto a map. By placing
|
||||||
|
blocks on a plot to color in a map one pixel at a time, you can create an image.</p>
|
||||||
|
<p>Creating these maps is time consuming, and Altitude wants to support these mapartists by offering an
|
||||||
|
easier way to share their mapart..</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>How to save mapart</h2>
|
||||||
|
<p>After you have placed all blocks on your plot and have the image recorded on your map, hold the map in
|
||||||
|
your main hand and do <span style="font-family: 'opensans-bold', sans-serif;">/mapart save</span>. This
|
||||||
|
will cost you $1000 in-game currency. The saving process can take several minutes.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful resources for creating mapart</h2>
|
||||||
|
<p>Mapart may be created simply by zooming in on an image you wish to create and placing pixels by eye.
|
||||||
|
First, however, you must know what blocks to use for each color. This <a
|
||||||
|
href="https://minecraft.gamepedia.com/Map_item_format">Minecraft wiki article</a> explains map item
|
||||||
|
formatting such as colors available on maps and the blocks that correspond.</p>
|
||||||
|
<p>Pick whichever blocks are the most convenient to collect and to break later if you plan on reusing blocks
|
||||||
|
for multiple maps.</p>
|
||||||
|
<p>Additionally, there is an incredibly useful online tool known as Mapartcraft by Rebane that can take an
|
||||||
|
image and automatically convert it to a schematic that can be used in conjunction with
|
||||||
|
litematica/schematica as a guide to create much more detailed mapart! Be sure to select the correct
|
||||||
|
version of Minecraft in the settings and pick your preferred blocks. This tool can be found <a
|
||||||
|
href="https://rebane2001.com/mapartcraft/">here</a>.</p>
|
||||||
|
<p>Reminder: While schematica and litematica are permitted mods on Altitude, any features like auto-build
|
||||||
|
and easy-place they may include are not!</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Is my mapart protected?</h2>
|
||||||
|
<p>The only person that can remove or clone saved maps is the one who created them. Mapart can be copied by
|
||||||
|
using the vanilla method on a cartography table.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>What mapart can I make</h2>
|
||||||
|
<p>If you plan on making some kind of map art that some may consider inappropriate, explicit, or
|
||||||
|
controversial, you can submit a request for review through our AltitudeBot support bot on Discord. Simply
|
||||||
|
message the bot anything, and the bot will reply with commands that you can utilize. For mapart review
|
||||||
|
requests, you will be using the command <span style="font-family: 'opensans-bold', sans-serif;">!mapart <description of map art></span>.
|
||||||
|
Please be sure to also attach an image of the mapart (it is not possible to submit the request without an
|
||||||
|
image)!</p>
|
||||||
|
<p>Creating inappropriate mapart without prior approval may result in deletion of the mapart itself and
|
||||||
|
punishment for the creator.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful Commands</h2>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mapart save -</span> Save a mapart</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mapart remove -</span> Delete a mapart</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/mapart find -</span> See who made a specific
|
||||||
|
mapart
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { MapartComponent } from './mapart.component';
|
||||||
|
|
||||||
|
describe('MapartComponent', () => {
|
||||||
|
let component: MapartComponent;
|
||||||
|
let fixture: ComponentFixture<MapartComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [MapartComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(MapartComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-mapart',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './mapart.component.html',
|
||||||
|
styleUrl: './mapart.component.scss'
|
||||||
|
})
|
||||||
|
export class MapartComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'nickgenerator'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Nickname Generator</h1>
|
||||||
|
<h2>Customize your in-game nickname</h2>
|
||||||
|
<h3 style="font-family: 'minecraft-text', sans-serif; font-size: 0.8rem; margin-top: 10px;">Made by TheParm</h3>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<!-- <section class="darkmodeSection">
|
||||||
|
<div class="container containerNick">
|
||||||
|
<div style="padding: 0 5% 0 5%;">
|
||||||
|
<div id="parts" class="previewNickDiv">
|
||||||
|
</div>
|
||||||
|
<div class="previewNickDiv">
|
||||||
|
<input type="button" class="button" value="Add Part" onclick="addPart()"/>
|
||||||
|
<input type="button" class="button" value="Remove Part" onclick="deletePart()"/>
|
||||||
|
</div>
|
||||||
|
<br><br><br><br>
|
||||||
|
<div id="commandTry" class="previewNickDiv">
|
||||||
|
<div id="try" class="command darkBg"></div>
|
||||||
|
<input type="button" class="button copy" value="Copy" onclick="copy(this)"/>
|
||||||
|
</div>
|
||||||
|
<div id="commandRequest" class="previewNickDiv">
|
||||||
|
<div id="request" class="command darkBg"></div>
|
||||||
|
<input type="button" class="button copy" value="Copy" onclick="copy(this)"/>
|
||||||
|
</div>
|
||||||
|
<div id="preview" class="preview darkBg previewNickDiv">
|
||||||
|
</div>
|
||||||
|
<div id="template" class='part' style="display: none">
|
||||||
|
<p style="font-family: 'minecraft-text', sans-serif">
|
||||||
|
Text: <input type="text" id="text" class="textPart" size=18 oninput="inputChanged()"/>
|
||||||
|
Gradient: <input type="checkbox" id="grad" class="gradPart" oninput="onGradient(this)"/>
|
||||||
|
<input id="colorA" type="text" class="coloris colorAPart color" value="#ffffff" oninput="inputChanged()"/>
|
||||||
|
<input id="colorB" type="text" class="coloris colorBPart color" value="#ffffff" oninput="inputChanged()"/>
|
||||||
|
Continuation: <input type="checkbox" id="cont" class="contPart" disabled oninput="onContinuation(this)"/>
|
||||||
|
<span id="invalid" class="invalidPart" style="display: none">(min 1 - max 16 chars)</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div style="margin-top: 20px; text-align: center;">
|
||||||
|
<p style="font-family: 'minecraft-text', sans-serif">
|
||||||
|
Usage: Add as many parts as you wish, then apply the color and/or gradient, and copy/paste the command
|
||||||
|
into the minecraft chat. The total length of the nickname should be between 3 and 16 characters. Use the
|
||||||
|
continuation checkbox to continue the gradient from the last gradient color.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section> -->
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NickgeneratorComponent } from './nickgenerator.component';
|
||||||
|
|
||||||
|
describe('NickgeneratorComponent', () => {
|
||||||
|
let component: NickgeneratorComponent;
|
||||||
|
let fixture: ComponentFixture<NickgeneratorComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [NickgeneratorComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NickgeneratorComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-nickgenerator',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './nickgenerator.component.html',
|
||||||
|
styleUrl: './nickgenerator.component.scss'
|
||||||
|
})
|
||||||
|
export class NickgeneratorComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,82 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'nicknames'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>How To Get A Nickname</h1>
|
||||||
|
<h2>Personalize your writing and nickname in-game by choosing some of the millions of custom colors that we have
|
||||||
|
to offer!</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Creating a Nickname</h2>
|
||||||
|
<p>Donors that have the Duke or Archduke rank have the ability to create custom nicknames for themselves. A
|
||||||
|
nickname should be similar enough to the player’s username for that player to be identifiable, and it
|
||||||
|
<span style="font-family: 'opensans-bold', sans-serif;">must not exceed 16 characters in length</span>. In
|
||||||
|
general, a nickname should include the main part of a player’s actual username to avoid confusion.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Make it Your Own</h2>
|
||||||
|
<p>Altitude now supports the full range of RGB colors, which includes more than 16 million different hues.
|
||||||
|
In addition, the previous basic color codes still exist and are able to be used in combination with the
|
||||||
|
RGB colors. You can still use <span style="font-family: 'opensans-bold', sans-serif;">/colors</span> to
|
||||||
|
see the basic color codes that are available for use. You can also make use of <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/colorsextra</span> which shows many different RGB
|
||||||
|
color options. These colors will be in the form of <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">#XXXXXX</span>; write down the colors that you like so
|
||||||
|
that you can easily use them later.</p>
|
||||||
|
<img ngSrc="/public/img/random/colors.png" alt="RGB colors" style="width: 100%; padding:0;" height="130"
|
||||||
|
width="480">
|
||||||
|
<p>Output of /colorsextra</p>
|
||||||
|
<p>Furthermore, you can also use the <a [routerLink]="['/nickgenerator']">nickname generation tool</a> to
|
||||||
|
make and preview potential nicknames - as well as copying the commands to use in-game.</p>
|
||||||
|
<p>Players are encouraged to play around and personalize their nicknames, as long as they follow the rules
|
||||||
|
and are legible! There are endless possibilities beyond simple gradients as well; players can create any
|
||||||
|
combination of standard colors and gradients that they choose.</p>
|
||||||
|
<p>Even if you aren’t a Duke or Archduke, you can try out colors like this on signs in-game.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Request Your Nickname</h2>
|
||||||
|
<p>Players can only submit a new nickname request using <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">/nick request <name></span> once per day. When a
|
||||||
|
nickname is requested, it will notify staff for near-instant approval. Creating custom nicknames using RGB
|
||||||
|
can be difficult, so we have implemented <span style="font-family: 'opensans-bold', sans-serif;">/nick try <name></span>
|
||||||
|
so that you can experiment with your nicknames as many times as you want. It is highly encouraged to use
|
||||||
|
this command before submitting a formal request to staff. You can select an RGB color by putting the <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">#XXXXXX</span> code into curly braces before your
|
||||||
|
text.</p>
|
||||||
|
<p>We also support gradients in nicknames. You can specify the start and finish color and your nickname will
|
||||||
|
automatically find a gradient that goes between the two endpoints. <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">The symbol > is used to start a gradient, and the symbol < is used to close a gradient.</span>
|
||||||
|
You can have as many gradients as you want in your nickname, as long as it is readable.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">/nick try {#003380}Player</span>
|
||||||
|
would result in a blue name that read as “Player”.</p>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">/nick try {#003380>}Player{#0000FF<}</span>
|
||||||
|
would result in the nickname reading as “Player” with a blue gradient fading across the letters in the
|
||||||
|
name.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful Commands</h2>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nick help</span> - Shows a list of all useful
|
||||||
|
nickname commands and how to use them.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nick try name</span> - Allows players to see
|
||||||
|
how a nickname would look as many times as you like.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/nick request name</span> - Allows players to
|
||||||
|
submit a new nickname to staff for approval. This function can only be used once per day.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { NicknamesComponent } from './nicknames.component';
|
||||||
|
|
||||||
|
describe('NicknamesComponent', () => {
|
||||||
|
let component: NicknamesComponent;
|
||||||
|
let fixture: ComponentFixture<NicknamesComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [NicknamesComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(NicknamesComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
import {NgOptimizedImage} from '@angular/common';
|
||||||
|
import {RouterLink} from '@angular/router';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-nicknames',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent,
|
||||||
|
NgOptimizedImage,
|
||||||
|
RouterLink
|
||||||
|
],
|
||||||
|
templateUrl: './nicknames.component.html',
|
||||||
|
styleUrl: './nicknames.component.scss'
|
||||||
|
})
|
||||||
|
export class NicknamesComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,228 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'ranks'" height="460px" background_image="/public/img/backgrounds/118fjord.png"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Server Ranks</h1>
|
||||||
|
<h2>The standard ranks are unlocked by playing on the server. As you accumulate time you will automatically rank
|
||||||
|
up. Do /rank to check your playtime.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="tableTitle">Rank</th>
|
||||||
|
<th scope="col" class="tableTitle">Requirements</th>
|
||||||
|
<th scope="col" class="tableTitle">Perks</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Drifter</span>]</td>
|
||||||
|
<td data-label="Requirements">Join the server!</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
All basic permissions<br>
|
||||||
|
Up to 3 homes in survival<br>
|
||||||
|
Up to 2 MyPet slots<br>
|
||||||
|
Up to 5 claims in survival
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Nomad</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 2 hours</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 4 homes in survival<br>
|
||||||
|
Up to 3 MyPet slots<br>
|
||||||
|
Up to 10 claims in survival
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Peddler</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 1 day</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 5 homes in survival<br>
|
||||||
|
Up to 4 MyPet slots<br>
|
||||||
|
Up to 15 claims in survival
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Settler</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 3 days</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 6 homes in survival<br>
|
||||||
|
Up to 5 MyPet slots<br>
|
||||||
|
Up to 20 claims in survival
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Resident</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 7 days</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 7 homes in survival<br>
|
||||||
|
Up to 6 MyPet slots<br>
|
||||||
|
Up to 25 claims in survival
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Esquire</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 14 days</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 8 homes in survival<br>
|
||||||
|
Up to 7 MyPet slots<br>
|
||||||
|
Up to 30 claims in survival
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Knight</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 30 days</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 9 homes in survival<br>
|
||||||
|
Up to 8 MyPet slots
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Baron</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 60 days</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 10 homes in survival<br>
|
||||||
|
Up to 9 MyPet slots
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank1">Viscount</span>]</td>
|
||||||
|
<td data-label="Requirements">Play for 180 days</td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Up to 11 homes in survival<br>
|
||||||
|
Up to 9 MyPet slots
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="subheaderDonor">
|
||||||
|
<div class="title" style="margin-top: 0; height: 100%;">
|
||||||
|
<h1>Premium Ranks</h1>
|
||||||
|
<h2>Premium ranks are a symbol of those who have purchased a rank to support the server. Purchase a rank today
|
||||||
|
at: <a href="https://store.alttd.com">donate.alttd.com</a>. Thank you!</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<table style="width:100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="tableTitle">Rank</th>
|
||||||
|
<th scope="col" class="tableTitle">Requirements</th>
|
||||||
|
<th scope="col" class="tableTitle">Perks</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank2">Count</span>]</td>
|
||||||
|
<td data-label="Requirements">Purchase for $15 on <a href="https://store.alttd.com">Buycraft</a></td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Donor Discord channel access<br>
|
||||||
|
Up to 12 homes in survival<br>
|
||||||
|
Up to 10 MyPet slots<br>
|
||||||
|
More on the <a href="https://store.alttd.com/category/514458">store page</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank2">Viceroy</span>]</td>
|
||||||
|
<td data-label="Requirements">Purchase for $30 on <a href="https://store.alttd.com">Buycraft</a></td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Priority queue for full servers<br>
|
||||||
|
Up to 13 homes in survival<br>
|
||||||
|
Up to 18 MyPet slots<br>
|
||||||
|
More on the <a href="https://store.alttd.com/category/514458">store page</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank2">Duke</span>]</td>
|
||||||
|
<td data-label="Requirements">Purchase for $50 on <a href="https://store.alttd.com">Buycraft</a></td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Nickname in chat<br>
|
||||||
|
Up to 14 homes in survival<br>
|
||||||
|
Up to 27 MyPet slots<br>
|
||||||
|
More on the <a href="https://store.alttd.com/category/514458">store page</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank2">Archduke</span>]</td>
|
||||||
|
<td data-label="Requirements">Purchase for $100 on <a href="https://store.alttd.com">Buycraft</a></td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Custom prefix in chat<br>
|
||||||
|
Up to 16 homes in survival<br>
|
||||||
|
Up to 36 MyPet slots<br>
|
||||||
|
More on the <a href="https://store.alttd.com/category/514458">store page</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank2">α</span>]</td>
|
||||||
|
<td data-label="Requirements">Purchase for $25 on <a href="https://store.alttd.com">Buycraft</a></td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Time-limited rank<br>
|
||||||
|
Access to alpha testing<br>
|
||||||
|
This custom prefix<br>
|
||||||
|
More on the <a href="https://store.alttd.com/category/514458">store page</a></td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank4">▲</span>]</td>
|
||||||
|
<td data-label="Requirements">Boosting with <a href="https://discordapp.com/nitro">Discord Nitro</a></td>
|
||||||
|
<td data-label="Perks" class="perks">
|
||||||
|
Discord Nitro perks<br>
|
||||||
|
This custom prefix
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section id="subheaderCommunity">
|
||||||
|
<div class="title" style="margin-top: 0; height: 100%;">
|
||||||
|
<h1>Community Ranks</h1>
|
||||||
|
<h2>Community ranks are for talented people who support Altitude in more than one way.</h2>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<table style="width:100%">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th scope="col" class="tableTitle">Rank</th>
|
||||||
|
<th scope="col" class="tableTitle">Requirements</th>
|
||||||
|
<th scope="col" class="tableTitle">Perks</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank5">Social Media</span>]</td>
|
||||||
|
<td data-label="Requirements">See requirements on the <a href="/community.php">Community</a> page</td>
|
||||||
|
<td data-label="Perks" class="perks">/record<br>/ptime<br>/pweather</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank5">Streamer</span>]</td>
|
||||||
|
<td data-label="Requirements">See requirements on the <a href="/community.php">Community</a> page</td>
|
||||||
|
<td data-label="Perks" class="perks">/record<br>/ptime<br>/pweather</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank5">YouTube</span>]</td>
|
||||||
|
<td data-label="Requirements">See requirements on the <a href="/community.php">Community</a> page</td>
|
||||||
|
<td data-label="Perks" class="perks">/record<br>/ptime<br>/pweather</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank5">Event Leader</span>]</td>
|
||||||
|
<td data-label="Requirements">See requirements on the <a href="/community.php">Community</a> page</td>
|
||||||
|
<td data-label="Perks" class="perks">Build perms on event server</td>
|
||||||
|
</tr>
|
||||||
|
<tr>
|
||||||
|
<td data-label="Rank" class="rankTitle">[<span class="rank5">Event Team</span>]</td>
|
||||||
|
<td data-label="Requirements">See requirements on the <a href="/community.php">Community</a> page</td>
|
||||||
|
<td data-label="Perks" class="perks">Build perms on event server</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,151 @@
|
|||||||
|
#subheaderDonor {
|
||||||
|
height: 350px;
|
||||||
|
background: -webkit-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/newtown.jpg");
|
||||||
|
background: -o-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/newtown.jpg");
|
||||||
|
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/newtown.jpg");
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#subheaderCommunity {
|
||||||
|
height: 350px;
|
||||||
|
background: -webkit-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/caruselimage4.png");
|
||||||
|
background: -o-linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/caruselimage4.png");
|
||||||
|
background: linear-gradient(rgba(0, 0, 0, 0.5), rgba(0, 0, 0, 0.5)), url("/public/img/backgrounds/caruselimage4.png");
|
||||||
|
background-attachment: fixed;
|
||||||
|
background-position: center;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-size: cover;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
td,
|
||||||
|
th {
|
||||||
|
font-family: 'opensans', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
border-collapse: collapse;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
width: 100%;
|
||||||
|
table-layout: fixed;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr {
|
||||||
|
padding: .35em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
padding: .625em;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th {
|
||||||
|
font-size: .85em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perks {
|
||||||
|
text-align: left;
|
||||||
|
padding-left: 105px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tableTitle {
|
||||||
|
font-family: 'opensans-bold', sans-serif;
|
||||||
|
font-size: 1.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rankTitle {
|
||||||
|
font-family: 'opensans-bold', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank1 {
|
||||||
|
color: #265ac9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank2 {
|
||||||
|
color: #980399;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank3 {
|
||||||
|
color: #0c3999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank4 {
|
||||||
|
color: #c949c9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.rank5 {
|
||||||
|
color: #ffaa00;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1200px) {
|
||||||
|
.perks {
|
||||||
|
padding-left: 80px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 900px) {
|
||||||
|
.perks {
|
||||||
|
padding-left: 50px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 690px) {
|
||||||
|
main .container {
|
||||||
|
width: 80% !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (max-width: 600px) {
|
||||||
|
table {
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
table th,
|
||||||
|
table td {
|
||||||
|
padding: .400em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table thead {
|
||||||
|
border: none;
|
||||||
|
clip: rect(0 0 0 0);
|
||||||
|
height: 1px;
|
||||||
|
margin: -1px;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 0;
|
||||||
|
position: absolute;
|
||||||
|
width: 1px;
|
||||||
|
}
|
||||||
|
|
||||||
|
table tr {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: .625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td {
|
||||||
|
display: block;
|
||||||
|
font-size: .8em;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td::before {
|
||||||
|
content: attr(data-label);
|
||||||
|
float: left;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
table td:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.perks {
|
||||||
|
text-align: right;
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { RanksComponent } from './ranks.component';
|
||||||
|
|
||||||
|
describe('RanksComponent', () => {
|
||||||
|
let component: RanksComponent;
|
||||||
|
let fixture: ComponentFixture<RanksComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [RanksComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(RanksComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-ranks',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './ranks.component.html',
|
||||||
|
styleUrl: './ranks.component.scss'
|
||||||
|
})
|
||||||
|
export class RanksComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -40,7 +40,7 @@
|
|||||||
<li>You can talk about current events and real-world topics. However, if staff recognizes the topic is
|
<li>You can talk about current events and real-world topics. However, if staff recognizes the topic is
|
||||||
causing players to become uncomfortable, they may shut the conversation down.
|
causing players to become uncomfortable, they may shut the conversation down.
|
||||||
<ul class="full-page-list-2">
|
<ul class="full-page-list-2">
|
||||||
<li>You are welcome to take conversations regarding sensitive topics to dm's and party chat with people
|
<li>You are welcome to take conversations regarding sensitive topics to DMs and party chat with people
|
||||||
who want to be involved, or off the server.
|
who want to be involved, or off the server.
|
||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|||||||
@@ -0,0 +1,115 @@
|
|||||||
|
import {Injectable} from '@angular/core';
|
||||||
|
import {LoginService} from '../../api';
|
||||||
|
import {CookieService} from 'ngx-cookie-service';
|
||||||
|
import {BehaviorSubject, Observable, throwError} from 'rxjs';
|
||||||
|
import {catchError, tap} from 'rxjs/operators';
|
||||||
|
import {MatSnackBar} from '@angular/material/snack-bar';
|
||||||
|
|
||||||
|
@Injectable({
|
||||||
|
providedIn: 'root'
|
||||||
|
})
|
||||||
|
export class AuthService {
|
||||||
|
private isAuthenticatedSubject = new BehaviorSubject<boolean>(false);
|
||||||
|
public isAuthenticated$ = this.isAuthenticatedSubject.asObservable();
|
||||||
|
|
||||||
|
private userClaimsSubject = new BehaviorSubject<any>(null);
|
||||||
|
public userClaims$ = this.userClaimsSubject.asObservable();
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private loginService: LoginService,
|
||||||
|
private cookieService: CookieService,
|
||||||
|
private snackBar: MatSnackBar
|
||||||
|
) {
|
||||||
|
// Check if user is already logged in on service initialization
|
||||||
|
this.checkAuthStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attempt to login with the provided code
|
||||||
|
*/
|
||||||
|
public login(code: string): Observable<any> {
|
||||||
|
return this.loginService.login(code).pipe(
|
||||||
|
tap(jwt => {
|
||||||
|
this.saveJwt(jwt as JsonWebKey);
|
||||||
|
this.isAuthenticatedSubject.next(true);
|
||||||
|
}),
|
||||||
|
catchError(error => {
|
||||||
|
this.snackBar.open('Login failed', '', {duration: 2000});
|
||||||
|
return throwError(() => error);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Log the user out by removing the JWT
|
||||||
|
*/
|
||||||
|
public logout(): void {
|
||||||
|
this.cookieService.delete('jwt', '/');
|
||||||
|
this.isAuthenticatedSubject.next(false);
|
||||||
|
this.userClaimsSubject.next(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the user is authenticated
|
||||||
|
*/
|
||||||
|
public checkAuthStatus(): boolean {
|
||||||
|
const jwt = this.getJwt();
|
||||||
|
if (jwt) {
|
||||||
|
try {
|
||||||
|
const claims = this.extractJwtClaims(jwt as JsonWebKey);
|
||||||
|
// Check if token is expired
|
||||||
|
const currentTime = Math.floor(Date.now() / 1000);
|
||||||
|
if (claims.exp && claims.exp < currentTime) {
|
||||||
|
this.logout();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.userClaimsSubject.next(claims);
|
||||||
|
this.isAuthenticatedSubject.next(true);
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
this.logout();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the JWT from cookies
|
||||||
|
*/
|
||||||
|
public getJwt(): string | null {
|
||||||
|
return this.cookieService.check('jwt') ? this.cookieService.get('jwt') : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Save the JWT to cookies
|
||||||
|
*/
|
||||||
|
private saveJwt(jwt: JsonWebKey): void {
|
||||||
|
this.cookieService.set('jwt', jwt.toString(), {
|
||||||
|
path: '/',
|
||||||
|
secure: true,
|
||||||
|
sameSite: 'Strict'
|
||||||
|
});
|
||||||
|
|
||||||
|
const claims = this.extractJwtClaims(jwt);
|
||||||
|
this.userClaimsSubject.next(claims);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extract claims from JWT
|
||||||
|
*/
|
||||||
|
private extractJwtClaims(jwt: JsonWebKey): any {
|
||||||
|
const token = jwt.toString();
|
||||||
|
const base64Url = token.split('.')[1];
|
||||||
|
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
|
||||||
|
return JSON.parse(window.atob(base64));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get user authorizations from claims
|
||||||
|
*/
|
||||||
|
public getUserAuthorizations(): string[] {
|
||||||
|
const claims = this.userClaimsSubject.getValue();
|
||||||
|
return claims?.authorizations || [];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,123 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'skyblock'" height="460px" background_image="/public/img/backgrounds/babywither.png"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Skyblock</h1>
|
||||||
|
<h2>A skyblock server blending vanilla gameplay with unique enhancements for an elevated, community-driven sky
|
||||||
|
adventure.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Discover Comet</h2>
|
||||||
|
<p>Welcome to Comet, a skyblock server that offers players a unique blend of the features you know and love
|
||||||
|
with a dash of exiciting enhancements to elevate your sky-high adventure.</p>
|
||||||
|
<p>The heart of the skyblock challenge is the same as your traditional experience: start on a small island
|
||||||
|
in the sky with limited resources and aim to expand your domain and conquer the endless void. However,
|
||||||
|
Comet is not your ordinary skyblock experience. We've carefully introduced a selection of enhancements to
|
||||||
|
enrich the gameplay without overwhelming the core vanilla experience that players love and cherish.</p>
|
||||||
|
<p style="padding-bottom: 5px !important;">Some of the key features and enhancements includes:</p>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Explorable Biomes:</span> All islands are on
|
||||||
|
the same public seed, you can expand your island and explore to find different biomes. Certain mobs only
|
||||||
|
spawn in certain biomes.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Dynamic Economy:</span> Engage with our
|
||||||
|
vibrant server economy, where you can trade items with other players, visit the spawn shops or even set
|
||||||
|
up your own shops on your island.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Custom Achievements:</span> Beyond the
|
||||||
|
traditional skyblock objectives, Comet introduces a series of custom achievements which will test your
|
||||||
|
skills and reward you with valuable resources and unique items.
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">Cooperative Play:</span> Team up with friends
|
||||||
|
or make new allies to build your sky empire together.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
<p style="padding-top: 10px;">Comet is the perfect experience for those who love skyblock but crave a bit
|
||||||
|
more excitement and community interaction. Whether you're a seasoned player or new to the sky life, Comet
|
||||||
|
provides a rich platform for exploration, creativity and fun.</p>
|
||||||
|
<p>Join Comet today and discover that in the world of skyblock, the sky is just the beginning.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Useful commands</h2>
|
||||||
|
<ul>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island -</span> Opens a GUI where you can see
|
||||||
|
information about your island, change settings and do upgrades
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island go -</span> Start a new island or go
|
||||||
|
to your current island
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island invite <username> -</span>
|
||||||
|
Invite a player to your island
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island kick <username> -</span> Kick a
|
||||||
|
player from your island
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island leave -</span> Leave your current
|
||||||
|
island
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island restart -</span> Reset your island
|
||||||
|
without affecting removing your island members
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island setowner <username> -</span> Set
|
||||||
|
a new owner of your current island
|
||||||
|
</li>
|
||||||
|
<li><span style="font-family: 'opensans-bold', sans-serif;">/island visit <username> -</span> Visit
|
||||||
|
the specificed player's island
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Cobble Generator Levels</h2>
|
||||||
|
<p><span style="font-family: 'opensans-bold', sans-serif;">Note:</span> Did you mess up your generator and
|
||||||
|
accidentally made an obsidian block? No fear, simply right click the obsidian with your bucket and it will
|
||||||
|
change lava in your bucket!</p>
|
||||||
|
<ul>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 1, coal</p>
|
||||||
|
<li>Island level 20</li>
|
||||||
|
<li>64 cobblestone</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 2, iron</p>
|
||||||
|
<li>Island level 35</li>
|
||||||
|
<li>128 cobblestone</li>
|
||||||
|
<li>35 coal</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 3, redstone</p>
|
||||||
|
<li>Island level 50</li>
|
||||||
|
<li>192 cobblestone</li>
|
||||||
|
<li>50 iron</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 4, lapis</p>
|
||||||
|
<li>Island level 60</li>
|
||||||
|
<li>256 cobblestone</li>
|
||||||
|
<li>60 redstone</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 5, copper</p>
|
||||||
|
<li>Island level 70</li>
|
||||||
|
<li>320 cobblestone</li>
|
||||||
|
<li>70 lapis</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 6, gold</p>
|
||||||
|
<li>Island level 80</li>
|
||||||
|
<li>384 cobblestone</li>
|
||||||
|
<li>80 copper</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 7, emerald</p>
|
||||||
|
<li>Island level 90</li>
|
||||||
|
<li>448 cobblestone</li>
|
||||||
|
<li>90 gold</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 8, diamond</p>
|
||||||
|
<li>Island level 100</li>
|
||||||
|
<li>512 cobblestone</li>
|
||||||
|
<li>100 emerald</li>
|
||||||
|
<p style="padding-bottom: 5px !important;">Level 9, ancient debris</p>
|
||||||
|
<li>Island level 150</li>
|
||||||
|
<li>576 cobblestone</li>
|
||||||
|
<li>150 diamond</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
main ul {
|
||||||
|
font-family: opensans, sans-serif;
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
main li {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { SkyblockComponent } from './skyblock.component';
|
||||||
|
|
||||||
|
describe('SkyblockComponent', () => {
|
||||||
|
let component: SkyblockComponent;
|
||||||
|
let fixture: ComponentFixture<SkyblockComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [SkyblockComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(SkyblockComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from '../header/header.component';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-skyblock',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './skyblock.component.html',
|
||||||
|
styleUrl: './skyblock.component.scss'
|
||||||
|
})
|
||||||
|
export class SkyblockComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,277 @@
|
|||||||
|
<ng-container>
|
||||||
|
<app-header [current_page]="'staffpowers'" height="460px" background_image="/public/img/backgrounds/trees.jpg"
|
||||||
|
[overlay_gradient]="0.5">
|
||||||
|
<div class="title" header-content>
|
||||||
|
<h1>Staff Powers</h1>
|
||||||
|
<h2>Our staff members have numerous powers to assist them in moderating effectively.</h2>
|
||||||
|
</div>
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<main>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<section class="columnSection">
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Staff Powers</h2>
|
||||||
|
<p>We believe it's important to be transparent with our community about what our staff can see and do. To do
|
||||||
|
this we've created this web page to explain all the things staff can see/do and why.</p>
|
||||||
|
<p>We store a lot of information, basically everything you do on Altitude is logged. We only use this data
|
||||||
|
to moderate, we will never sell it or give it to anyone who isn't an active staff member. Any misuse of
|
||||||
|
the data we store by staff members results in their immediate removal from the team.</p>
|
||||||
|
<p>This is not a complete list of all powers the staff team has, these are just a list of the commands that
|
||||||
|
could be misinterpreted as giving staff members an advantage or are related to your data.</p>
|
||||||
|
<p>If you have a concern about any of the powers talked about on the rest of this page, or you feel like
|
||||||
|
something is missing. Please get in contact with head staff through the AltitudeBot on Discord or by
|
||||||
|
submitting a contact form with a valid email.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Staff Rooms</h2>
|
||||||
|
<p>Staff rooms are places where staff may need to go to handle a variety of staff-related tasks. We don’t
|
||||||
|
want staff to seem unresponsive when working on a task if a player sees them unmoving on the server
|
||||||
|
somewhere when sorting through logs, coreprotect data, or sending mails to players. Often, secure
|
||||||
|
‘physical’ space is needed by staff to deal with an issue on the server (For example, storing stolen items
|
||||||
|
in one organized place, where all other mods can safely access them so they can be returned to the player
|
||||||
|
who owns them as quickly as possible). Staff meetings also often occur in these rooms so that the server
|
||||||
|
isn’t left unattended during the meeting, while the team can still remain engaged in the meeting.</p>
|
||||||
|
<p>Previously, staff rooms were placed under server spawns for quick access by that server’s staff team.
|
||||||
|
Now, staff rooms have been removed from the survival world and exist in a dimension similar to the nether
|
||||||
|
and the end.<br>
|
||||||
|
Concerns have been raised in the past that this space is not accessible to players. Due to the purpose of
|
||||||
|
the space (solely to handle staff-related tasks/player issues, and host meetings), this space will forever
|
||||||
|
remain inaccessible to players as that access would eliminate the purpose of the space.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Spy (seeing DMs & PartyChat)</h2>
|
||||||
|
<p>Staff members are able to see all private messages and messages sent in party chats. This is vital for
|
||||||
|
protecting our players, we would not be able to moderate effectively without also being able to see
|
||||||
|
private messages.</p>
|
||||||
|
<p>We will never purposely spy on your private conversation for personal gain, the only time we will
|
||||||
|
interfere with these messages is if we see a severely concerning conversation happening; harassment,
|
||||||
|
bullying, suicide/self harm discussion, sexual discussion, etc. We will almost always step in through just
|
||||||
|
a simple DM to check in on things. We will take relevant private messages into account when collecting
|
||||||
|
evidence on a player.</p>
|
||||||
|
<p>Please keep in mind, this does not mean staff are constantly moderating your DM’s in the way they do
|
||||||
|
chat. If you are experiencing an issue in DM’s and need help, please reach out to a member of staff.<br>
|
||||||
|
Altitude staff are <span style="font-family: 'opensans-bold', sans-serif;">not</span> trained mental
|
||||||
|
health professionals, and we are not qualified to counsel through suicide and self harm, but we value our
|
||||||
|
community members deeply and will do our best to give you some official resources that you can use.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="columnContainer">
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Player Data (IPs and logs)</h2>
|
||||||
|
<p>All staff members have access to your IP through /whois and /iphist. They will never use this information
|
||||||
|
outside of moderation, and it will never be shared outside of our staff team. Everytime you visit a place
|
||||||
|
on the internet, your IP is used to share data back to you. If we did not have access to your IP, you
|
||||||
|
would not be able to play on Altitude. The only times we will use your IP for staff purposes is to compare
|
||||||
|
your IP to an account we may think is an alt (this is also why we have a history of your previous IPs
|
||||||
|
you’ve logged in with).</p>
|
||||||
|
<p>Almost every single action taken on Altitude is tracked through some sort of log. We’ve created a very
|
||||||
|
detailed logging system to help ensure that we are always well informed when dealing with different staff
|
||||||
|
scenarios, and these logs help us tremendously. We do not share these logs outside of the staff team and
|
||||||
|
we will never use them for anything unrelated to staff. We will not list everything we log because it’s
|
||||||
|
not necessary and would be too long of a list, but just beware that basically anything you do on Altitude
|
||||||
|
is logged in some way. This is not only to protect Altitude, but you as well.</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>CoreProtect</h2>
|
||||||
|
<p>Coreprotect is a log on the server that allows staff to see a variety of interactions on the server- From
|
||||||
|
block deletion to buttons pressed to commands issued. 90% of the time, staff use coreprotect when dealing
|
||||||
|
with issues related to blocks/entities, such as griefing and theft. Staff may use coreprotect in
|
||||||
|
conjunction with our database logs if a situation is bigger than coreprotect can support us with. As a
|
||||||
|
result, staff may take a bit of time when investigating a player issue due to the searching they must do
|
||||||
|
with coreprotect, especially if there is a lot of related data to search through!</p>
|
||||||
|
<p>Many servers use coreprotect as it is the most reliable source of this information, is well organized and
|
||||||
|
is extremely well documented. Coreprotect data is considered confidential, and staff will <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">not</span> release or share any coreprotect data with
|
||||||
|
players. The only exception to this rule is cases of theft under trust - where staff will share the name
|
||||||
|
of the thief with the claim owner for the purpose of them being removed from their claimlist and not being
|
||||||
|
re-trusted in the future.</p>
|
||||||
|
<p>Coreprotect also allows us to fix some serious problems using a tool called <span
|
||||||
|
style="font-family: 'opensans-bold', sans-serif;">Rollback</span>. Rollback allows staff to reverse the
|
||||||
|
interactions done with blocks/entities/containers within a selected period of time, and helps us fix
|
||||||
|
things quickly for you after griefing occurs!</p>
|
||||||
|
</div>
|
||||||
|
<div class="columnParagraph">
|
||||||
|
<h2>Head Staff</h2>
|
||||||
|
<p>Head staff (everyone above mod) have access to a lot of things depending on their exact job. The most
|
||||||
|
relevant things they have access to are: creative, worldedit, and invload. These powers can be used to
|
||||||
|
restore items we can't properly restore with coreprotect and remove/move/restore builds as needed.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/fly</h3>
|
||||||
|
<p>Fly allows staff members to freely do their job without having to worry about being limited to just the
|
||||||
|
ground. There are a lot of instances where they need to be able to check out something high off the ground
|
||||||
|
or they just need to be able to travel more efficiently, and flying allows them to do this. We allow our
|
||||||
|
staff members to use fly for their own personal builds, sort of as a thank you and to help make up for the
|
||||||
|
time they lose through all of their moderation. We do not want any staff members to be openly flying around
|
||||||
|
in front of players, and they will never use it for anything other than building (looting, exploring, etc.
|
||||||
|
is not allowed).</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/god</h3>
|
||||||
|
<p>God mode allows staff members to work on something in game without being physically disturbed or harmed.
|
||||||
|
This is a tool that is only ever used for moderation purposes, to allow staff to focus on their job without
|
||||||
|
being interrupted by outside elements.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/ignoreclaims</h3>
|
||||||
|
<p>The ignore claims command is extremely helpful when a staff member is tracking down stolen items, checking
|
||||||
|
trustlists, helping players with other miscellaneous issues, etc. This command is very powerful and all of
|
||||||
|
our staff members acknowledge this, it will never be used outside of a staff related incident.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">CoreProtect</h3>
|
||||||
|
<p>Coreprotect is used to track and view information on block/item/etc placements and gives us the ability to
|
||||||
|
restore these actions. This is only used to fix and track damage done by stealing/griefing/etc.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/vanish</h3>
|
||||||
|
<p>Vanish is used to check on what players are doing. This allows us to ensure all players are following the
|
||||||
|
rules. We hide ourselves while doing so to avoid disrupting the player experience.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/patrol</h3>
|
||||||
|
<p>This command allows mods to teleport to each player one by one without missing anyone. This helps staff to
|
||||||
|
moderate all players equally and efficiently.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/invsee - /ec</h3>
|
||||||
|
<p>These commands allow the inventory/enderchest of a player to be viewed and edited. This is only used in
|
||||||
|
situations where a player breaks a rule by taking items from someone else and refuses to return them.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/whois - /whowas</h3>
|
||||||
|
<p>Whois is a command that shows a lot of player information, namely IP, UUID, player balance, number of
|
||||||
|
homes, etc. Your IP is kept private and will never be used maliciously, and your UUID is public information.
|
||||||
|
This command will never be used for anything outside of moderating purposes. Whowas is a command that lists
|
||||||
|
all previous usernames of a specific player, as well as your UUID again. This information is public
|
||||||
|
information that can easily be accessed through a number of different websites.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/flag</h3>
|
||||||
|
<p>We use flags as an internal tool to inform other staff members of things a player has done, these are
|
||||||
|
usually in the form of short notes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/alts - /iphist</h3>
|
||||||
|
<p>These commands can be used to show if a player has other players on the same ip as them. This is necessary
|
||||||
|
to make it harder to bypass punishments. Your ip history is kept private and will never be shared outside of
|
||||||
|
staff related incidents.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/spy</h3>
|
||||||
|
<p>All staff usually have /spy on, this allows them to see all conversations in DM's and party chat. This is
|
||||||
|
important to ensure people aren't using our server to harass players without staff being able to see and
|
||||||
|
respond to it.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/cuff</h3>
|
||||||
|
<p>This command is used when a player is being uncooperative or not listening to staff after being instructed
|
||||||
|
to do something. When a player is cuffed, they cannot move, they cannot teleport, etc., but they can still
|
||||||
|
chat or message players. This is mainly used in cases where a player is attempting to interfere with a staff
|
||||||
|
situation (Ex: theft). This command will only be used for moderation purposes.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/afklist</h3>
|
||||||
|
<p>This command lists everyone who has been marked as AFK by our plugin. It also notifies us if a player gets
|
||||||
|
marked as AFK. This is used to check if a player isn't going AFK near farms which might give them an
|
||||||
|
advantage over other players.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/heal - /feed</h3>
|
||||||
|
<p>These commands are only used during moderation to deal with unexpected situations where a player might need
|
||||||
|
to be healed. They are not used by any staff member while playing on the server.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/tp - /tphere - /spawn</h3>
|
||||||
|
<p>These commands are used to get a player away from a situation that requires staff intervention such as
|
||||||
|
attempted player killing or if they are in a place they aren't allowed to be in anymore. Tp can be used
|
||||||
|
while in vanish to check on a player or to easily and quickly get to a player who's asking for help.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSection">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px;">/cmi invlist</h3>
|
||||||
|
<p>This command is used to see all of a player's recent deaths. It allows us to see your death inventory, your
|
||||||
|
levels, the time of your death, location, cause, etc.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
<section class="darkmodeSectionThree">
|
||||||
|
<div class="container">
|
||||||
|
<div class="paragraph">
|
||||||
|
<h3 style="padding-top: 15px; padding-bottom: 0 !important;">Ability to see emails during appeals, contact
|
||||||
|
forms & staff applications</h3>
|
||||||
|
<p style="font-size: 0.75em;">(Head-Staff only)</p>
|
||||||
|
<p>When you send in a ban appeal, a contact form, or a staff application, we will be given your email (if you
|
||||||
|
send in an anonymous contact form we cannot see your email). We will never use your email for anything
|
||||||
|
besides replying. Head-staff are the only staff members who are able to see your email.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</main>
|
||||||
|
</ng-container>
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { ComponentFixture, TestBed } from '@angular/core/testing';
|
||||||
|
|
||||||
|
import { StaffpowersComponent } from './staffpowers.component';
|
||||||
|
|
||||||
|
describe('StaffpowersComponent', () => {
|
||||||
|
let component: StaffpowersComponent;
|
||||||
|
let fixture: ComponentFixture<StaffpowersComponent>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
await TestBed.configureTestingModule({
|
||||||
|
imports: [StaffpowersComponent]
|
||||||
|
})
|
||||||
|
.compileComponents();
|
||||||
|
|
||||||
|
fixture = TestBed.createComponent(StaffpowersComponent);
|
||||||
|
component = fixture.componentInstance;
|
||||||
|
fixture.detectChanges();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create', () => {
|
||||||
|
expect(component).toBeTruthy();
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
import {Component} from '@angular/core';
|
||||||
|
import {HeaderComponent} from "../header/header.component";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-staffpowers',
|
||||||
|
imports: [
|
||||||
|
HeaderComponent
|
||||||
|
],
|
||||||
|
templateUrl: './staffpowers.component.html',
|
||||||
|
styleUrl: './staffpowers.component.scss'
|
||||||
|
})
|
||||||
|
export class StaffpowersComponent {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -44,7 +44,7 @@
|
|||||||
<img [ngSrc]="getAvatarUrl(member)" alt="{{member.name}}'s Minecraft skin"
|
<img [ngSrc]="getAvatarUrl(member)" alt="{{member.name}}'s Minecraft skin"
|
||||||
height="160" width="160" style="width: 160px;">
|
height="160" width="160" style="width: 160px;">
|
||||||
<h2>{{ member.name }}</h2>
|
<h2>{{ member.name }}</h2>
|
||||||
<p>Admin</p>
|
<p>Head Mod</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
@import '@angular/material/prebuilt-themes/azure-blue.css';
|
||||||
|
|
||||||
:root {
|
:root {
|
||||||
--white: #FFFFFF;
|
--white: #FFFFFF;
|
||||||
--black: #282828;
|
--black: #282828;
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ info:
|
|||||||
version: 1.0.0
|
version: 1.0.0
|
||||||
servers:
|
servers:
|
||||||
- url: https://alttd.com/api/v3
|
- url: https://alttd.com/api/v3
|
||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PermissionClaim:
|
||||||
|
$ref: './schemas/permissions/permissions.yml#/components/schemas/PermissionClaim'
|
||||||
tags:
|
tags:
|
||||||
- name: history
|
- name: history
|
||||||
description: Retrieves punishment history
|
description: Retrieves punishment history
|
||||||
@@ -28,6 +32,8 @@ paths:
|
|||||||
$ref: './schemas/bans/bans.yml#/getTotalResultsForUserSearch'
|
$ref: './schemas/bans/bans.yml#/getTotalResultsForUserSearch'
|
||||||
/history/single/{type}/{id}:
|
/history/single/{type}/{id}:
|
||||||
$ref: './schemas/bans/bans.yml#/getHistoryById'
|
$ref: './schemas/bans/bans.yml#/getHistoryById'
|
||||||
|
/history/all/{uuid}:
|
||||||
|
$ref: './schemas/bans/bans.yml#/getAllHistoryForUUID'
|
||||||
/history/total:
|
/history/total:
|
||||||
$ref: './schemas/bans/bans.yml#/getTotalPunishments'
|
$ref: './schemas/bans/bans.yml#/getTotalPunishments'
|
||||||
/appeal/update-mail:
|
/appeal/update-mail:
|
||||||
@@ -36,3 +42,7 @@ paths:
|
|||||||
$ref: './schemas/forms/appeal/appeal.yml#/MinecraftAppeal'
|
$ref: './schemas/forms/appeal/appeal.yml#/MinecraftAppeal'
|
||||||
/appeal/discord-appeal:
|
/appeal/discord-appeal:
|
||||||
$ref: './schemas/forms/appeal/appeal.yml#/DiscordAppeal'
|
$ref: './schemas/forms/appeal/appeal.yml#/DiscordAppeal'
|
||||||
|
/login/requestNewUserLogin/{uuid}:
|
||||||
|
$ref: './schemas/login/login.yml#/RequestNewUserLogin'
|
||||||
|
/login/userLogin/{code}:
|
||||||
|
$ref: './schemas/login/login.yml#/UserLogin'
|
||||||
|
|||||||
@@ -89,7 +89,7 @@ getHistoryForUuid:
|
|||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/UserType'
|
- $ref: '#/components/parameters/UserType'
|
||||||
- $ref: '#/components/parameters/HistoryType'
|
- $ref: '#/components/parameters/HistoryType'
|
||||||
- $ref: '#/components/parameters/Uuid'
|
- $ref: '../generic/parameters.yml#/components/parameters/Uuid'
|
||||||
- $ref: '#/components/parameters/Page'
|
- $ref: '#/components/parameters/Page'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
@@ -158,7 +158,7 @@ getTotalResultsForUuidSearch:
|
|||||||
parameters:
|
parameters:
|
||||||
- $ref: '#/components/parameters/UserType'
|
- $ref: '#/components/parameters/UserType'
|
||||||
- $ref: '#/components/parameters/HistoryType'
|
- $ref: '#/components/parameters/HistoryType'
|
||||||
- $ref: '#/components/parameters/Uuid'
|
- $ref: '../generic/parameters.yml#/components/parameters/Uuid'
|
||||||
responses:
|
responses:
|
||||||
'200':
|
'200':
|
||||||
description: Successful operation
|
description: Successful operation
|
||||||
@@ -195,6 +195,22 @@ getHistoryById:
|
|||||||
application/json:
|
application/json:
|
||||||
schema:
|
schema:
|
||||||
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
||||||
|
getAllHistoryForUUID:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- history
|
||||||
|
summary: Gets all history for specified UUID
|
||||||
|
description: Retrieves all history for specified UUID
|
||||||
|
operationId: getAllHistoryForUUID
|
||||||
|
parameters:
|
||||||
|
- $ref: '../generic/parameters.yml#/components/parameters/Uuid'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Successful operation
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '#/components/schemas/PunishmentHistoryList'
|
||||||
components:
|
components:
|
||||||
parameters:
|
parameters:
|
||||||
HistoryType:
|
HistoryType:
|
||||||
@@ -212,13 +228,6 @@ components:
|
|||||||
schema:
|
schema:
|
||||||
type: string
|
type: string
|
||||||
description: The (partial) username to search for
|
description: The (partial) username to search for
|
||||||
Uuid:
|
|
||||||
name: uuid
|
|
||||||
in: path
|
|
||||||
required: true
|
|
||||||
schema:
|
|
||||||
type: string
|
|
||||||
description: The uuid of the desired user
|
|
||||||
UserType:
|
UserType:
|
||||||
name: userType
|
name: userType
|
||||||
in: path
|
in: path
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
Uuid:
|
||||||
|
name: uuid
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The uuid of the desired user
|
||||||
@@ -0,0 +1,91 @@
|
|||||||
|
UserLogin:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- login
|
||||||
|
summary: Log in to the site
|
||||||
|
description: Log in to the site through a code from the server
|
||||||
|
operationId: login
|
||||||
|
parameters:
|
||||||
|
- $ref: '#/components/parameters/Code'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Logged in
|
||||||
|
content:
|
||||||
|
application/text:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: A JWT token for this user
|
||||||
|
'401':
|
||||||
|
description: Login failed - Invalid credentials
|
||||||
|
content:
|
||||||
|
application/text:
|
||||||
|
schema:
|
||||||
|
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
||||||
|
default:
|
||||||
|
description: Unexpected error
|
||||||
|
content:
|
||||||
|
application/text:
|
||||||
|
schema:
|
||||||
|
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
||||||
|
RequestNewUserLogin:
|
||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- login
|
||||||
|
summary: Request a login
|
||||||
|
description: Request a code, that can be used to log in
|
||||||
|
operationId: requestLogin
|
||||||
|
parameters:
|
||||||
|
- name: Authorization
|
||||||
|
in: header
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: Secret
|
||||||
|
- $ref: '../generic/parameters.yml#/components/parameters/Uuid'
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Success
|
||||||
|
content:
|
||||||
|
application/text:
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: code to log in with
|
||||||
|
'401':
|
||||||
|
description: Login failed - Invalid secret
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
||||||
|
default:
|
||||||
|
description: Unexpected error
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
$ref: '../generic/errors.yml#/components/schemas/ApiError'
|
||||||
|
components:
|
||||||
|
parameters:
|
||||||
|
Code:
|
||||||
|
name: code
|
||||||
|
in: path
|
||||||
|
required: true
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
description: The code to log in with
|
||||||
|
schemas:
|
||||||
|
LoginData:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- loginCode
|
||||||
|
properties:
|
||||||
|
loginCode:
|
||||||
|
type: string
|
||||||
|
description: The code to log in
|
||||||
|
AddLogin:
|
||||||
|
type: object
|
||||||
|
required:
|
||||||
|
- uuid
|
||||||
|
properties:
|
||||||
|
uuid:
|
||||||
|
type: string
|
||||||
|
format: uuid
|
||||||
|
description: UUID of the user that will get logged in
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
components:
|
||||||
|
schemas:
|
||||||
|
PermissionClaim:
|
||||||
|
type: string
|
||||||
|
enum: [ SCOPE_user, SCOPE_head_mod ]
|
||||||
|
description: Permission claims used for authorization
|
||||||
|
x-enum-varnames: [ USER, HEAD_MOD ]
|
||||||
|
x-enum-descriptions: [ "User permission", "Head moderator permission" ]
|
||||||
Reference in New Issue
Block a user