+++ /dev/null
-#!/usr/bin/env sh
-
-screen -dmS starshipfights gradle runShadow
navyName = "Mechyrdian Star Fleet",
polityName = "Empire of Mechyrdia",
demonymSingular = "Mechyrdian",
- currencyName = "throne",
+ currencyName = "thrones",
shipPrefix = "CMS ", // Ciarstuos Mehurdiasi Štelnau
blurbDesc = {
p {
navyName = "Masra Draetsen Khoy'qan",
polityName = "Diadochus Masra Draetsen",
demonymSingular = "Diadochi",
- currencyName = "sylaph",
+ currencyName = "sylaphs",
shipPrefix = "", // The Diadochi don't use ship prefixes
blurbDesc = {
p {
navyName = "Isarnareyksk Styurnamariyn",
polityName = "Isarnareyksk Federation",
demonymSingular = "Isarnareyksk",
- currencyName = "mark",
- shipPrefix = "ISMS ", // Isarnareyksk StyurnaMariyn nu Skyf
+ currencyName = "marks",
+ shipPrefix = "ISS ", // Isarnareyksk Styurnamariyn nu Skyf
blurbDesc = {
p {
+"The Isarnareyksk Federation is the largest and most populous successor state to the Fulkreyksk Authoritariat. A shadow of its former glory, Isarnareykk is led by Faurasitand Demeter Ursalia and ruled by dissenting factions such as the tech barons and the revanchist military, that hate each other more than they hate Ursalia."
navyName = "Imperial States Space Force",
polityName = "Imperial States of America",
demonymSingular = "American",
- currencyName = "dollar",
- shipPrefix = "ISFC ", // Imperial Space Force Craft
+ currencyName = "dollars",
+ shipPrefix = "ASC ", // American Space Craft
blurbDesc = {
p {
+"The Imperial States of America was once the political hyperpower of Earth and beyond, and the ideological bulwark of the Caesarism of its time. They were strong, they were proud... they were hated. Hated to the point that entire nations fled from Earth and colonized the stars just to escape American hegemony."
val admiralId = call.parameters["id"]?.let { Id<Admiral>(it) }!!
val admiral = Admiral.get(admiralId)!!
- if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
val newAdmiral = admiral.copy(
name = form["name"]?.takeIf { it.isNotBlank() } ?: admiral.name,
admiral.await() to ship.await()
}
- if (admiral.owningUser != currentUser) throw ForbiddenException()
- if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
+ if (ship.owningAdmiral != admiralId) forbid()
val newName = formParams["name"]?.takeIf { it.isNotBlank() && it.length <= SHIP_NAME_MAX_LENGTH } ?: redirect("/admiral/${admiralId}/manage")
ShipInDrydock.set(shipId, setValue(ShipInDrydock::name, newName))
admiral.await() to ship.await()
}
- if (admiral.owningUser != currentUser) throw ForbiddenException()
- if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
+ if (ship.owningAdmiral != admiralId) forbid()
if (ship.status != DrydockStatus.Ready) redirect("/admiral/${admiralId}/manage")
if (ship.shipType.weightClass.isUnique) redirect("/admiral/${admiralId}/manage")
val admiralId = call.parameters["id"]?.let { Id<Admiral>(it) }!!
val admiral = Admiral.get(admiralId)!!
- if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
val shipType = call.parameters["ship"]?.let { param -> ShipType.values().singleOrNull { it.toUrlSlug() == param } }!!
val admiralId = call.parameters["id"]?.let { Id<Admiral>(it) }!!
val admiral = Admiral.get(admiralId)!!
- if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
coroutineScope {
launch { Admiral.del(admiralId) }
}
get("/login/discord/callback") {
- val userAgent = call.request.userAgent() ?: throw ForbiddenException()
+ val userAgent = call.request.userAgent() ?: forbid()
val principal: OAuthAccessTokenResponse.OAuth2 = call.principal() ?: redirect("/login")
val userInfoJson = httpClient.get<String>("https://discord.com/api/users/@me") {
headers {
import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
+import starshipfights.rateLimit
import kotlin.math.roundToLong
class RateLimit(
val jsonBody = context.response.receive<String>()
val rateLimitedResponse = feature.jsonCodec.decodeFromString(RateLimitedResponse.serializer(), jsonBody)
feature.resetAfter = rateLimitedResponse.retryAfter
+
+ rateLimit()
} else {
context.response.headers[feature.remainingHeader]?.toIntOrNull()?.let {
feature.remainingRequests = it
import io.ktor.request.*
import io.ktor.sessions.*
import io.ktor.util.*
-import starshipfights.ForbiddenException
+import starshipfights.forbid
import starshipfights.data.Id
import starshipfights.data.auth.User
import starshipfights.data.auth.UserSession
if (CsrfProtector.verifyNonce(csrfToken, sessionId, request.uri))
return formInput
else
- throw ForbiddenException()
+ forbid()
}
private val caliboreseVowels = "aeiouy".toSet()
private fun randomCaliboreseName(isFemale: Boolean) = caliboreseNames.filter {
- it.length < 8 && (if (isFemale) it.last() in caliboreseVowels else it.last() !in caliboreseVowels)
+ it.length < 8 && (isFemale == (it.last() in caliboreseVowels))
}.random() + " " + caliboreseNames.filter { it.length > 7 }.random()
private val diadochiMaleNames = listOf(
"Murder",
"Gore",
"Daemon",
- "Talon"
+ "Talon",
)
private fun randomDiadochiName(isFemale: Boolean) = (if (isFemale) diadochiFemaleNames else diadochiMaleNames).random() + " " + diadochiEpithetParts.random() + diadochiEpithetParts.random().lowercase()
private val thedishMaleNames = listOf(
- "Prethoris",
+ "Praethoris",
"Severus",
"Augast",
"Dagobar",
"Toval",
"Ivon",
"Belis",
- "Jorh"
+ "Jorh",
+ "Svar",
+ "Alaric",
)
private val thedishFemaleNames = listOf(
"Amberli",
"Alysia",
"Lenera",
+ "Demeter",
)
private val thedishSurnames = listOf(
"Arvi",
"Galvus",
"Voss",
- "Mandanof"
+ "Mandanof",
+ "Ursali",
+ "Vytunn",
+ "Quesrinn",
)
private fun randomThedishName(isFemale: Boolean) = (if (isFemale) thedishFemaleNames else thedishMaleNames).random() + " " + thedishSurnames.random()
"Catonsville",
"Ocean City",
"Philadelphia",
+ "Somerset",
"Pittsburgh",
"Las Vegas",
override fun ASIDE.render() {
p {
style = "text-align:center;border:2px solid #62a;padding:3px;background-color:#93f;color:#315;font-variant:small-caps;font-family:'Orbitron',sans-serif"
- title = "This person helps with coding the site"
+ title = "This person helps with coding the game"
+"Site Developer"
}
}
get() = 3
}
-fun User.getTrophies(): List<UserTrophy> =
+fun User.getTrophiesUnsorted(): Set<UserTrophy> =
(if (discordId == CurrentConfiguration.discordClient?.ownerId)
- listOf(SiteOwnerTrophy)
- else emptyList()) + (if (amountDonatedInUsCents > 0)
- listOf(SiteSupporterTrophy(amountDonatedInUsCents))
- else emptyList())
+ setOf(SiteOwnerTrophy)
+ else emptySet()) + (if (amountDonatedInUsCents > 0)
+ setOf(SiteSupporterTrophy(amountDonatedInUsCents))
+ else emptySet())
+
+fun User.getTrophies(): List<UserTrophy> = getTrophiesUnsorted().sorted()
devModeCallId(callId)
}
+suspend fun ApplicationCall.error429(): HTML.() -> Unit = page("Too Many Requests", standardNavBar()) {
+ section {
+ h1 { +"Too Many Requests" }
+ p { +"Our server is being bogged down in a quagmire of HTTP requests. Please try again later." }
+ }
+ devModeCallId(callId)
+}
+
suspend fun ApplicationCall.error503(): HTML.() -> Unit = page("Internal Error", standardNavBar()) {
section {
h1 { +"Internal Error" }
import org.litote.kmongo.eq
import org.litote.kmongo.gt
import org.litote.kmongo.or
-import starshipfights.ForbiddenException
+import starshipfights.forbid
import starshipfights.auth.*
import starshipfights.data.Id
import starshipfights.data.admiralty.*
val admiralId = parameters["id"]?.let { Id<Admiral>(it) }!!
val admiral = Admiral.get(admiralId)!!
- if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
val ownedShips = ShipInDrydock.filter(ShipInDrydock::owningAdmiral eq admiralId).toList()
section {
h2 { +"Manage Fleet" }
p {
- +"${admiral.fullName} currently owns ${admiral.money} ${admiral.faction.currencyName}s, and earns ${admiral.rank.dailyWage} ${admiral.faction.currencyName}s every day."
+ +"${admiral.fullName} currently owns ${admiral.money} ${admiral.faction.currencyName}, and earns ${admiral.rank.dailyWage} ${admiral.faction.currencyName}s every day."
}
table {
tr {
+ship.shipType.weightClass.sellPrice.toString()
+" "
+admiral.faction.currencyName
- +"s"
if (ship.status == DrydockStatus.Ready && !ship.shipType.weightClass.isUnique) {
br
a(href = "/admiral/${admiralId}/sell/${ship.id}") { +"Sell" }
+st.weightClass.buyPrice.toString()
+" "
+admiral.faction.currencyName
- +"s"
br
a(href = "/admiral/${admiralId}/buy/${st.toUrlSlug()}") { +"Buy" }
}
admiral.await() to ship.await()
}
- if (admiral.owningUser != currentUser) throw ForbiddenException()
- if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
+ if (ship.owningAdmiral != admiralId) forbid()
return page("Renaming Ship", null, null) {
section {
admiral.await() to ship.await()
}
- if (admiral.owningUser != currentUser) throw ForbiddenException()
- if (ship.owningAdmiral != admiralId) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
+ if (ship.owningAdmiral != admiralId) forbid()
if (ship.status != DrydockStatus.Ready) redirect("/admiral/${admiralId}/manage")
if (ship.shipType.weightClass.isUnique) redirect("/admiral/${admiralId}/manage")
section {
h1 { +"Are You Sure?" }
p {
- +"${admiral.fullName} is about to sell the ${ship.shipType.fullDisplayName} ${ship.shipData.fullName} for ${ship.shipType.weightClass.sellPrice} ${admiral.faction.currencyName}s."
+ +"${admiral.fullName} is about to sell the ${ship.shipType.fullDisplayName} ${ship.shipData.fullName} for ${ship.shipType.weightClass.sellPrice} ${admiral.faction.currencyName}."
}
form(method = FormMethod.get, action = "/admiral/${admiral.id}/manage") {
submitInput {
val admiralId = parameters["id"]?.let { Id<Admiral>(it) }!!
val admiral = Admiral.get(admiralId)!!
- if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
val shipType = parameters["ship"]?.let { param -> ShipType.values().singleOrNull { it.toUrlSlug() == param } }!!
section {
h1 { +"Too Expensive" }
p {
- +"Unfortunately, the ${shipType.fullDisplayName} is out of ${admiral.fullName}'s budget. It costs ${shipType.weightClass.buyPrice} ${admiral.faction.currencyName}s, and ${admiral.name} only has ${admiral.money} ${admiral.faction.currencyName}s."
+ +"Unfortunately, the ${shipType.fullDisplayName} is out of ${admiral.fullName}'s budget. It costs ${shipType.weightClass.buyPrice} ${admiral.faction.currencyName}, and ${admiral.name} only has ${admiral.money} ${admiral.faction.currencyName}s."
}
form(method = FormMethod.get, action = "/admiral/${admiral.id}/manage") {
submitInput {
section {
h1 { +"Are You Sure?" }
p {
- +"${admiral.fullName} is about to buy a ${shipType.fullDisplayName} for ${shipType.weightClass.buyPrice} ${admiral.faction.currencyName}s."
+ +"${admiral.fullName} is about to buy a ${shipType.fullDisplayName} for ${shipType.weightClass.buyPrice} ${admiral.faction.currencyName}."
}
form(method = FormMethod.get, action = "/admiral/${admiral.id}/manage") {
submitInput {
val admiralId = parameters["id"]?.let { Id<Admiral>(it) }!!
val admiral = Admiral.get(admiralId)!!
- if (admiral.owningUser != currentUser) throw ForbiddenException()
+ if (admiral.owningUser != currentUser) forbid()
return page(
"Are You Sure?", null, null
exception<NullPointerException> {
call.respondHtml(HttpStatusCode.NotFound, call.error404())
}
+ exception<RateLimitException> {
+ call.respondHtml(HttpStatusCode.TooManyRequests, call.error429())
+ }
+
exception<Throwable> {
call.respondHtml(HttpStatusCode.InternalServerError, call.error503())
throw it
import org.slf4j.LoggerFactory
class ForbiddenException : IllegalArgumentException()
+fun forbid(): Nothing = throw ForbiddenException()
data class HttpRedirectException(val url: String, val permanent: Boolean) : RuntimeException()
fun redirect(url: String, permanent: Boolean = false): Nothing = throw HttpRedirectException(url, permanent)
+class RateLimitException : RuntimeException()
+fun rateLimit(): Nothing = throw RateLimitException()
+
val sfLogger: Logger = LoggerFactory.getLogger("StarshipFights")
screen -S starshipfights -X quit
git pull
-gradle shadowJar
+gradle clean shadowJar
screen -dmS starshipfights gradle runShadow