~starkingdoms/starkingdoms

46218c06a96bcafb08cd18349edc8a5b7e451604 — ghostlyzsh 1 year, 4 months ago f09a962
calculator parser kabel
M Cargo.lock => Cargo.lock +4 -0
@@ 2463,6 2463,10 @@ dependencies = [
]

[[package]]
name = "kabel"
version = "0.1.0"

[[package]]
name = "khronos-egl"
version = "6.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"

M kabel/grammar.ebnf => kabel/grammar.ebnf +5 -4
@@ 12,16 12,17 @@ expression = assignment | declaration ;

declaration = "var" , identifier , "=" , expression ;

assignment = { identifier , "=" , } logical_and ;
assignment = { identifier , "=" , } logical_or ;

logical_and = logical_or { , "||" , logical_or } ;
logical_or = logical_and { , "||" , logical_and } ;

logical_or = equality { , "&&" , equality } ;
logical_and = equality { , "&&" , equality } ;

equality = comparison { , ( "==" | "!=" ) , comparison } ;

comparison = term { , ( ">" | "<" | ">=" | "<=" ) , term } ;

(* implemented *)
term = factor { , ( "+" | "-" ) , factor } ;

factor = primary { , ( "*" | "/" ) , primary } ;


@@ 31,5 32,5 @@ primary = identifier | number | string | group ;
group = "(" , expression , ")" ;

identifier = alphabetic , { alphabetic | digit } ;
string = ( '"' | "'" ) , { character - ( '"' | "'" ) } , ( '"' | "'" ) ;
string = '"' , { character - '"' } , '"' ;
number = digit , { digit } , [ "." , digit , { digit } ] ;

A kabel/src/error.rs => kabel/src/error.rs +68 -0
@@ 0,0 1,68 @@
#[derive(Debug, Clone)]
pub struct KabelError {
    pub kind: ErrorKind,
    pub message: String,
    pub line: usize,
    pub column: usize,
    pub code: String,
}

impl KabelError {
    pub fn new(kind: ErrorKind, message: String, line: usize,
                column: usize, code: String) -> Self {
        Self {
            kind,
            message,
            line,
            column,
            code,
        }
    }
}

impl std::fmt::Display for KabelError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let caret_space: String = vec![' '; self.column-1].iter().collect();
        f.write_str(&
            format!("Error {:0>4}: {1} at line {2}, column {3}\n\
                    {4}\n\
                    {5}^",
                    self.kind.clone() as usize, self.message, self.line+1, self.column,
                    self.code, caret_space))
    }
}

impl std::error::Error for KabelError {}


#[derive(Debug, Clone)]
pub enum ErrorKind {
    UnexpectedEof,
    UnexpectedCharacter,
    UnexpectedToken,
    MissingDelimiter,
}

impl std::fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use ErrorKind::*;
        match self {
            UnexpectedEof => f.write_str("Unexpected End of File"),
            UnexpectedCharacter => f.write_str("Unrecognized Charcter"),
            UnexpectedToken => f.write_str("Unrecognized Token"),
            MissingDelimiter => f.write_str("Missing delimiter"),
        }
    }
}

impl From<ErrorKind> for usize {
    fn from(value: ErrorKind) -> Self {
        use ErrorKind::*;
        match value {
            UnexpectedEof => 0x00,
            UnexpectedCharacter => 0x01,
            UnexpectedToken => 0x02,
            MissingDelimiter => 0x03,
        }
    }
}

A kabel/src/lexer.rs => kabel/src/lexer.rs +133 -0
@@ 0,0 1,133 @@
use std::str::from_utf8;

use crate::{error::{ErrorKind, KabelError}, token};

pub struct Lexer {
    input: Vec<u8>,
    start: usize,
    current: usize,
    line: usize,
    line_start: usize,
    column: usize,
    c: u8,
    pub errors: Vec<KabelError>,
    pub output: Vec<Token>,
}

impl Lexer {
    pub fn new(input: String) -> Self {
        Self {
            input: input.as_bytes().to_vec(),
            start: 0,
            current: 0,
            line: 0,
            line_start: 0,
            column: 0,
            c: 0x00,
            errors: Vec::new(),
            output: Vec::new(),
        }
    }

    pub fn next_token(&mut self) -> bool {
        self.read_char();
        match self.c {
            b'+' => { self.output.push(token!(self, TokenType::Plus)); self.start = self.current; },
            b'-' => { self.output.push(token!(self, TokenType::Minus)); self.start = self.current; },
            b'*' => { self.output.push(token!(self, TokenType::Star)); self.start = self.current; },
            b'/' => { self.output.push(token!(self, TokenType::Slash)); self.start = self.current; },
            b'(' => { self.output.push(token!(self, TokenType::LeftParen)); self.start = self.current; },
            b')' => { self.output.push(token!(self, TokenType::RightParen)); self.start = self.current; },
            b'"' => {
                let mut contents = String::new();
                while self.read_char() != b'"' {
                    if self.c == 0x05 {
                        self.errors.push(KabelError::new(ErrorKind::UnexpectedEof,
                                "File ended before closing quote".to_string(),
                                self.line, self.column, from_utf8(&self.input[self.start..self.current]).unwrap().to_string()));
                        return false;
                    }
                    contents.push(self.c as char);
                }
                self.start = self.current;
                self.output.push(token!(self, TokenType::Str(contents)));
            }
            b'\n' => {
                self.line += 1;
                self.line_start = self.current;
                self.column = 0;
            }
            b' ' | b'\r' | b'\t' => { self.start = self.current; }
            0x05 => return false,
            c => {
                if c.is_ascii_alphabetic() {
                    let mut content = (c as char).to_string();
                    while self.peek().is_ascii_alphanumeric() || self.c == b'_' {
                        content.push(self.c as char);
                        self.read_char();
                    }
                    self.output.push(token!(self, TokenType::Ident(content)));
                } else if c.is_ascii_digit() {
                    let mut number = (c as char).to_string();
                    while self.peek().is_ascii_digit() {
                        number.push(self.c as char);
                        self.read_char();
                    }
                    if self.c == b'.' {
                        number.push('.');
                        while self.read_char().is_ascii_digit() {
                            number.push(self.c as char);
                        }
                    }
                    /*self.current -= 1;
                    self.column -= 1;*/
                    // panic = error in this code
                    self.output.push(token!(self, TokenType::Num(number.parse().unwrap())));
                    self.start = self.current;
                } else {
                    self.errors.push(KabelError::new(ErrorKind::UnexpectedToken,
                            format!("Stray \"{0}\"", c as char),
                            self.line, self.column,
                            from_utf8(&self.input[self.line_start..self.current]).unwrap().to_string()));
                }
            }
        }
        true
    }

    pub fn read_char(&mut self) -> u8 {
        if self.current >= self.input.len() {
            self.c = 0x05; // EOF
            return self.c;
        }
        self.c = self.input[self.current];
        self.current += 1;
        self.column += 1;
        return self.c;
    }
    pub fn peek(&mut self) -> u8 {
        if self.current >= self.input.len() {
            self.c = 0x05; // EOF
            return self.c;
        }
        self.c = self.input[self.current];
        return self.c;
    }
}

#[derive(Debug, Clone)]
pub struct Token {
    pub token_type: TokenType,
    pub column: usize,
    pub line: usize,
    pub line_start: usize,
    pub start: usize,
    pub end: usize,
}

#[derive(Debug, Clone, PartialEq)]
pub enum TokenType {
    Star, Slash, Plus, Minus, LeftParen, RightParen,

    Ident(String), Str(String), Num(f32),
}

A kabel/src/lib.rs => kabel/src/lib.rs +18 -0
@@ 0,0 1,18 @@
use lexer::{Lexer, Token};
use parser::{Parser, AST};

pub mod lexer;
pub mod parser;
pub mod macros;
pub mod error;

pub fn run_lexer(input: String) -> Lexer {
    let mut lexer = Lexer::new(input);
    while lexer.next_token() {}
    lexer
}

pub fn run_parser(text:String, input: Vec<Token>) -> (AST, Parser) {
    let mut parser = Parser::new(text, input);
    (parser.program(), parser)
}

A kabel/src/macros.rs => kabel/src/macros.rs +13 -0
@@ 0,0 1,13 @@
#[macro_export]
macro_rules! token {
    ($self:expr, $token:expr) => {
        crate::lexer::Token {
            line: $self.line,
            line_start: $self.line_start,
            column: $self.column,
            start: $self.start,
            end: $self.current,
            token_type: $token,
        }
    };
}

D kabel/src/main.rs => kabel/src/main.rs +0 -3
@@ 1,3 0,0 @@
fn main() {
    println!("Hello, world!");
}

A kabel/src/parser.rs => kabel/src/parser.rs +226 -0
@@ 0,0 1,226 @@
use crate::{error::{ErrorKind, KabelError}, lexer::{Token, TokenType}};

pub struct Parser {
    input: Vec<Token>,
    text: String,
    //start: usize,
    current: usize,
    token: Token,
    pub errors: Vec<KabelError>,
}

impl Parser {
    pub fn new(text: String, input: Vec<Token>) -> Self {
        Self {
            input: input.clone(),
            text,
            //start: 0,
            current: 0,
            token: input[0].clone(),
            errors: Vec::new(),
        }
    }

    pub fn program(&mut self) -> AST {
        let mut program = Vec::new();
        loop {
            if self.current >= self.input.len() {
                break;
            }
            match self.expression() {
                Ok(ast) => program.push(ast),
                Err(e) => self.errors.push(e),
            }
            break;
        }
        AST {
            ast_type: ASTType::Program(program),
            start: 0,
            end: 0,
            line: 0,
            column: 0,
        }
    }

    pub fn expression(&mut self) -> Result<AST, KabelError> {
        let term = self.term()?;
        Ok(term)
    }

    pub fn term(&mut self) -> Result<AST, KabelError> {
        let mut left = self.factor()?;

        while self.current < self.input.len() &&
            (self.peek()?.token_type == TokenType::Plus || self.peek()?.token_type == TokenType::Minus) {
            let binop = self.read_token()?;
            let right = self.factor()?;

            if binop.token_type == TokenType::Plus {
                left = AST {
                    ast_type: ASTType::Binary(Box::new(left.clone()), BinOp::Add, Box::new(right.clone())),
                    start: left.start,
                    end: right.end,
                    line: left.line,
                    column: left.column,
                };
            } else {
                left = AST {
                    ast_type: ASTType::Binary(Box::new(left.clone()), BinOp::Sub, Box::new(right.clone())),
                    start: left.start,
                    end: right.end,
                    line: left.line,
                    column: left.column,
                };
            }
        }
        Ok(left)
    }
    pub fn factor(&mut self) -> Result<AST, KabelError> {
        let mut left = self.primary()?;

        while self.current < self.input.len() &&
            (self.peek()?.token_type == TokenType::Star || self.peek()?.token_type == TokenType::Slash) {
            let binop = self.read_token()?;
            let right = self.primary()?;

            if binop.token_type == TokenType::Star {
                left = AST {
                    ast_type: ASTType::Binary(Box::new(left.clone()), BinOp::Mul, Box::new(right.clone())),
                    start: left.start,
                    end: right.end,
                    line: left.line,
                    column: left.column,
                };
            } else {
                left = AST {
                    ast_type: ASTType::Binary(Box::new(left.clone()), BinOp::Div, Box::new(right.clone())),
                    start: left.start,
                    end: right.end,
                    line: left.line,
                    column: left.column,
                };
            }
        }
        Ok(left)
    }
    pub fn primary(&mut self) -> Result<AST, KabelError> {
        let token = self.read_token()?;

        match token.token_type {
            TokenType::Ident(ident) => {
                return Ok(AST {
                    ast_type: ASTType::Ident(ident),
                    start: token.start,
                    end: token.end,
                    line: token.line,
                    column: token.column,
                });
            }
            TokenType::Num(num) => {
                return Ok(AST {
                    ast_type: ASTType::Num(num),
                    start: token.start,
                    end: token.end,
                    line: token.line,
                    column: token.column,
                });
            }
            TokenType::Str(string) => {
                return Ok(AST {
                    ast_type: ASTType::Str(string),
                    start: token.start,
                    end: token.end,
                    line: token.line,
                    column: token.column,
                });
            }
            TokenType::LeftParen => {
                return Ok(self.group(token)?);
            }
            _ => {
                return Err(KabelError::new(ErrorKind::UnexpectedToken,
                        format!("Unexpected token {}", self.text[token.start..token.end].to_string()),
                        token.line, token.column,
                        self.text[token.line_start..token.end].to_string()));
            },
        }
    }
    pub fn group(&mut self, left_paren: Token) -> Result<AST, KabelError> {
        let expr = self.expression()?;
        let right_paren = self.peek();
        if let Ok(right_paren) = right_paren {
            if right_paren.token_type != TokenType::RightParen {
                return Err(KabelError::new(ErrorKind::MissingDelimiter,
                        "Missing right parenthesis".to_string(),
                        right_paren.line, right_paren.column,
                        self.text[left_paren.start..right_paren.end].to_string()));
            }
            self.read_token()?;
            return Ok(AST {
                ast_type: ASTType::Group(Box::new(expr.clone())),
                start: left_paren.start,
                end: right_paren.end,
                line: left_paren.line,
                column: left_paren.column,
            });
        }
        if let Err(e) = right_paren {
            return Err(KabelError::new(ErrorKind::MissingDelimiter,
                    "Missing right parenthesis".to_string(),
                    e.line, e.column,
                    self.text[left_paren.line_start..expr.end].to_string()));
        }
        unreachable!();
    }


    pub fn read_token(&mut self) -> Result<Token, KabelError> {
        if self.current >= self.input.len() {
            let last_token = self.input[self.input.len()-1].clone();
            return Err(KabelError::new(ErrorKind::UnexpectedEof,
                    "Unexpected end of file".to_string(), last_token.line, last_token.column,
                    self.text[last_token.line_start..last_token.end].to_string()));
        }
        self.token = self.input[self.current].clone();
        self.current += 1;
        return Ok(self.token.clone());
    }
    pub fn peek(&mut self) -> Result<Token, KabelError> {
        if self.current >= self.input.len() {
            let last_token = self.input[self.input.len()-1].clone();
            return Err(KabelError::new(ErrorKind::UnexpectedEof,
                    "Unexpected end of file".to_string(), last_token.line, last_token.column,
                    self.text[last_token.line_start..last_token.end].to_string()));
        }
        return Ok(self.input[self.current].clone());
    }
}

#[derive(Debug, Clone)]
pub struct AST {
    pub ast_type: ASTType,
    pub start: usize,
    pub end: usize,
    pub line: usize,
    pub column: usize,
}

#[derive(Debug, Clone)]
pub enum ASTType {
    Program(Vec<AST>),

    Binary(Box<AST>, BinOp, Box<AST>),

    Group(Box<AST>),
    Ident(String),
    Num(f32),
    Str(String),
}

#[derive(Debug, Clone, Copy)]
pub enum BinOp {
    Add,
    Sub,
    Mul,
    Div,
}

M server/src/main.rs => server/src/main.rs +14 -5
@@ 150,12 150,21 @@ fn main() {
            server_config.world.pixels_per_meter,
        ))
        .add_plugins(StkTungsteniteServerPlugin)
        .add_systems(Startup, (setup_integration_parameters, planet::spawn_planets))
        .add_systems(
            Startup,
            (setup_integration_parameters, planet::spawn_planets),
        )
        .add_systems(Update, (player::on_message, player::packet::on_close))
        .add_systems(FixedUpdate,
            (module::module_spawn, player::packet::send_player_energy,
                player::packet::on_position_change, module::save::save_eligibility,
                module::convert_modules))
        .add_systems(
            FixedUpdate,
            (
                module::module_spawn,
                player::packet::send_player_energy,
                player::packet::on_position_change,
                module::save::save_eligibility,
                module::convert_modules,
            ),
        )
        .add_systems(
            FixedUpdate,
            (

M server/src/module/component.rs => server/src/module/component.rs +3 -1
@@ 1,5 1,7 @@
use bevy::prelude::*;
use bevy_rapier2d::prelude::{ExternalForce, ExternalImpulse, ReadMassProperties, RigidBody, Velocity};
use bevy_rapier2d::prelude::{
    ExternalForce, ExternalImpulse, ReadMassProperties, RigidBody, Velocity,
};
use serde::{Deserialize, Serialize};
use starkingdoms_common::PartType as c_PartType;


M server/src/module/mod.rs => server/src/module/mod.rs +102 -25
@@ 71,12 71,28 @@ pub fn module_spawn(
pub fn detach_recursive(
    commands: &mut Commands,
    this: Entity,
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) -> u32 {


@@ 163,22 179,45 @@ pub fn despawn_module_tree(
}

pub fn attach_on_module_tree(
    x: f32, y: f32,
    x: f32,
    y: f32,
    commands: &mut Commands,
    this: Entity,
    select: Entity,
    player_id: Entity,
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>,
            &mut PartFlags),
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
    part_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) -> bool {


@@ 194,8 233,15 @@ pub fn attach_on_module_tree(
        let part_type = *module.1;
        ret |= if part_type != PartType::LandingThrusterSuspension {
            attach_on_module_tree(
                x, y, commands, *this, select, player_id, attached_query,
                part_query, player_query,
                x,
                y,
                commands,
                *this,
                select,
                player_id,
                attached_query,
                part_query,
                player_query,
            )
        } else {
            false


@@ 324,9 370,16 @@ pub fn convert_modules(
    rapier_context: Res<RapierContext>,
    planet_query: Query<(Entity, &PlanetType, &Children)>,
    mut player_query: Query<(&Attach, &mut Player)>,
    mut attached_query: Query<(Entity, &mut PartType, &mut Attach,
            &mut AdditionalMassProperties, &Children, &Transform,
            &PartFlags),
    mut attached_query: Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
        Without<Player>,
    >,
    mut collider_query: Query<


@@ 389,8 442,16 @@ fn convert_modules_recursive(
    commands: &mut Commands,
    planet_type: PlanetType,
    attach: Attach,
    attached_query: &mut Query<(Entity, &mut PartType, &mut Attach,
            &mut AdditionalMassProperties, &Children, &Transform, &PartFlags),
    attached_query: &mut Query<
        (
            Entity,
            &mut PartType,
            &mut Attach,
            &mut AdditionalMassProperties,
            &Children,
            &Transform,
            &PartFlags,
        ),
        Without<Player>,
    >,
    collider_query: &mut Query<


@@ 486,8 547,8 @@ fn convert_modules_recursive(
                        .insert(ImpulseJoint::new(module_entity, joint))
                        .insert(AdditionalMassProperties::MassProperties(MassProperties {
                            local_center_of_mass: vec2(0.0, 0.0),
                            mass:               0.00000000000001,
                            principal_inertia:  0.00000000000001,
                            mass: 0.00000000000001,
                            principal_inertia: 0.00000000000001,
                        }))
                        .insert(Attach {
                            associated_player: attach.associated_player,


@@ 550,12 611,28 @@ fn convert_modules_recursive(
pub fn break_modules(
    mut commands: Commands,
    rapier_context: Res<RapierContext>,
    mut attached_query: Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    mut attached_query: Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    mut player_query: Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    mut player_query: Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
) {

M server/src/player/client_login.rs => server/src/player/client_login.rs +96 -39
@@ 8,13 8,26 @@ use rand::Rng;
use sha2::Sha256;
use starkingdoms_common::unpack_savefile;

use crate::{config::StkConfig, module::{component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType}, save::load_savefile}, planet::PlanetType, proto_part_flags, proto_transform, ws::WsEvent, AppKeys, MessageType, Packet, Part, Planet, ProtoPartFlags, UserToken, CLIENT_SCALE};
use crate::{
    config::StkConfig,
    module::{
        component::{Attach, CanAttach, LooseAttach, PartBundle, PartFlags, PartType},
        save::load_savefile,
    },
    planet::PlanetType,
    proto_part_flags, proto_transform,
    ws::WsEvent,
    AppKeys, MessageType, Packet, Part, Planet, ProtoPartFlags, UserToken, CLIENT_SCALE,
};

use super::component::{Input, Player};

pub fn join_auth(
    jwt: Option<String>, app_keys: AppKeys, from: &SocketAddr,
    event_queue: &mut Vec<WsEvent>, server_config: StkConfig,
    jwt: Option<String>,
    app_keys: AppKeys,
    from: &SocketAddr,
    event_queue: &mut Vec<WsEvent>,
    server_config: StkConfig,
) -> Result<(), ()> {
    if let Some(token) = jwt {
        let key: Hmac<Sha256> = Hmac::new_from_slice(&app_keys.app_key).unwrap();


@@ 30,9 43,7 @@ pub fn join_auth(
            }
        };

        if claims.permission_level
            < server_config.security.required_permission_level
        {
        if claims.permission_level < server_config.security.required_permission_level {
            event_queue.push(WsEvent::Send {
                to: *from,
                message: Packet::Message { message_type: MessageType::Error, actor: "SERVER".to_string(), content: format!("Permission level {} is too low, {} is required. If your permissions were just changed, you need to log out and log back in for the change to take effect. If you believe this is a mistake, contact StarKingdoms staff.", claims.permission_level, server_config.security.required_permission_level) }.into(),


@@ 57,7 68,9 @@ pub fn join_auth(
}

pub fn spawn_player(
    commands: &mut Commands, from: &SocketAddr, username: String,
    commands: &mut Commands,
    from: &SocketAddr,
    username: String,
) -> (Entity, Transform, Player) {
    // generate random angle
    let angle: f32 = {


@@ 65,8 78,7 @@ pub fn spawn_player(
        rng.gen::<f32>() * std::f32::consts::PI * 2.
    };
    // convert to cartesian with 30.0 meter radius
    let mut transform =
        Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0);
    let mut transform = Transform::from_xyz(angle.cos() * 30.0, angle.sin() * 30.0, 0.0);
    transform.rotate_z(angle);
    let player_comp = Player {
        addr: *from,


@@ 94,21 106,49 @@ pub fn spawn_player(
}

pub fn load_save(
    commands: &mut Commands, transform: Transform, id: Entity,
    save: Option<String>, app_keys: AppKeys,
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    commands: &mut Commands,
    transform: Transform,
    id: Entity,
    save: Option<String>,
    app_keys: AppKeys,
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
    part_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
    player_comp: &mut Player, attach: &mut Attach, from: &SocketAddr,
    player_comp: &mut Player,
    attach: &mut Attach,
    from: &SocketAddr,
    event_queue: &mut Vec<WsEvent>,
) {
    if let Some(save) = save {


@@ 149,18 189,43 @@ pub fn load_save(

pub fn packet_stream(
    planet_query: &Query<(Entity, &PlanetType, &Transform)>,
    event_queue: &mut Vec<WsEvent>, from: &SocketAddr,
    player_query: &Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    event_queue: &mut Vec<WsEvent>,
    from: &SocketAddr,
    player_query: &Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
    index: u32, username: String,
    part_query: &Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
    index: u32,
    username: String,
    part_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    attached_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    transform: Transform,


@@ 177,15 242,9 @@ pub fn packet_stream(
                    translation * CLIENT_SCALE
                )),
                radius: match *planet_type {
                    PlanetType::Earth => {
                        planet!(PlanetType::Earth).size * CLIENT_SCALE
                    }
                    PlanetType::Moon => {
                        planet!(PlanetType::Moon).size * CLIENT_SCALE
                    }
                    PlanetType::Mars => {
                        planet!(PlanetType::Mars).size * CLIENT_SCALE
                    }
                    PlanetType::Earth => planet!(PlanetType::Earth).size * CLIENT_SCALE,
                    PlanetType::Moon => planet!(PlanetType::Moon).size * CLIENT_SCALE,
                    PlanetType::Mars => planet!(PlanetType::Mars).size * CLIENT_SCALE,
                },
            },
        ));


@@ 254,10 313,8 @@ pub fn packet_stream(
        index,
        Part {
            part_type: PartType::Hearty,
            transform: proto_transform!(Transform::from_translation(
                transform.translation
            )
            .with_rotation(transform.rotation)),
            transform: proto_transform!(Transform::from_translation(transform.translation)
                .with_rotation(transform.rotation)),
            flags: ProtoPartFlags { attached: false },
        },
    ));

M server/src/player/mod.rs => server/src/player/mod.rs +71 -24
@@ 1,8 1,4 @@
use bevy::{
    ecs::event::ManualEventReader,
    math::vec2,
    prelude::*,
};
use bevy::{ecs::event::ManualEventReader, math::vec2, prelude::*};
use bevy_rapier2d::prelude::*;
use client_login::packet_stream;
use component::Player;


@@ 11,18 7,25 @@ use request_save::request_save;
use send_message::send_message;

use crate::{
    config::StkConfig, err_or_cont, mathutil::rot2d, module::{
    config::StkConfig,
    err_or_cont,
    mathutil::rot2d,
    module::{
        component::{Attach, CanAttach, LooseAttach, PartFlags, PartType},
        PART_HALF_SIZE,
    }, part, planet::PlanetType, ws::WsEvent, AppKeys, Packet, CLIENT_SCALE
    },
    part,
    planet::PlanetType,
    ws::WsEvent,
    AppKeys, Packet, CLIENT_SCALE,
};

pub mod client_login;
pub mod component;
pub mod packet;
pub mod client_login;
pub mod send_message;
pub mod player_mouse_input;
pub mod request_save;
pub mod send_message;

pub fn on_message(
    mut commands: Commands,


@@ 79,9 82,14 @@ pub fn on_message(
                    jwt,
                } => {
                    // auth
                    err_or_cont!(client_login::join_auth(jwt, app_keys.clone(),
                        from, &mut event_queue, server_config.clone()));
                    
                    err_or_cont!(client_login::join_auth(
                        jwt,
                        app_keys.clone(),
                        from,
                        &mut event_queue,
                        server_config.clone()
                    ));

                    // create player in world
                    let (id, transform, mut player_comp) =
                        client_login::spawn_player(&mut commands, from, username.clone());


@@ 94,19 102,37 @@ pub fn on_message(
                        children: [None, None, None, None],
                    };
                    // create ship from potential save
                    client_login::load_save(&mut commands, transform, id, save,
                        app_keys.clone(), &mut attached_query, &mut part_query,
                        &mut player_query, &mut player_comp, &mut attach,
                        from, &mut event_queue);
                    client_login::load_save(
                        &mut commands,
                        transform,
                        id,
                        save,
                        app_keys.clone(),
                        &mut attached_query,
                        &mut part_query,
                        &mut player_query,
                        &mut player_comp,
                        &mut attach,
                        from,
                        &mut event_queue,
                    );
                    // finish player entity
                    let mut entity_id = commands.entity(id);
                    entity_id.insert(player_comp);
                    entity_id.insert(attach);

                    // send packets that tell player initial world state
                    packet_stream(&planet_query, &mut event_queue, from,
                        &player_query, index, username, &part_query,
                        &attached_query, transform);
                    packet_stream(
                        &planet_query,
                        &mut event_queue,
                        from,
                        &player_query,
                        index,
                        username,
                        &part_query,
                        &attached_query,
                        transform,
                    );
                }
                Packet::SendMessage { target, content } => {
                    // a player sent a message


@@ 147,12 173,27 @@ pub fn on_message(
                                };
                                q_player.selected = None;
                                // process if module was attach or detached
                                attach_or_detach(select, &mut attached_query,
                                    &mut player_query, &mut part_query, &mut commands, x, y, entity);
                                attach_or_detach(
                                    select,
                                    &mut attached_query,
                                    &mut player_query,
                                    &mut part_query,
                                    &mut commands,
                                    x,
                                    y,
                                    entity,
                                );
                                break;
                            }
                            // check if mouse touched a module
                            mouse_picking(&attached_query, &part_query, &mut q_player, x, y, entity);
                            mouse_picking(
                                &attached_query,
                                &part_query,
                                &mut q_player,
                                x,
                                y,
                                entity,
                            );
                        }
                    }
                }


@@ 164,8 205,14 @@ pub fn on_message(
                            // mhm yeah done

                            // client asked to save, generate save data and send it back
                            request_save(&attached_query, old_save.clone(), app_keys.clone(),
                                attach.clone(), &mut event_queue, from);
                            request_save(
                                &attached_query,
                                old_save.clone(),
                                app_keys.clone(),
                                attach.clone(),
                                &mut event_queue,
                                from,
                            );
                        }
                    }
                }

M server/src/player/player_mouse_input.rs => server/src/player/player_mouse_input.rs +58 -24
@@ 1,22 1,48 @@
use bevy::{math::vec3, prelude::*};
use bevy_rapier2d::prelude::*;

use crate::{module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType}, planet::PlanetType};
use crate::{
    module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType},
    planet::PlanetType,
};

use super::component::Player;

pub fn attach_or_detach(
    select: Entity,
    attached_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    attached_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    player_query: &mut Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    player_query: &mut Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
    part_query: &mut Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
    part_query: &mut Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    commands: &mut Commands,


@@ 27,24 53,18 @@ pub fn attach_or_detach(
    if attached_query.contains(select) {
        let module = attached_query.get(select).unwrap();
        let attach = module.3.clone();
        let lost_energy_capacity = crate::module::detach_recursive(
            commands,
            module.0,
            attached_query,
            player_query,
        );
        let lost_energy_capacity =
            crate::module::detach_recursive(commands, module.0, attached_query, player_query);
        let mut module = attached_query.get_mut(select).unwrap();
        module.2.translation = vec3(x, y, 0.);
        if *module.1 == PartType::LandingThruster {
            let sub_entity = attach.children[2].unwrap();
            let mut suspension =
                attached_query.get_mut(sub_entity).unwrap();
            let mut suspension = attached_query.get_mut(sub_entity).unwrap();
            suspension.2.translation = vec3(x, y, 0.);
        }
        let mut player = player_query.get_mut(entity).unwrap().1;
        player.energy_capacity -= lost_energy_capacity;
        player.energy =
            std::cmp::min(player.energy, player.energy_capacity);
        player.energy = std::cmp::min(player.energy, player.energy_capacity);
        return;
    }
    if crate::module::attach_on_module_tree(


@@ 75,12 95,28 @@ pub fn attach_or_detach(
}

pub fn mouse_picking(
    attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    attached_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    part_query: &Query<(Entity, &PartType, &mut Transform, &mut Velocity,
            Option<&LooseAttach>, &mut PartFlags),
    part_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Velocity,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>, Without<Attach>),
    >,
    q_player: &mut Player,


@@ 88,9 124,7 @@ pub fn mouse_picking(
    y: f32,
    entity: Entity,
) {
    for (m_entity, part_type, transform, m_attach, _velocity, _, _, _) in
        attached_query.iter()
    {
    for (m_entity, part_type, transform, m_attach, _velocity, _, _, _) in attached_query.iter() {
        if *part_type == PartType::LandingThrusterSuspension {
            continue;
        }

M server/src/player/request_save.rs => server/src/player/request_save.rs +20 -11
@@ 4,25 4,37 @@ use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;
use starkingdoms_common::{pack_savefile, unpack_savefile, SaveData};

use crate::{module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType}, planet::PlanetType, ws::WsEvent, AppKeys, Packet};
use crate::{
    module::component::{Attach, CanAttach, LooseAttach, PartFlags, PartType},
    planet::PlanetType,
    ws::WsEvent,
    AppKeys, Packet,
};

use super::component::Player;

pub fn request_save(
    attached_query: &Query<(Entity, &PartType, &mut Transform, &mut Attach,
            &Velocity, Option<&CanAttach>, Option<&LooseAttach>, &mut PartFlags),
    attached_query: &Query<
        (
            Entity,
            &PartType,
            &mut Transform,
            &mut Attach,
            &Velocity,
            Option<&CanAttach>,
            Option<&LooseAttach>,
            &mut PartFlags,
        ),
        (Without<PlanetType>, Without<Player>),
    >,
    old_save: Option<String>,
    app_keys: AppKeys,
    attach: Attach,
    event_queue: &mut Vec<WsEvent>,
    from: &SocketAddr
    from: &SocketAddr,
) {
    let unused_modules = if let Some(ref old_save) = old_save {
        if let Ok(old_savedata) =
            unpack_savefile(&app_keys.app_key, old_save.to_string())
        {
        if let Ok(old_savedata) = unpack_savefile(&app_keys.app_key, old_save.to_string()) {
            old_savedata.unused_modules
        } else {
            Vec::new()


@@ 31,10 43,7 @@ pub fn request_save(
        Vec::new()
    };
    let save = SaveData {
        children: crate::module::save::construct_save_data(
            attach.clone(),
            attached_query,
        ),
        children: crate::module::save::construct_save_data(attach.clone(), attached_query),
        unused_modules,
    };
    let save_string = pack_savefile(&app_keys.app_key, save);

M server/src/player/send_message.rs => server/src/player/send_message.rs +15 -3
@@ 3,13 3,25 @@ use std::net::SocketAddr;
use bevy::prelude::*;
use bevy_rapier2d::prelude::Velocity;

use crate::{module::component::{Attach, PartFlags}, planet::PlanetType, ws::WsEvent, Packet};
use crate::{
    module::component::{Attach, PartFlags},
    planet::PlanetType,
    ws::WsEvent,
    Packet,
};

use super::component::Player;

pub fn send_message(
    player_query: &Query<(Entity, &mut Player, &Transform, &Velocity,
            &mut Attach, &mut PartFlags),
    player_query: &Query<
        (
            Entity,
            &mut Player,
            &Transform,
            &Velocity,
            &mut Attach,
            &mut PartFlags,
        ),
        Without<PlanetType>,
    >,
    from: &SocketAddr,

A starkingdoms-client/vite.config.ts.timestamp-1722468930578-9a55b81119f46.mjs => starkingdoms-client/vite.config.ts.timestamp-1722468930578-9a55b81119f46.mjs +39 -0
@@ 0,0 1,39 @@
// vite.config.ts
import { defineConfig } from "file:///home/ghostlyzsh/dev/starkingdoms.tk/starkingdoms-client/node_modules/vite/dist/node/index.js";
import { resolve } from "path";
import * as child from "child_process";
import autoprefixer from "file:///home/ghostlyzsh/dev/starkingdoms.tk/starkingdoms-client/node_modules/autoprefixer/lib/autoprefixer.js";
import { svelte } from "file:///home/ghostlyzsh/dev/starkingdoms.tk/starkingdoms-client/node_modules/@sveltejs/vite-plugin-svelte/src/index.js";
var __vite_injected_original_dirname = "/home/ghostlyzsh/dev/starkingdoms.tk/starkingdoms-client";
var commitHash = child.execSync("git describe --no-match --always --abbrev=8 --dirty").toString().trim();
var vite_config_default = defineConfig({
  plugins: [svelte()],
  define: {
    APP_VERSION: JSON.stringify(process.env.npm_package_version),
    COMMIT_HASH: JSON.stringify(commitHash)
  },
  build: {
    target: ["chrome89", "edge89", "firefox89", "safari15"],
    cssCodeSplit: false,
    rollupOptions: {
      input: {
        main: resolve(__vite_injected_original_dirname, "index.html"),
        play: resolve(__vite_injected_original_dirname, "play/index.html"),
        signup: resolve(__vite_injected_original_dirname, "signup/index.html"),
        login: resolve(__vite_injected_original_dirname, "login/index.html"),
        shipeditor: resolve(__vite_injected_original_dirname, "shipeditor/index.html"),
        uikit: resolve(__vite_injected_original_dirname, "uikit/index.html")
      }
    }
  },
  appType: "mpa",
  css: {
    postcss: {
      plugins: [autoprefixer({})]
    }
  }
});
export {
  vite_config_default as default
};
//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCIvaG9tZS9naG9zdGx5enNoL2Rldi9zdGFya2luZ2RvbXMudGsvc3Rhcmtpbmdkb21zLWNsaWVudFwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiL2hvbWUvZ2hvc3RseXpzaC9kZXYvc3Rhcmtpbmdkb21zLnRrL3N0YXJraW5nZG9tcy1jbGllbnQvdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL2hvbWUvZ2hvc3RseXpzaC9kZXYvc3Rhcmtpbmdkb21zLnRrL3N0YXJraW5nZG9tcy1jbGllbnQvdml0ZS5jb25maWcudHNcIjtpbXBvcnQgeyBkZWZpbmVDb25maWcgfSBmcm9tIFwidml0ZVwiO1xuaW1wb3J0IHsgcmVzb2x2ZSB9IGZyb20gXCJwYXRoXCI7XG5pbXBvcnQgKiBhcyBjaGlsZCBmcm9tIFwiY2hpbGRfcHJvY2Vzc1wiO1xuLy9AdHMtaWdub3JlXG5pbXBvcnQgYXV0b3ByZWZpeGVyIGZyb20gXCJhdXRvcHJlZml4ZXJcIjtcbmltcG9ydCB7IHN2ZWx0ZSB9IGZyb20gXCJAc3ZlbHRlanMvdml0ZS1wbHVnaW4tc3ZlbHRlXCI7XG5cbmNvbnN0IGNvbW1pdEhhc2ggPSBjaGlsZFxuICAuZXhlY1N5bmMoXCJnaXQgZGVzY3JpYmUgLS1uby1tYXRjaCAtLWFsd2F5cyAtLWFiYnJldj04IC0tZGlydHlcIilcbiAgLnRvU3RyaW5nKClcbiAgLnRyaW0oKTtcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lQ29uZmlnKHtcbiAgcGx1Z2luczogW3N2ZWx0ZSgpXSxcbiAgZGVmaW5lOiB7XG4gICAgQVBQX1ZFUlNJT046IEpTT04uc3RyaW5naWZ5KHByb2Nlc3MuZW52Lm5wbV9wYWNrYWdlX3ZlcnNpb24pLFxuICAgIENPTU1JVF9IQVNIOiBKU09OLnN0cmluZ2lmeShjb21taXRIYXNoKSxcbiAgfSxcbiAgYnVpbGQ6IHtcbiAgICB0YXJnZXQ6IFtcImNocm9tZTg5XCIsIFwiZWRnZTg5XCIsIFwiZmlyZWZveDg5XCIsIFwic2FmYXJpMTVcIl0sXG4gICAgY3NzQ29kZVNwbGl0OiBmYWxzZSxcbiAgICByb2xsdXBPcHRpb25zOiB7XG4gICAgICBpbnB1dDoge1xuICAgICAgICBtYWluOiByZXNvbHZlKF9fZGlybmFtZSwgXCJpbmRleC5odG1sXCIpLFxuICAgICAgICBwbGF5OiByZXNvbHZlKF9fZGlybmFtZSwgXCJwbGF5L2luZGV4Lmh0bWxcIiksXG4gICAgICAgIHNpZ251cDogcmVzb2x2ZShfX2Rpcm5hbWUsIFwic2lnbnVwL2luZGV4Lmh0bWxcIiksXG4gICAgICAgIGxvZ2luOiByZXNvbHZlKF9fZGlybmFtZSwgXCJsb2dpbi9pbmRleC5odG1sXCIpLFxuICAgICAgICBzaGlwZWRpdG9yOiByZXNvbHZlKF9fZGlybmFtZSwgXCJzaGlwZWRpdG9yL2luZGV4Lmh0bWxcIiksXG4gICAgICAgIHVpa2l0OiByZXNvbHZlKF9fZGlybmFtZSwgXCJ1aWtpdC9pbmRleC5odG1sXCIpLFxuICAgICAgfSxcbiAgICB9LFxuICB9LFxuICBhcHBUeXBlOiBcIm1wYVwiLFxuICBjc3M6IHtcbiAgICBwb3N0Y3NzOiB7XG4gICAgICBwbHVnaW5zOiBbYXV0b3ByZWZpeGVyKHt9KV0sXG4gICAgfSxcbiAgfSxcbn0pO1xuIl0sCiAgIm1hcHBpbmdzIjogIjtBQUEwVixTQUFTLG9CQUFvQjtBQUN2WCxTQUFTLGVBQWU7QUFDeEIsWUFBWSxXQUFXO0FBRXZCLE9BQU8sa0JBQWtCO0FBQ3pCLFNBQVMsY0FBYztBQUx2QixJQUFNLG1DQUFtQztBQU96QyxJQUFNLGFBQ0gsZUFBUyxxREFBcUQsRUFDOUQsU0FBUyxFQUNULEtBQUs7QUFFUixJQUFPLHNCQUFRLGFBQWE7QUFBQSxFQUMxQixTQUFTLENBQUMsT0FBTyxDQUFDO0FBQUEsRUFDbEIsUUFBUTtBQUFBLElBQ04sYUFBYSxLQUFLLFVBQVUsUUFBUSxJQUFJLG1CQUFtQjtBQUFBLElBQzNELGFBQWEsS0FBSyxVQUFVLFVBQVU7QUFBQSxFQUN4QztBQUFBLEVBQ0EsT0FBTztBQUFBLElBQ0wsUUFBUSxDQUFDLFlBQVksVUFBVSxhQUFhLFVBQVU7QUFBQSxJQUN0RCxjQUFjO0FBQUEsSUFDZCxlQUFlO0FBQUEsTUFDYixPQUFPO0FBQUEsUUFDTCxNQUFNLFFBQVEsa0NBQVcsWUFBWTtBQUFBLFFBQ3JDLE1BQU0sUUFBUSxrQ0FBVyxpQkFBaUI7QUFBQSxRQUMxQyxRQUFRLFFBQVEsa0NBQVcsbUJBQW1CO0FBQUEsUUFDOUMsT0FBTyxRQUFRLGtDQUFXLGtCQUFrQjtBQUFBLFFBQzVDLFlBQVksUUFBUSxrQ0FBVyx1QkFBdUI7QUFBQSxRQUN0RCxPQUFPLFFBQVEsa0NBQVcsa0JBQWtCO0FBQUEsTUFDOUM7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUFBLEVBQ0EsU0FBUztBQUFBLEVBQ1QsS0FBSztBQUFBLElBQ0gsU0FBUztBQUFBLE1BQ1AsU0FBUyxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUM7QUFBQSxJQUM1QjtBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogW10KfQo=