2022-10-20 03:18:35 +02:00
/ *
* The Peacock Project - a HITMAN server replacement .
* Copyright ( C ) 2021 - 2022 The Peacock Project Team
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https : / / www.gnu.org / licenses / > .
* /
import { decode } from "jsonwebtoken"
import type { NextFunction , Response } from "express"
import type {
MissionManifestObjective ,
RepositoryId ,
RequestWithJwt ,
ServerVersion ,
Unlockable ,
UserProfile ,
} from "./types/types"
import axios , { AxiosError } from "axios"
import { log , LogLevel } from "./loggingInterop"
import { writeFileSync } from "fs"
import { getFlag } from "./flags"
/ * *
* True if the server is being run by the launcher , false otherwise .
* /
export const IS_LAUNCHER = process . env . IS_PEACOCK_LAUNCHER === "true"
export const ServerVer : ServerVersion = {
_Major : 8 ,
_Minor : 10 ,
_Build : 0 ,
_Revision : 0 ,
}
export const PEACOCKVER = REV_IDENT
export const PEACOCKVERSTRING = HUMAN_VERSION
export const uuidRegex =
/^[a-zA-Z0-9]{8}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{4}-[a-zA-Z0-9]{12}$/
export async function checkForUpdates ( ) : Promise < void > {
if ( getFlag ( "updateChecking" ) === false ) {
return
}
try {
const res = await axios (
"https://backend.rdil.rocks/peacock/latest-version" ,
)
const current = res . data
if ( current === PEACOCKVER ) {
log ( LogLevel . DEBUG , "Peacock is up to date." )
} else {
log (
LogLevel . WARN ,
` Peacock is out-of-date! Check the Discord for the latest release. ` ,
)
}
} catch ( e ) {
log ( LogLevel . WARN , "Failed to check for updates!" )
}
}
/ * *
* Middleware that validates the authentication token sent by the client .
*
* @param req The express request object .
* @param res The express response object .
* @param next The express next function .
* /
export function extractToken (
req : RequestWithJwt ,
res? : Response ,
next? : NextFunction ,
) : void {
const header = req . header ( "Authorization" )
const auth = header ? header . split ( " " ) : [ ]
if ( auth . length === 2 && auth [ 0 ] . toLowerCase ( ) === "bearer" ) {
// @ts-expect-error Type overlap
req . jwt = < string > decode ( auth [ 1 ] )
if ( ! uuidRegex . test ( req . jwt . unique_name ) ) {
res ? . status ( 424 ) . send ( "profile appears to be corrupted" )
return
}
next ? . ( )
return
}
req . shouldCease = true
next ? . ( "router" )
}
export function xpRequiredForLevel ( level : number ) : number {
return level * 6000 - 6000
}
export function castUserProfile ( profile : UserProfile ) : UserProfile {
const j = fastClone ( profile )
if ( ! j . Extensions || Object . keys ( j . Extensions ) . length === 0 ) {
log ( LogLevel . DEBUG , "No extensions - skipping validation" )
return j
}
let dirty = false
for ( const item of [
"entP" ,
"PeacockEscalations" ,
"PeacockFavoriteContracts" ,
"PeacockCompletedEscalations" ,
] ) {
if ( ! Object . prototype . hasOwnProperty . call ( j . Extensions , item ) ) {
log ( LogLevel . DEBUG , ` Err missing property ${ item } ` )
log (
LogLevel . WARN ,
` A requested user profile is missing some vital data. ` ,
)
log (
LogLevel . WARN ,
` Attempting to repair the profile automatically... ` ,
)
if ( item === "entP" ) {
log (
LogLevel . ERROR ,
"Can't repair this issue, please let us know in the Discord!" ,
)
process . exit ( 1 )
}
if ( item === "PeacockEscalations" ) {
j . Extensions . PeacockEscalations = { }
}
if ( item === "PeacockCompletedEscalations" ) {
j . Extensions . PeacockCompletedEscalations = [ ]
}
if ( item === "PeacockFavoriteContracts" ) {
j . Extensions . PeacockFavoriteContracts = [ ]
}
dirty = true
}
}
if ( dirty ) {
writeFileSync ( ` userdata/users/ ${ j . Id } .json ` , JSON . stringify ( j ) )
log ( LogLevel . INFO , "Profile successfully repaired!" )
}
return j
}
export function getDefaultSuitFor ( location : string ) {
switch ( location ) {
case "LOCATION_PARENT_ICA_FACILITY" :
return "TOKEN_OUTFIT_GREENLAND_HERO_TRAININGSUIT"
case "LOCATION_PARENT_PARIS" :
return "TOKEN_OUTFIT_PARIS_HERO_PARISSUIT"
case "LOCATION_PARENT_COASTALTOWN" :
return "TOKEN_OUTFIT_SAPIENZA_HERO_SAPIENZASUIT"
case "LOCATION_PARENT_MARRAKECH" :
return "TOKEN_OUTFIT_MARRAKESH_HERO_MARRAKESHSUIT"
case "LOCATION_PARENT_BANGKOK" :
return "TOKEN_OUTFIT_BANGKOK_HERO_BANGKOKSUIT"
case "LOCATION_PARENT_COLORADO" :
return "TOKEN_OUTFIT_COLORADO_HERO_COLORADOSUIT"
case "LOCATION_PARENT_HOKKAIDO" :
return "TOKEN_OUTFIT_HOKKAIDO_HERO_HOKKAIDOSUIT"
default :
return "TOKEN_OUTFIT_HITMANSUIT"
}
}
export const nilUuid = "00000000-0000-0000-0000-000000000000"
export function isObjectiveActive (
objective : MissionManifestObjective ,
doneObjectives : Set < RepositoryId > ,
) : boolean {
if ( objective . Activation !== undefined ) {
if ( Array . isArray ( objective . Activation . $eq ) ) {
return (
objective . Activation . $eq [ 1 ] === "Completed" &&
doneObjectives . has (
( < string > objective . Activation . $eq [ 0 ] ) . substring ( 1 ) ,
)
)
}
}
return false
}
export const jokes = [
"STILL... we are close now, 47." ,
"James Batty, the plaintiff, wants Janus to stop his annual landing of a helicopter, near the local creek." ,
"Searching for local masons in your area..." ,
"I wonder what kind of curry currymaker makes" ,
"Listening for uncommon guard voice lines..." ,
"Mistah Jason Portman, please come to the hospital entrance. A doctor will escort you to your checkup. That was for: mistah Jason Portman." ,
"Nakamura-San, please come to the operating theatre. Stat." ,
"Ugh, my knee is so sore." ,
"Mister Prichard I presume? Hi, I'm Chen Ting. Pleasure to meet you." ,
"Find your inner... zen" ,
"I love those little drones when they scoot around like that." ,
"Welcome, welcome! Hello! Good to see you. So lovely to see so many familiar faces here today." ,
"hi so i own hitman 1 and 2 i got hitman 1 and 2 on disc hitman 1 def edition and hitman 2 gold disc both on ps4 and when i got hitman 2 i downloaded the best legesy pack and all of my saves are on hitman 2 but i got rid of that game will i need both of them to get all the levels onto hitman 3 and im staying on ps4" ,
"The weather is always nice in Paris." ,
"Have you seen a girl around? Short hair, with a bright green bag?" ,
"The show is just about to start." ,
"Entering the Ether lab requires a uniform and a keycard... luckily, it seems both are within reach." ,
"Welcome to Bangkok, 47. The target has been in residence at the hotel for almost a month and security is tight, with several other high-profile guests also at the hotel. Good Hunting." ,
]
/ * *
* The current game difficulty .
* /
export const gameDifficulty = {
/ * *
* The game isn ' t on a set difficulty ( missions with no difficulties data ? ) .
* /
unset : 0 ,
/ * *
* Casual mode .
* /
casual : 1 ,
/ * *
* Professional ( normal ) mode .
* /
normal : 2 ,
/ * *
* Master mode .
* /
master : 4 ,
}
export function difficultyToString ( difficulty : number ) : string {
switch ( difficulty ) {
case 1 :
return "casual"
case 2 :
return "normal"
case 4 :
return "master"
case 0 :
default :
return "unset"
}
}
/ * *
* Handle an { @link AxiosError } from axios .
*
* @see https : //axios-http.com/docs/handling_errors
* @param error The error from axios .
* /
export function handleAxiosError ( error : AxiosError ) : void {
if ( error . response ) {
// The request was made and the server responded with a status code
// that falls out of the range of 2xx
log ( LogLevel . DEBUG , ` code ${ error . response . status } ` )
} else if ( error . request ) {
// The request was made but no response was received
// `error.request` is an instance of http.ClientRequest
log ( LogLevel . DEBUG , ` bad fetch ` )
} else {
// Something happened in setting up the request that triggered an Error
log ( LogLevel . DEBUG , ` generic ` )
}
}
export function unlockorderComparer ( a : Unlockable , b : Unlockable ) : number {
return (
( a ? . Properties ? . UnlockOrder ? ? Number . POSITIVE_INFINITY ) -
( b ? . Properties ? . UnlockOrder ? ? Number . POSITIVE_INFINITY ) || 0
)
}
/ * *
* Converts a contract ' s public ID as a long - form number into the version with dashes .
*
* @param publicId The ID without dashes .
* @returns The ID with dashes .
* /
export function addDashesToPublicId ( publicId : string ) : string {
if ( publicId . includes ( "-" ) ) {
// this work is already done
return publicId
}
let id = publicId
id = id . slice ( 0 , 1 ) + "-" + id . slice ( 1 )
id = id . slice ( 0 , 4 ) + "-" + id . slice ( 4 )
id = id . slice ( 0 , 12 ) + "-" + id . slice ( 12 )
return id
}
/ * *
* A fast implementation of deep object cloning .
*
* @param item The item to clone .
* @returns The new item .
* /
export function fastClone < T > ( item : T ) : T {
// null, undefined values check
if ( ! item ) {
return item
}
const types = [ Number , String , Boolean ]
let result
// normalizing primitives if someone did new String("aaa"), or new Number("444")
for ( const type of types ) {
if ( item instanceof type ) {
result = type ( item )
}
}
if ( typeof result === "undefined" ) {
if ( Object . prototype . toString . call ( item ) === "[object Array]" ) {
result = [ ]
// Ugly type casting.
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const itemAsArray : Array < typeof item > = item as any
itemAsArray . forEach ( ( child , index ) = > {
result [ index ] = fastClone ( child )
} )
} else if ( typeof item === "object" ) {
2022-10-23 03:30:36 +02:00
// this is a literal
if ( item instanceof Date ) {
result = new Date ( item )
2022-10-20 03:18:35 +02:00
} else {
2022-10-23 03:30:36 +02:00
// object literal
result = { }
for ( const i in item ) {
result [ i ] = fastClone ( item [ i ] )
}
2022-10-20 03:18:35 +02:00
}
} else {
result = item
}
}
return result
}