Compare commits

..

12 commits

121 changed files with 209578 additions and 9 deletions

49
.gitignore vendored
View file

@ -1,3 +1,46 @@
# Include your project-specific ignores in this file # See http://help.github.com/ignore-files/ for more about ignoring files.
# Read about how to use .gitignore: https://help.github.com/articles/ignoring-files
# Useful .gitignore templates: https://github.com/github/gitignore # compiled output
/dist
/tmp
/out-tsc
# Only exists if Bazel was run
/bazel-out
# dependencies
/node_modules
# profiling files
chrome-profiler-events*.json
speed-measure-plugin*.json
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
.history/*
# misc
/.sass-cache
/connect.lock
/coverage
/libpeerconnection.log
npm-debug.log
yarn-error.log
testem.log
/typings
# System Files
.DS_Store
Thumbs.db

3
.gitmodules vendored Normal file
View file

@ -0,0 +1,3 @@
[submodule "src/assets/server-grid-editor"]
path = src/assets/server-grid-editor
url = https://github.com/GrapeshotGames/ServerGridEditor.git

View file

@ -2,6 +2,11 @@
<dictionary name="Tom"> <dictionary name="Tom">
<words> <words>
<w>deparse</w> <w>deparse</w>
<w>gameplay</w>
<w>pegjs</w>
<w>powerstones</w>
<w>sublevel</w>
<w>sublevels</w>
</words> </words>
</dictionary> </dictionary>
</component> </component>

1
.idea/vcs.xml generated
View file

@ -2,5 +2,6 @@
<project version="4"> <project version="4">
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
<mapping directory="$PROJECT_DIR$/src/assets/server-grid-editor" vcs="Git" />
</component> </component>
</project> </project>

View file

@ -2,7 +2,10 @@
<module type="WEB_MODULE" version="4"> <module type="WEB_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$">
<excludeFolder url="file://$MODULE_DIR$/dist" />
<excludeFolder url="file://$MODULE_DIR$/tmp" />
</content>
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="jquery-3.4.1" level="application" /> <orderEntry type="library" name="jquery-3.4.1" level="application" />
<orderEntry type="library" name="popper" level="application" /> <orderEntry type="library" name="popper" level="application" />

27
README.md Normal file
View file

@ -0,0 +1,27 @@
# AtlasConfigGenerator
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 8.3.12.
## Development server
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
## Code scaffolding
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
## Build
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
## Running unit tests
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io).
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via [Protractor](http://www.protractortest.org/).
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).

135
angular.json Normal file
View file

@ -0,0 +1,135 @@
{
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
"version": 1,
"newProjectRoot": "projects",
"projects": {
"atlas-config-generator": {
"projectType": "application",
"schematics": {},
"root": "",
"sourceRoot": "src",
"prefix": "app",
"architect": {
"build": {
"builder": "@angular-devkit/build-angular:browser",
"options": {
"outputPath": "dist/atlas-config-generator",
"index": "src/index.html",
"main": "src/main.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.app.json",
"aot": false,
"assets": [
"src/favicon.ico",
"src/assets",
{ "glob": "worker-javascript.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" },
{ "glob": "worker-css.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" },
{ "glob": "worker-json.js", "input": "./node_modules/ace-builds/src-min/", "output": "/" }
],
"styles": [
"src/styles.scss",
"./node_modules/leaflet/dist/leaflet.css"
],
"scripts": []
},
"configurations": {
"production": {
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"optimization": true,
"outputHashing": "all",
"sourceMap": false,
"extractCss": true,
"namedChunks": false,
"aot": true,
"extractLicenses": true,
"vendorChunk": false,
"buildOptimizer": true,
"budgets": [
{
"type": "initial",
"maximumWarning": "2mb",
"maximumError": "5mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "6kb",
"maximumError": "10kb"
}
]
}
}
},
"serve": {
"builder": "@angular-devkit/build-angular:dev-server",
"options": {
"browserTarget": "atlas-config-generator:build"
},
"configurations": {
"production": {
"browserTarget": "atlas-config-generator:build:production"
}
}
},
"extract-i18n": {
"builder": "@angular-devkit/build-angular:extract-i18n",
"options": {
"browserTarget": "atlas-config-generator:build"
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "src/test.ts",
"polyfills": "src/polyfills.ts",
"tsConfig": "tsconfig.spec.json",
"karmaConfig": "karma.conf.js",
"assets": [
"src/favicon.ico",
"src/assets"
],
"styles": [
"src/styles.scss"
],
"scripts": []
}
},
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": [
"tsconfig.app.json",
"tsconfig.spec.json",
"e2e/tsconfig.json"
],
"exclude": [
"**/node_modules/**"
]
}
},
"e2e": {
"builder": "@angular-devkit/build-angular:protractor",
"options": {
"protractorConfig": "e2e/protractor.conf.js",
"devServerTarget": "atlas-config-generator:serve"
},
"configurations": {
"production": {
"devServerTarget": "atlas-config-generator:serve:production"
}
}
}
}
}
},
"defaultProject": "atlas-config-generator",
"schematics": {
"@schematics/angular:component": {
"styleext": "scss"
}
}
}

12
browserslist Normal file
View file

@ -0,0 +1,12 @@
# This file is used by the build system to adjust CSS and JS output to support the specified browsers below.
# For additional information regarding the format and rule options, please see:
# https://github.com/browserslist/browserslist#queries
# You can see what browsers were selected by your queries by running:
# npx browserslist
> 0.5%
last 2 versions
Firefox ESR
not dead
not IE 9-11 # For IE 9-11 support, remove 'not'.

32
e2e/protractor.conf.js Normal file
View file

@ -0,0 +1,32 @@
// @ts-check
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.ts
const { SpecReporter } = require('jasmine-spec-reporter');
/**
* @type { import("protractor").Config }
*/
exports.config = {
allScriptsTimeout: 11000,
specs: [
'./src/**/*.e2e-spec.ts'
],
capabilities: {
browserName: 'chrome'
},
directConnect: true,
baseUrl: 'http://localhost:4200/',
framework: 'jasmine',
jasmineNodeOpts: {
showColors: true,
defaultTimeoutInterval: 30000,
print: function() {}
},
onPrepare() {
require('ts-node').register({
project: require('path').join(__dirname, './tsconfig.json')
});
jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } }));
}
};

23
e2e/src/app.e2e-spec.ts Normal file
View file

@ -0,0 +1,23 @@
import { AppPage } from './app.po';
import { browser, logging } from 'protractor';
describe('workspace-project App', () => {
let page: AppPage;
beforeEach(() => {
page = new AppPage();
});
it('should display welcome message', () => {
page.navigateTo();
expect(page.getTitleText()).toEqual('atlas-config-generator app is running!');
});
afterEach(async () => {
// Assert that there are no errors emitted from the browser
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
expect(logs).not.toContain(jasmine.objectContaining({
level: logging.Level.SEVERE,
} as logging.Entry));
});
});

11
e2e/src/app.po.ts Normal file
View file

@ -0,0 +1,11 @@
import { browser, by, element } from 'protractor';
export class AppPage {
navigateTo() {
return browser.get(browser.baseUrl) as Promise<any>;
}
getTitleText() {
return element(by.css('app-root .content span')).getText() as Promise<string>;
}
}

13
e2e/tsconfig.json Normal file
View file

@ -0,0 +1,13 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"outDir": "../out-tsc/e2e",
"module": "commonjs",
"target": "es5",
"types": [
"jasmine",
"jasminewd2",
"node"
]
}
}

View file

@ -1986,7 +1986,7 @@
<button class="close" data-bind="click: cancelDeparseEdit">&times;</button> <button class="close" data-bind="click: cancelDeparseEdit">&times;</button>
</div> </div>
<div class="modal-body"> <div class="modal-body">
<p>JSON! Its all JSON. Only magical ones are the 'BlueprintGeneratedClass' and 'SoundWave' items - these <p>JSON! Its all JSON. Only magical ones are the 'BlueprintGeneratedClassModel' and 'SoundWave' items - these
appear as an array of two items, the 'class' name and the argument for them.</p> appear as an array of two items, the 'class' name and the argument for them.</p>
<div id="deparse-editor"></div> <div id="deparse-editor"></div>
</div> </div>

View file

@ -18,9 +18,9 @@ ws "whitespace" = [ \t\n\r]*
value = false / null / true / object / array / number / stringval / class / empty value = false / null / true / object / array / number / stringval / class / empty
empty = "" { return undefined; } empty = "" { return undefined; }
null = "None" / "none" { return null; } null = "None" { return null; }
false = "False" / "false" { return false; } false = "False" { return false; }
true = "True" / "true" { return true; } true = "True" { return true; }
// ----- Objects ----- // ----- Objects -----

View file

@ -87,7 +87,7 @@ function deparseArray(input) {
function deparseNumber(input, key) { function deparseNumber(input, key) {
// Any number which has a decimal is stored to 6 decimal places // Any number which has a decimal is stored to 6 decimal places
// Except for IDs. Unless they actually have such a number... erm... // Except for IDs. Unless they actually have such a number... erm...
if (key && key.substr(key.length - 2, 2) === "ID") { if (key.substr(key.length - 2, 2) === "ID") {
if (input % 1 === 0) { if (input % 1 === 0) {
return Number(input).toString(); return Number(input).toString();
} }

32
karma.conf.js Normal file
View file

@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html
module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, './coverage/atlas-config-generator'),
reports: ['html', 'lcovonly', 'text-summary'],
fixWebpackSourcePaths: true
},
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false,
restartOnFileChange: true
});
};

13189
package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

57
package.json Normal file
View file

@ -0,0 +1,57 @@
{
"name": "atlas-config-generator",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"build": "ng build",
"test": "ng test",
"lint": "ng lint",
"e2e": "ng e2e"
},
"private": true,
"dependencies": {
"@angular/animations": "~8.2.11",
"@angular/common": "~8.2.11",
"@angular/compiler": "~8.2.11",
"@angular/core": "~8.2.11",
"@angular/forms": "~8.2.11",
"@angular/platform-browser": "~8.2.11",
"@angular/platform-browser-dynamic": "~8.2.11",
"@angular/router": "~8.2.11",
"@asymmetrik/ngx-leaflet": "^6.0.1",
"@ng-bootstrap/ng-bootstrap": "^5.1.1",
"@types/leaflet-imageoverlay-rotated": "^0.1.4",
"@types/pegjs": "^0.10.1",
"ace-builds": "^1.4.7",
"bootstrap": "^4.3.1",
"leaflet": "^1.6.0",
"leaflet-imageoverlay-rotated": "^0.2.1",
"rxjs": "~6.4.0",
"ts-pegjs": "^0.2.6",
"tslib": "^1.10.0",
"zone.js": "~0.9.1"
},
"devDependencies": {
"@angular-devkit/build-angular": "~0.803.12",
"@angular/cli": "~8.3.12",
"@angular/compiler-cli": "~8.2.11",
"@angular/language-service": "~8.2.11",
"@types/jasmine": "~3.3.8",
"@types/jasminewd2": "~2.0.3",
"@types/leaflet": "^1.5.7",
"@types/node": "~8.9.4",
"codelyzer": "^5.0.0",
"jasmine-core": "~3.4.0",
"jasmine-spec-reporter": "~4.2.1",
"karma": "~4.1.0",
"karma-chrome-launcher": "~2.2.0",
"karma-coverage-istanbul-reporter": "~2.0.1",
"karma-jasmine": "~2.0.1",
"karma-jasmine-html-reporter": "^1.4.0",
"protractor": "~5.4.0",
"ts-node": "~7.0.0",
"tslint": "~5.15.0",
"typescript": "~3.5.3"
}
}

View file

@ -0,0 +1,37 @@
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { RawEditorComponent } from "./pages/raw-editor/raw-editor.component";
import { ConfigComponent } from "./pages/config/config.component";
import { QuestsComponent } from "./pages/config/quests/quests.component";
import { GridComponent } from "./pages/config/grid/grid.component";
import { DbsComponent } from "./pages/config/dbs/dbs.component";
import { ServersComponent } from "./pages/config/servers/servers.component";
import { ServerComponent } from "./pages/config/servers/server/server.component";
import { IslandInstanceComponent } from "./pages/config/servers/island-instance/island-instance.component";
import { MapComponent } from "./pages/config/map/map.component";
import { DiscoveryZoneComponent } from "./pages/config/servers/discovery-zone/discovery-zone.component";
const routes: Routes = [
{path: 'raw', component: RawEditorComponent},
{
path: 'config', component: ConfigComponent, children: [
{path: '', redirectTo: 'grid', pathMatch: 'full'},
{path: 'grid', component: GridComponent},
{path: 'dbs', component: DbsComponent},
{path: 'quests', component: QuestsComponent},
{path: 'servers', component: ServersComponent},
{path: 'server/:index', component: ServerComponent},
{path: 'server/:index/island/:islandIndex', component: IslandInstanceComponent},
{path: 'server/:index/discovery/:discoIndex', component: DiscoveryZoneComponent},
{path: 'map', component: MapComponent}
]
}
];
@NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View file

@ -0,0 +1,23 @@
<div class="container">
<div class="row">
<div class="col">
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">
<a class="navbar-brand" href="#">Atlas Config</a>
<button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarTop">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarTop">
<ul class="navbar-nav ml-auto">
<li class="nav-item">
<a class="nav-link" routerLink="/raw" routerLinkActive="active">Raw</a>
</li>
<li class="nav-item">
<a class="nav-link" routerLink="/config" routerLinkActive="active">Config</a>
</li>
</ul>
</div>
</nav>
</div>
</div>
<router-outlet></router-outlet>
</div>

View file

19
src/app/app.component.ts Normal file
View file

@ -0,0 +1,19 @@
import { Component } from '@angular/core';
import { Server } from "./server";
import { ServerGridModel } from "./models/serverGrid.model";
import * as data from '../serverGridExample.json';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'atlas-config-generator';
constructor(private server: Server) {
// Temporary just load all the grid. Remove later.
this.server.serverGrid = new ServerGridModel().deserialize((data as any).default);
console.log(this.server.serverGrid);
}
}

63
src/app/app.module.ts Normal file
View file

@ -0,0 +1,63 @@
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { NgbModule } from '@ng-bootstrap/ng-bootstrap';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { RawEditorComponent } from "./pages/raw-editor/raw-editor.component";
import { CodeEditorComponent } from './components/code-editor/code-editor.component';
import { ConfigComponent } from './pages/config/config.component';
import { FormsModule } from "@angular/forms";
import { Server } from "./server";
import { PegjsService } from "./services/pegjs.service";
import { QuestsComponent } from './pages/config/quests/quests.component';
import { GridComponent } from './pages/config/grid/grid.component';
import { NumberComponent } from './components/form/number/number.component';
import { TextComponent } from './components/form/text/text.component';
import { PasswordComponent } from './components/form/password/password.component';
import { DbsComponent } from './pages/config/dbs/dbs.component';
import { CheckboxComponent } from './components/form/checkbox/checkbox.component';
import { ServersComponent } from './pages/config/servers/servers.component';
import { ServerComponent } from './pages/config/servers/server/server.component';
import { TemplateComponent } from './pages/config/servers/template/template.component';
import { SelectListComponent } from './components/form/select-list/select-list.component';
import { SelectObjectComponent } from './components/form/select-object/select-object.component';
import { IslandInstanceComponent } from './pages/config/servers/island-instance/island-instance.component';
import { LeafletModule } from "@asymmetrik/ngx-leaflet";
import { MapComponent } from './pages/config/map/map.component';
import { DiscoveryZoneComponent } from './pages/config/servers/discovery-zone/discovery-zone.component';
@NgModule({
declarations: [
AppComponent,
RawEditorComponent,
CodeEditorComponent,
ConfigComponent,
QuestsComponent,
GridComponent,
NumberComponent,
TextComponent,
PasswordComponent,
DbsComponent,
CheckboxComponent,
ServersComponent,
ServerComponent,
TemplateComponent,
SelectListComponent,
SelectObjectComponent,
IslandInstanceComponent,
MapComponent,
DiscoveryZoneComponent
],
imports: [
BrowserModule,
AppRoutingModule,
NgbModule,
FormsModule,
LeafletModule.forRoot()
],
providers: [Server, PegjsService],
bootstrap: [AppComponent]
})
export class AppModule {
}

View file

@ -0,0 +1 @@
<div class="code-editor" #codeEditor></div>

View file

@ -0,0 +1 @@

View file

@ -0,0 +1,25 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CodeEditorComponent } from './code-editor.component';
describe('CodeEditorComponent', () => {
let component: CodeEditorComponent;
let fixture: ComponentFixture<CodeEditorComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ CodeEditorComponent ]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CodeEditorComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});

View file

@ -0,0 +1,62 @@
import { Component, ElementRef, OnInit, ViewChild } from '@angular/core';
import * as ace from 'ace-builds';
import 'ace-builds/src-noconflict/mode-json';
import 'ace-builds/src-noconflict/theme-monokai';
import 'ace-builds/src-noconflict/ext-language_tools';
import 'ace-builds/src-noconflict/ext-beautify';
const THEME = 'ace/theme/monokai';
const LANG = 'ace/mode/json';
@Component({
selector: 'code-editor',
templateUrl: './code-editor.component.html',
styleUrls: ['./code-editor.component.scss']
})
export class CodeEditorComponent implements OnInit {
private codeEditor: ace.Ace.Editor;
@ViewChild('codeEditor', {static: true}) private codeEditorElmRef: ElementRef;
constructor() {
}
ngOnInit() {
ace.require('ace/ext/language_tools');
const element = this.codeEditorElmRef.nativeElement;
const editorOptions = CodeEditorComponent.getEditorOptions();
this.codeEditor = ace.edit(element, editorOptions);
this.codeEditor.setTheme(THEME);
this.codeEditor.getSession().setMode(LANG);
this.codeEditor.setShowFoldWidgets(true);
}
// missing property on EditorOptions 'enableBasicAutocompletion' so this is a workaround still using ts
private static getEditorOptions(): Partial<ace.Ace.EditorOptions> & { enableBasicAutocompletion?: boolean; } {
const basicEditorOptions: Partial<ace.Ace.EditorOptions> = {
highlightActiveLine: true,
minLines: 14,
maxLines: Infinity,
};
const extraEditorOptions = {enableBasicAutocompletion: true};
return Object.assign(basicEditorOptions, extraEditorOptions);
}
/**
* @returns - the current editor's content.
*/
public getContent() {
if (this.codeEditor) {
return this.codeEditor.getValue();
}
}
public setContent(text) {
if (this.codeEditor) {
this.codeEditor.setValue(text);
}
}
}

View file

@ -0,0 +1,7 @@
<div class="form-group row">
<label for="check-{{ id }}" class="col-2">{{ label }}</label>
<div class="col-10">
<input id="check-{{ id }}" type="checkbox" class="form-control" (ngModelChange)="checkboxChange.emit($event)" [ngModel]="checkbox">
<small class="form-text text-muted" *ngIf="help">{{ help }}</small>
</div>
</div>

View file

@ -0,0 +1,19 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-checkbox',
templateUrl: './checkbox.component.html'
})
export class CheckboxComponent implements OnInit {
@Input() checkbox: boolean;
@Output() checkboxChange = new EventEmitter();
@Input() label: string;
@Input() id: string;
@Input() help: string;
constructor() { }
ngOnInit() {
}
}

View file

@ -0,0 +1,13 @@
<div class="form-group row">
<label for="num-{{ id }}" class="col-2">{{ label }}</label>
<div class="col-10">
<div class="input-group">
<input id="num-{{ id }}" type="number" class="form-control" (ngModelChange)="numberChange.emit($event)"
[ngModel]="number" min="{{ min }}" max="{{ max }}" step="{{ step }}">
<div class="input-group-append" *ngIf="postButtonText">
<button class="btn btn-outline-primary" (click)="postButton.emit($event)">{{ postButtonText }}</button>
</div>
</div>
<small class="form-text text-muted" *ngIf="help">{{ help }}</small>
</div>
</div>

View file

@ -0,0 +1,24 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-number',
templateUrl: './number.component.html'
})
export class NumberComponent implements OnInit {
@Input() number: number;
@Output() numberChange = new EventEmitter();
@Input() postButtonText: string;
@Output() postButton = new EventEmitter();
@Input() label: string;
@Input() id: string;
@Input() help: string;
@Input() min: number;
@Input() max: number;
@Input() step: number;
constructor() { }
ngOnInit() {
}
}

View file

@ -0,0 +1,13 @@
<div class="form-group row">
<label for="text-{{ id }}" class="col-2">{{ label }}</label>
<div class="col-10">
<div class="input-group">
<input id="text-{{ id }}" type="{{ show ? 'text' : 'password' }}" class="form-control"
(ngModelChange)="passwordChange.emit($event)" [ngModel]="password">
<div class="input-group-append">
<button class="btn btn-warning" (click)="toggleShow()">{{ show ? 'Hide' : 'Show' }}</button>
</div>
</div>
<small class="form-text text-muted" *ngIf="help">{{ help }}</small>
</div>
</div>

View file

@ -0,0 +1,23 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-password',
templateUrl: './password.component.html'
})
export class PasswordComponent implements OnInit {
@Input() password: string;
@Output() passwordChange = new EventEmitter();
@Input() label: string;
@Input() id: string;
@Input() help: string;
private show: boolean = false;
constructor() { }
private toggleShow() {
this.show = !this.show;
}
ngOnInit() {
}
}

View file

@ -0,0 +1,10 @@
<div class="form-group row">
<label for="select-{{ id }}" class="col-2">{{ label }}</label>
<div class="col-10">
<select id="select-{{ id }}" class="form-control" (ngModelChange)="optionChange.emit($event)" [ngModel]="option">
<option *ngIf="withEmptyOption" value=""></option>
<option *ngFor="let option of optionList" value="{{ option }}">{{ option }}</option>
</select>
<small class="form-text text-muted" *ngIf="help">{{ help }}</small>
</div>
</div>

View file

@ -0,0 +1,21 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-select-list',
templateUrl: './select-list.component.html'
})
export class SelectListComponent implements OnInit {
@Input() withEmptyOption: boolean = false;
@Input() optionList: any[];
@Input() option: any;
@Output() optionChange = new EventEmitter();
@Input() label: string;
@Input() id: string;
@Input() help: string;
constructor() { }
ngOnInit() {
}
}

View file

@ -0,0 +1,10 @@
<div class="form-group row">
<label for="select-{{ id }}" class="col-2">{{ label }}</label>
<div class="col-10">
<select id="select-{{ id }}" class="form-control" (ngModelChange)="optionChange.emit($event)" [ngModel]="option">
<option *ngIf="withEmptyOption" value=""></option>
<option *ngFor="let option of optionObject | keyvalue" value="{{ option.key }}">{{ option.value }}</option>
</select>
<small class="form-text text-muted" *ngIf="help">{{ help }}</small>
</div>
</div>

View file

@ -0,0 +1,21 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-select-object',
templateUrl: './select-object.component.html'
})
export class SelectObjectComponent implements OnInit {
@Input() withEmptyOption: boolean = false;
@Input() optionObject: object;
@Input() option: any;
@Output() optionChange = new EventEmitter();
@Input() label: string;
@Input() id: string;
@Input() help: string;
constructor() { }
ngOnInit() {
}
}

View file

@ -0,0 +1,12 @@
<div class="form-group row">
<label for="text-{{ id }}" class="col-2">{{ label }}</label>
<div class="col-10">
<div class="input-group">
<input id="text-{{ id }}" type="text" class="form-control" (ngModelChange)="textChange.emit($event)" [ngModel]="text">
<div class="input-group-append" *ngIf="postButtonText">
<button class="btn btn-outline-primary" (click)="postButton.emit($event)">{{ postButtonText || "click me" }}</button>
</div>
</div>
<small class="form-text text-muted" *ngIf="help">{{ help }}</small>
</div>
</div>

View file

@ -0,0 +1,20 @@
import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';
@Component({
selector: 'app-text',
templateUrl: './text.component.html'
})
export class TextComponent implements OnInit {
@Input() text: string;
@Output() textChange = new EventEmitter();
@Input() postButtonText: string;
@Output() postButton = new EventEmitter();
@Input() label: string;
@Input() id: string;
@Input() help: string;
constructor() { }
ngOnInit() {
}
}

View file

@ -0,0 +1,11 @@
export abstract class BaseConfig {
public BackupMode: string = "off";
public MaxFileHistory: number = 10;
public HttpBackupURL: string = "";
public HttpAPIKey: string = "";
public S3URL: string = "";
public S3AccessKeyId: string = "";
public S3SecretKey: string = "";
public S3BucketName: string = "";
public S3KeyPrefix: string = "";
}

View file

@ -0,0 +1,15 @@
import {Deserialize} from "../util/deserialize.model";
export class BlueprintGeneratedClassModel implements Deserialize {
public blueprint: string;
public deserialize(raw: any): this {
if( raw[0] !== 'BlueprintGeneratedClass') Error('wrong type, expecting BlueprintGeneratedClassModel, got [' + raw[0] + ']!');
this.blueprint = raw[1];
return this;
};
public toJSON(): object {
return [ 'BlueprintGeneratedClass', this.blueprint ];
}
}

View file

@ -0,0 +1,20 @@
import { Deserialize } from "../util/deserialize.model";
export class Coordinate3dStringModel implements Deserialize {
x: number;
y: number;
z: number;
deserialize(input: any): this {
const array = input.split(' ');
if (array.length !== 3) throw Error(`Bad string [${input}] for Coordinate3d`);
this.x = Number.parseFloat(array[0]);
this.y = Number.parseFloat(array[1]);
this.z = Number.parseFloat(array[2]);
return this;
}
toJSON(): string {
return `${this.x} ${this.y} ${this.z}`;
}
}

View file

@ -0,0 +1,34 @@
import { Deserialize } from "../util/deserialize.model";
export class DiscoveryZoneModel implements Deserialize {
id: number;
name: string;
sizeX: number = 0;
sizeY: number = 0;
sizeZ: number = 0;
worldX: number = 0;
worldY: number = 0;
rotation: number = 0;
xp: number = 0;
allowSea: boolean = false;
explorerNoteIndex: number = 0;
bIsManuallyPlaced: boolean = false;
ManualVolumeName: string;
deserialize(input: any): this {
Object.assign(this, input);
return this;
}
toJSON(): object {
let json = Object.assign({}, this);
// Key not shown at all if manually placed.
if (!json.bIsManuallyPlaced) delete json.ManualVolumeName;
return json;
}
}

View file

@ -0,0 +1,118 @@
import { Deserialize } from "../util/deserialize.model";
import { IslandDataModel } from "../../../data/models/island-data.model";
import { Coordinate3dStringModel } from "./coordinate-3d-string.model";
export class IslandInstanceModel implements Deserialize {
name: string;
id: number;
spawnerOverrides: object; // TODO Figure out what this actually does
treasureMapSpawnPoints: Coordinate3dStringModel[];
wildPirateCampSpawnPoints: Coordinate3dStringModel[];
minTreasureQuality: number;
maxTreasureQuality: number;
useNpcVolumesForTreasures: boolean;
useLevelBoundsForTreasures: boolean;
prioritizeVolumesForTreasures: boolean;
islandPoints: number;
islandTreasureBottleSupplyCrateOverrides: string; // TODO figure out what this actually is
islandWidth: number;
islandHeight: number;
singleSpawnPointX: number;
singleSpawnPointY: number;
singleSpawnPointZ: number;
worldX: number;
worldY: number;
rotation: number;
// Specify extra player spawn points. Unique within entire grid
spawnPointRegionOverride: number = -1;
finalNPCLevelMultiplier: number = 1;
finalNPCLevelOffset: number = 0;
instanceTreasureQualityMultiplier: number = 1;
instanceTreasureQualityAddition: number = 0;
// Linked comma separated values normally. Lets just fix that...
public IslandInstanceCustomData: object = {};
public IslandInstanceClientCustomData: object = {};
deserialize(input: any): this {
// Do this before actually assigning to current object
const string1 = input.IslandInstanceCustomDatas1;
delete input.IslandInstanceCustomDatas1;
const string2 = input.IslandInstanceCustomDatas2;
delete input.IslandInstanceCustomDatas2;
const stringClient1 = input.IslandInstanceClientCustomDatas1;
delete input.IslandInstanceClientCustomDatas1;
const stringClient2 = input.IslandInstanceClientCustomDatas2;
delete input.IslandInstanceClientCustomDatas2;
Object.assign(this, input);
// Some minor assumption about how this works, but...
if (string1) {
const array1 = string1.split(',');
const array2 = string2.split(',');
if (array1.length !== array2.length)
throw Error('IslandInstanceCustomDatas unmatched array length');
array1.forEach((i, idx) => this.IslandInstanceCustomData[i] = array2[idx]);
}
if (stringClient1) {
const array1 = stringClient1.split(',');
const array2 = stringClient2.split(',');
if (array1.length !== array2.length)
throw Error('IslandInstanceClientCustomDatas unmatched array length');
array1.forEach((i, idx) => this.IslandInstanceClientCustomData[i] = array2[idx]);
}
if (this.treasureMapSpawnPoints === undefined) this.treasureMapSpawnPoints = [];
this.treasureMapSpawnPoints = this.treasureMapSpawnPoints.map(i => new Coordinate3dStringModel().deserialize(i));
if (this.wildPirateCampSpawnPoints === undefined) this.wildPirateCampSpawnPoints = [];
this.wildPirateCampSpawnPoints = this.wildPirateCampSpawnPoints.map(i => new Coordinate3dStringModel().deserialize(i));
return this;
}
toJSON(): object {
let json = Object.assign({}, this);
// Remove some stupidity from the above
delete json.IslandInstanceCustomData;
delete json.IslandInstanceClientCustomData;
// Remap to separate variables
if (Object.keys(this.IslandInstanceCustomData).length > 0) {
json['IslandInstanceCustomDatas1'] = Object.keys(this.IslandInstanceCustomData).join(',');
json['IslandInstanceCustomDatas2'] = Object.values(this.IslandInstanceCustomData).join(',');
}
if (Object.keys(this.IslandInstanceCustomData).length > 0) {
json['IslandInstanceClientCustomDatas1'] = Object.keys(this.IslandInstanceClientCustomData).join(',');
json['IslandInstanceClientCustomDatas2'] = Object.values(this.IslandInstanceClientCustomData).join(',');
}
if (json.spawnPointRegionOverride === -1) delete json.spawnPointRegionOverride;
if (json.finalNPCLevelMultiplier === 1) delete json.finalNPCLevelMultiplier;
if (json.finalNPCLevelOffset === 0) delete json.finalNPCLevelMultiplier;
if (json.instanceTreasureQualityMultiplier === 1) delete json.instanceTreasureQualityMultiplier;
if (json.instanceTreasureQualityAddition === 0) delete json.instanceTreasureQualityAddition;
return json;
}
static fromIslandData(data: IslandDataModel): IslandInstanceModel {
return new IslandInstanceModel().deserialize({
name: data.name,
spawnerOverrides: data.spawnerOverrides,
treasureMapSpawnPoints: data.treasureMapSpawnPoints,
wildPirateCampSpawnPoints: data.wildPirateCampSpawnPoints,
minTreasureQuality: data.minTreasureQuality,
maxTreasureQuality: data.maxTreasureQuality,
useNpcVolumesForTreasures: data.useNpcVolumesForTreasures,
useLevelBoundsForTreasures: data.useLevelBoundsForTreasures,
prioritizeVolumesForTreasures: data.prioritizeVolumesForTreasures,
islandPoints: data.islandPoints,
islandTreasureBottleSupplyCrateOverrides: data.islandTreasureBottleSupplyCrateOverrides,
islandWidth: data.x,
islandHeight: data.y,
singleSpawnPointX: data.singleSpawnPointX,
singleSpawnPointY: data.singleSpawnPointY,
singleSpawnPointZ: data.singleSpawnPointZ
});
}
}

View file

@ -0,0 +1,37 @@
import { Texture2DModel } from "./texture2D.model";
import { Deserialize } from "../util/deserialize.model";
import { BlueprintGeneratedClassModel } from "./blueprintGeneratedClass.model";
import { QuestPointsOfInterestModel } from "./questPointsOfInterest.model";
export class QuestEntryModel implements Deserialize {
public QuestID: number;
public CompletedIcon: Texture2DModel;
public UncompletedIcon: Texture2DModel;
public QuestName: string;
public QuestDescription: string;
public UnlockFeatNames: string[];
public CompleteGiveEngramClasses: Array<BlueprintGeneratedClassModel>;
public QuestPointsOfInterest: Array<QuestPointsOfInterestModel>;
public deserialize(raw: any): this {
Object.assign(this, raw);
this.CompletedIcon = new Texture2DModel().deserialize(raw.CompletedIcon);
this.UncompletedIcon = new Texture2DModel().deserialize(raw.UncompletedIcon);
// Some Quests dont have an engram on completion
if (this.CompleteGiveEngramClasses !== undefined)
this.CompleteGiveEngramClasses
= this.CompleteGiveEngramClasses.map(i => new BlueprintGeneratedClassModel().deserialize(i));
// Some Quests have no points of interest to complete
if (this.QuestPointsOfInterest !== undefined)
this.QuestPointsOfInterest
= this.QuestPointsOfInterest.map(i => new QuestPointsOfInterestModel().deserialize(i));
return this;
}
public toJSON() {
let json = Object.assign({}, this);
if (this.CompleteGiveEngramClasses === undefined) delete json.CompleteGiveEngramClasses;
if (this.QuestPointsOfInterest === undefined) delete json.QuestPointsOfInterest;
return json;
}
}

View file

@ -0,0 +1,20 @@
import { Deserialize } from "../util/deserialize.model";
import { WorldMapPositionModel } from "./worldMapPosition.model";
import { Texture2DModel } from "./texture2D.model";
export class QuestPointsOfInterestModel implements Deserialize {
public PointOfInterestID: number;
public PointOfInterestName: string;
public UnlockFeatNames: Array<string>;
public WorldMapPosition: WorldMapPositionModel;
public CompletedIcon: Texture2DModel;
public UncompletedIcon: Texture2DModel;
public deserialize(input: any): this {
Object.assign(this, input);
this.WorldMapPosition = new WorldMapPositionModel().deserialize(input.WorldMapPosition);
this.CompletedIcon = new Texture2DModel().deserialize(input.CompletedIcon);
this.UncompletedIcon = new Texture2DModel().deserialize(input.UncompletedIcon);
return this;
}
}

View file

@ -0,0 +1,79 @@
import { Deserialize } from "../util/deserialize.model";
import { ServerSublevelModel } from "./serverSublevel.model";
import { IslandInstanceModel } from "./island-instance.model";
import { DiscoveryZoneModel } from "./discovery-zone.model";
export class ServerModel implements Deserialize {
// General Settings
name: string;
gridX: number;
gridY: number;
isHomeServer: boolean;
utcOffset: number;
// Connection Settings
ip: string;
port: number;
gamePort: number;
seamlessDataPort: number;
// Gameplay Settings
floorZDist: number;
transitionMinZ: number;
serverIslandPointsMultiplier: number;
// Colour & Style Settings
waterColorR: number;
waterColorG: number;
waterColorB: number;
skyStyleIndex: number;
// Override Settings
OceanDinoDepthEntriesOverride: string;
oceanFloatsamCratesOverride: string;
treasureMapLootTablesOverride: string;
OverrideShooterGameModeDefaultGameIni: object;
//Dont render if default/empty
GlobalBiomeSeamlessServerGridPreOffsetValues: string;
GlobalBiomeSeamlessServerGridPreOffsetValuesOceanWater: string;
oceanEpicSpawnEntriesOverrideTemplateName: string;
NPCShipSpawnEntriesOverrideTemplateName: string;
regionOverrides: string;
lastImageOverride: string;
extraSublevels: string[];
totalExtraSublevels: string[];
islandInstances: IslandInstanceModel[];
discoZones: DiscoveryZoneModel[];
spawnRegions;
// Dont render if default/empty
serverTemplateName: string;
sublevels: ServerSublevelModel[];
// Irrelevant, used internally by Grapeshot
//MachineIdTag: string;
// Probably also irrelevant
//AdditionalCmdLineParams: string;
// Used by ServerGridEditor
//islandLocked: boolean;
//discoLocked: boolean;
//pathsLocked: boolean;
// Editing time for ServerGridEditor
//lastModified: string;
deserialize(input: any): this {
Object.assign(this, input);
this.sublevels = this.sublevels.map(i => new ServerSublevelModel().deserialize(i));
this.islandInstances = this.islandInstances.map(i => new IslandInstanceModel().deserialize(i));
this.discoZones = this.discoZones.map(i => new DiscoveryZoneModel().deserialize(i));
return this;
}
}

View file

@ -0,0 +1,40 @@
import { Deserialize } from "../util/deserialize.model";
import { IslandInstanceModel } from "./island-instance.model";
import { ServerModel } from "./server.model";
import { ServerGridModel } from "../serverGrid.model";
import { IslandDataModel } from "../../../data/models/island-data.model";
export class ServerSublevelModel implements Deserialize {
public name: string;
public additionalTranslationX: number;
public additionalTranslationY: number;
public additionalTranslationZ: number = 0;
public additionalRotationPitch: number = 0;
public additionalRotationYaw: number = 0;
public additionalRotationRoll: number = 0;
public id: number;
public landscapeMaterialOverride: number;
deserialize(input: any): this {
return Object.assign(this, input);
}
setupFromIsland(island: IslandInstanceModel, islandData: IslandDataModel, cell: ServerModel, grid: ServerGridModel) {
this.id = island.id;
this.landscapeMaterialOverride = islandData.landscapeMaterialOverride;
this.additionalRotationYaw = island.rotation;
// XY are based on centre of the cell
// Use integers to reduce precision issues.
// 10 Million chosen as is larger than gridSize recommended setting
const multiplier = 10_000_000;
const worldX = island.worldX * multiplier;
const worldY = island.worldY * multiplier;
const gridSize = grid.gridSize * multiplier;
const newX = worldX - (gridSize * cell.gridX) - (gridSize / 2);
this.additionalTranslationX = newX / multiplier;
const newY = worldY - (gridSize * cell.gridY) - (gridSize / 2);
this.additionalTranslationY = newY / multiplier;
}
}

View file

@ -0,0 +1,13 @@
import { Deserialize } from "../util/deserialize.model";
import { ServerModel } from "./server.model";
export class ServerTemplateModel extends ServerModel implements Deserialize {
public templateColorR: number;
public templateColorG: number;
public templateColorB: number;
deserialize(input: any): this {
Object.assign(this, input);
return this;
}
}

View file

@ -0,0 +1,15 @@
import { Deserialize } from "../util/deserialize.model";
export class Texture2DModel implements Deserialize {
public texture: string;
public deserialize(raw: any): this {
if (raw[0] !== 'Texture2D') Error('wrong type, expecting Texture2DModel, got [' + raw[0] + ']!');
this.texture = raw[1];
return this;
};
public toJSON(): object {
return ['Texture2D', this.texture];
}
}

View file

@ -0,0 +1,11 @@
import { Deserialize } from "../util/deserialize.model";
export class WorldMapPositionModel implements Deserialize {
public X: number;
public Y: number;
public deserialize(input: any): this {
Object.assign(this, input);
return this;
}
}

View file

@ -0,0 +1,12 @@
import {Deserialize} from "./util/deserialize.model";
export class DatabaseConnectionModel implements Deserialize {
public Name: string;
public URL: string;
public Port: number;
public Password: string;
deserialize(input: any): this {
return Object.assign(this, input);
}
}

View file

@ -0,0 +1,18 @@
import { PegjsService } from "../services/pegjs.service";
import { QuestEntryModel } from "./atlasData/questEntry.model";
import { Deserialize } from "./util/deserialize.model";
export class GlobalGameplaySetupModel implements Deserialize {
QuestEntries: QuestEntryModel[];
public deserialize(raw: string): this {
const rawData = PegjsService.parse(raw);
Object.assign(this, rawData);
this.QuestEntries = this.QuestEntries.map(i => new QuestEntryModel().deserialize(i));
return this;
}
public toJSON(): string {
return PegjsService.format(this, true);
}
}

View file

@ -0,0 +1,100 @@
import { TribeLogConfigModel } from "./tribeLogConfig.model";
import { SharedLogConfigModel } from "./sharedLogConfig.model";
import { TravelDataConfigModel } from "./travelDataConfig.model";
import { GlobalGameplaySetupModel } from "./GlobalGameplaySetupModel";
import { Deserialize } from "./util/deserialize.model";
import { DatabaseConnectionModel } from "./databaseConnection.model";
import { ServerModel } from "./atlasData/server.model";
import { ServerTemplateModel } from "./atlasData/serverTemplate.model";
export class ServerGridModel implements Deserialize {
// Server Argument Section
// Used internally by Grapeshot
//public BaseServerArgs: string = "";
// Probably also internal to Grapeshot
//public AdditionalCmdLineParams: string = "";
// World Options
public WorldFriendlyName: string = "New Server";
public WorldAtlasId: string = "";
public WorldAtlasPassword: string = "";
public ModIDs: string = "";
// Grid Options
public gridSize: number = 1000;
public totalGridsX: number = 1;
public totalGridsY: number = 1;
public globalTransitionMinZ: number = 0.0;
// Used by ServerGridEditor for Zoom Level, so ignored
// Will be added if present in config when loading.
//public coordsScaling: number = 0.000000000001;
// Image Paths
// Used for generating tile output
//public backgroundImgPath: string = "image.png";
// Used for displaying discovery zones in ServerGridEditor
//public discoZonesImagePath: string = "image.png";
// URL Options
public MetaWorldURL: string = "";
public AuthListURL: string = "";
public MapImageURL: string = "";
// Time Options
public Day0: string = "2019-01-09 20:28:56";
public bUseUTCTime: boolean = false;
public columnUTCOffset: number = 0.0;
public lastImageOverride: string = "0001-01-01T00:00:00";
// Info Options for ServerGridEditor
// public showServerInfo: boolean = false;
// public showDiscoZoneInfo: boolean = false;
// public showShipPathsInfo: boolean = false;
// public showIslandNames: boolean = false;
// public showLines: boolean = false;
// public showBackground: boolean = false;
// S3 Options
public LocalS3URL: string = "";
public LocalS3AccessKeyId: string = "";
public LocalS3SecretKey: string = "";
public LocalS3BucketName: string = "";
public LocalS3Region: string = "";
// ID Generator current values
// Used for individual ship path
public shipPathsIdGenerator: number = 0;
// Used for Discovery Zone & Island IDs
public idGenerator: number = 0;
// Unused as far as can see in ServerGridEditor
public regionsIdGenerator: number = 0;
// Log Configs
public TribeLogConfig: TribeLogConfigModel = new TribeLogConfigModel();
public SharedLogConfig: SharedLogConfigModel = new SharedLogConfigModel();
public TravelDataConfig: TravelDataConfigModel = new TravelDataConfigModel();
// Quest Config
public globalGameplaySetup: GlobalGameplaySetupModel = new GlobalGameplaySetupModel();
public DatabaseConnections: DatabaseConnectionModel[];
public servers: ServerModel[];
public serverTemplates: ServerTemplateModel[];
public deserialize(input: any): this {
Object.assign(this, input);
this.SharedLogConfig = new SharedLogConfigModel().deserialize(input.SharedLogConfig);
this.TribeLogConfig = new TribeLogConfigModel().deserialize(input.TribeLogConfig);
this.TravelDataConfig = new TravelDataConfigModel().deserialize(input.TravelDataConfig);
this.globalGameplaySetup = new GlobalGameplaySetupModel().deserialize(input.globalGameplaySetup);
this.DatabaseConnections = this.DatabaseConnections.map(i => new DatabaseConnectionModel().deserialize(i));
this.servers = this.servers.map(i => new ServerModel().deserialize(i));
this.serverTemplates = this.serverTemplates.map(i => new ServerTemplateModel().deserialize(i));
return this;
}
getNextId() {
return ++this.idGenerator;
}
}

View file

@ -0,0 +1,14 @@
import {BaseConfig} from "./abstract/baseConfig.model";
import { Deserialize } from "./util/deserialize.model";
export class SharedLogConfigModel extends BaseConfig implements Deserialize {
public FetchRateSec: number = 60;
public SnapshotCleanupSec: number = 900;
public SnapshotRateSec: number = 1800;
public SnapshotExpirationHours: number = 48;
public deserialize(input: any): this {
Object.assign(this, input);
return this;
}
}

View file

@ -0,0 +1,10 @@
import {BaseConfig} from "./abstract/baseConfig.model";
import { Deserialize } from "./util/deserialize.model";
export class TravelDataConfigModel extends BaseConfig implements Deserialize {
public deserialize(input: any): this {
Object.assign(this, input);
return this;
}
}

View file

@ -0,0 +1,13 @@
import {BaseConfig} from "./abstract/baseConfig.model";
import {Deserialize} from "./util/deserialize.model";
import {Serialize} from "./util/serialize.model";
import {PegjsService} from "../services/pegjs.service";
export class TribeLogConfigModel extends BaseConfig implements Deserialize {
public MaxRedisEntries: number = 1000;
public deserialize(raw: any): this {
Object.assign(this, raw);
return this;
}
}

View file

@ -0,0 +1,3 @@
export interface Deserialize {
deserialize(input: any): this;
}

View file

@ -0,0 +1,3 @@
export interface Serialize {
serialize(): any;
}

View file

@ -0,0 +1,10 @@
<div class="row">
<div class="col-2 mt-3">
<button class="btn btn-primary btn-block" [routerLink]="['grid']">Grid Settings</button>
<button class="btn btn-primary btn-block" [routerLink]="['dbs']">DB Settings</button>
<button class="btn btn-primary btn-block" [routerLink]="['quests']">Quest Settings</button>
<button class="btn btn-primary btn-block" [routerLink]="['servers']">Server Settings</button>
<button class="btn btn-primary btn-block" [routerLink]="['map']">Map</button>
</div>
<div class="col-10"><router-outlet></router-outlet></div>
</div>

View file

@ -0,0 +1,16 @@
import { Component, OnInit } from '@angular/core';
import { Server } from "../../server";
@Component({
selector: 'app-config',
templateUrl: './config.component.html',
styleUrls: ['./config.component.scss']
})
export class ConfigComponent implements OnInit {
constructor(private server: Server) {
}
ngOnInit() {
}
}

View file

@ -0,0 +1,15 @@
<div class="row">
<div class="col-12 mt-3">
<h2>Database Settings</h2>
<ngb-accordion>
<ngb-panel *ngFor="let db of dbs; let i = index" title="{{ db.Name }}">
<ng-template ngbPanelContent>
<app-text id="db-{{ i }}-name" label="Name" [(text)]="db.Name"></app-text>
<app-text id="db-{{ i }}-URL" label="URL" [(text)]="db.URL"></app-text>
<app-number id="db-{{ i }}-Port" label="Port" [(number)]="db.Port"></app-number>
<app-password id="db-{{ i }}-Password" label="Password" [(password)]="db.Password"></app-password>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>
</div>

View file

@ -0,0 +1,19 @@
import { Component, OnInit } from '@angular/core';
import { Server } from "../../../server";
import { DatabaseConnectionModel } from "../../../models/databaseConnection.model";
@Component({
selector: 'app-dbs',
templateUrl: './dbs.component.html'
})
export class DbsComponent implements OnInit {
dbs: DatabaseConnectionModel[];
constructor(private server: Server) {
this.dbs = server.serverGrid.DatabaseConnections;
}
ngOnInit() {
}
}

View file

@ -0,0 +1,102 @@
<div class="row">
<div class="col-12 mt-3">
<h2>Grid Settings</h2>
<ngb-accordion>
<ngb-panel id="toggle-generalSettings" title="World Settings">
<ng-template ngbPanelContent>
<app-text id="WorldFriendlyName" label="Name" [(text)]="grid.WorldFriendlyName"
help="Name shown on Atlas server listings for your server"></app-text>
<app-text id="WorldAtlasId" label="Atlas ID" [(text)]="grid.WorldAtlasId"
help="Unique ID used for joining multiple servers together in multi-host setups"></app-text>
<app-password id="WorldAtlasPassword" label="Password"
[(password)]="grid.WorldAtlasPassword"
help="Password used for connecting to your Server"></app-password>
<app-text id="ModIDs" label="Mod IDs" [(text)]="grid.ModIDs"
help="Comma separated list of Mod IDs from Steam Workshop"></app-text>
</ng-template>
</ngb-panel>
<ngb-panel id="toggle-gridSettings" title="Grid Size Settings">
<ng-template ngbPanelContent>
<app-number id="gridSize" label="Grid Size" [(number)]="grid.gridSize"
help="Size in Unreal Units per Cell. Recommended default & maximum 1,400,000"></app-number>
<app-number id="totalGridsX" label="Total Grids X" [(number)]="grid.totalGridsX"
help="Total number of Cells in X Direction"></app-number>
<app-number id="totalGridsY" label="Total Grids Y" [(number)]="grid.totalGridsY"
help="Total number of Cells in Y Direction"></app-number>
<app-number id="globalTransitionMinZ" label="Min Z Transition"
[(number)]="grid.globalTransitionMinZ"
help="Lowest Z height for transitions across cell borders"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel id="toggle-urlSettings" title="URL Settings">
<ng-template ngbPanelContent>
<app-text id="MetaWorldURL" label="Meta World URL" [(text)]="grid.MetaWorldURL" help="Used for an in game active map"></app-text>
<app-text id="AuthListURL" label="Auth List URL" [(text)]="grid.AuthListURL"></app-text>
<app-text id="MapImageURL" label="Map Image URL" [(text)]="grid.MapImageURL" help="Used for server listing and if Meta World URL is not defined"></app-text>
</ng-template>
</ngb-panel>
<ngb-panel id="toggle-timeOptions" title="Time Settings">
<ng-template ngbPanelContent>
<app-text id="Day0" label="Day Zero" [(text)]="grid.Day0"></app-text>
<app-checkbox id="bUseUTCTime" label="Use UTC" [(checkbox)]="grid.bUseUTCTime"></app-checkbox>
<app-number id="columnUTCOffset" label="UTC Offset" [(number)]="grid.columnUTCOffset"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel id="toggle-1" title="Local S3 Settings">
<ng-template ngbPanelContent>
<app-text id="LocalS3URL" label="URL" [(text)]="grid.LocalS3URL"></app-text>
<app-text id="LocalS3AccessKeyID" label="Access Key ID"
[(text)]="grid.LocalS3AccessKeyId"></app-text>
<app-password id="LocalS3SecretKey" label="Secret Key"
[(password)]="grid.LocalS3SecretKey"></app-password>
<app-text id="LocalS3BucketName" label="Bucket Name" [(text)]="grid.LocalS3BucketName"></app-text>
<app-text id="LocalS3Region" label="Region" [(text)]="grid.LocalS3Region"></app-text>
</ng-template>
</ngb-panel>
<ngb-panel title="Tribe Log Settings">
<ng-template ngbPanelContent>
<app-number id="Tribe-MaxRedisEntries" label="Max Redis Entries" [(number)]="tribeLog.MaxRedisEntries"></app-number>
<app-text id="Tribe-BackupMode" label="Backup Mode" [(text)]="tribeLog.BackupMode"></app-text>
<app-number id="Tribe-MaxFileHistory" label="Max File History" [(number)]="tribeLog.MaxFileHistory"></app-number>
<app-text id="Tribe-HttpBackupURL" label="Http Backup URL" [(text)]="tribeLog.HttpBackupURL"></app-text>
<app-text id="Tribe-HttpAPIKey" label="HTTP API Key" [(text)]="tribeLog.HttpAPIKey"></app-text>
<app-text id="Tribe-S3URL" label="S3 URL" [(text)]="tribeLog.S3URL"></app-text>
<app-text id="Tribe-S3AccessKeyId" label="S3 Access Key ID" [(text)]="tribeLog.S3AccessKeyId"></app-text>
<app-password id="Tribe-S3SecretKey" label="S3 Secret Key" [(password)]="tribeLog.S3SecretKey"></app-password>
<app-text id="Tribe-S3BucketName" label="S3 Bucket Name" [(text)]="tribeLog.S3BucketName"></app-text>
<app-text id="Tribe-S3KeyPrefix" label="S3 Key Prefix" [(text)]="tribeLog.S3KeyPrefix"></app-text>
</ng-template>
</ngb-panel>
<ngb-panel title="Shared Log Settings">
<ng-template ngbPanelContent>
<app-number id="Shared-FetchRateSec" label="Fetch Rate (s)" [(number)]="sharedLog.FetchRateSec"></app-number>
<app-number id="Shared-SnapshotRateSec" label="Snapshot Rate (s)" [(number)]="sharedLog.SnapshotRateSec"></app-number>
<app-number id="Shared-SnapshotCleanupSec" label="Snapshot Cleanup (s)" [(number)]="sharedLog.SnapshotCleanupSec"></app-number>
<app-number id="Shared-SnapshotExpirationHours" label="Snapshot Expiration (h)" [(number)]="sharedLog.SnapshotExpirationHours"></app-number>
<app-text id="Shared-BackupMode" label="Backup Mode" [(text)]="sharedLog.BackupMode"></app-text>
<app-number id="Shared-MaxFileHistory" label="Max File History" [(number)]="sharedLog.MaxFileHistory"></app-number>
<app-text id="Shared-HttpBackupURL" label="Http Backup URL" [(text)]="sharedLog.HttpBackupURL"></app-text>
<app-text id="Shared-HttpAPIKey" label="HTTP API Key" [(text)]="sharedLog.HttpAPIKey"></app-text>
<app-text id="Shared-S3URL" label="S3 URL" [(text)]="sharedLog.S3URL"></app-text>
<app-text id="Shared-S3AccessKeyId" label="S3 Access Key ID" [(text)]="sharedLog.S3AccessKeyId"></app-text>
<app-password id="Shared-S3SecretKey" label="S3 Secret Key" [(password)]="sharedLog.S3SecretKey"></app-password>
<app-text id="Shared-S3BucketName" label="S3 Bucket Name" [(text)]="sharedLog.S3BucketName"></app-text>
<app-text id="Shared-S3KeyPrefix" label="S3 Key Prefix" [(text)]="sharedLog.S3KeyPrefix"></app-text>
</ng-template>
</ngb-panel>
<ngb-panel title="Travel Data Settings">
<ng-template ngbPanelContent>
<app-text id="Travel-BackupMode" label="Backup Mode" [(text)]="travelData.BackupMode"></app-text>
<app-number id="Travel-MaxFileHistory" label="Max File History" [(number)]="travelData.MaxFileHistory"></app-number>
<app-text id="Travel-HttpBackupURL" label="Http Backup URL" [(text)]="travelData.HttpBackupURL"></app-text>
<app-text id="Travel-HttpAPIKey" label="HTTP API Key" [(text)]="travelData.HttpAPIKey"></app-text>
<app-text id="Travel-S3URL" label="S3 URL" [(text)]="travelData.S3URL"></app-text>
<app-text id="Travel-S3AccessKeyId" label="S3 Access Key ID" [(text)]="travelData.S3AccessKeyId"></app-text>
<app-password id="Travel-S3SecretKey" label="S3 Secret Key" [(password)]="travelData.S3SecretKey"></app-password>
<app-text id="Travel-S3BucketName" label="S3 Bucket Name" [(text)]="travelData.S3BucketName"></app-text>
<app-text id="Travel-S3KeyPrefix" label="S3 Key Prefix" [(text)]="travelData.S3KeyPrefix"></app-text>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>
</div>

View file

@ -0,0 +1,27 @@
import { Component, OnInit } from '@angular/core';
import { ServerGridModel } from "../../../models/serverGrid.model";
import { Server } from "../../../server";
import { TribeLogConfigModel } from "../../../models/tribeLogConfig.model";
import { SharedLogConfigModel } from "../../../models/sharedLogConfig.model";
import { TravelDataConfigModel } from "../../../models/travelDataConfig.model";
@Component({
selector: 'app-grid',
templateUrl: './grid.component.html'
})
export class GridComponent implements OnInit {
private grid: ServerGridModel;
private tribeLog: TribeLogConfigModel;
private sharedLog: SharedLogConfigModel;
private travelData: TravelDataConfigModel;
constructor(private server: Server) {
this.grid = server.serverGrid;
this.tribeLog = server.serverGrid.TribeLogConfig;
this.sharedLog = server.serverGrid.SharedLogConfig;
this.travelData = server.serverGrid.TravelDataConfig;
}
ngOnInit() {
}
}

View file

@ -0,0 +1,7 @@
<div style="height: 600px;"
leaflet
[leafletOptions]="options">
<div *ngFor="let s of serverLayers" [leafletLayer]="s"></div>
<div *ngFor="let i of islandLayers" [leafletLayer]="i"></div>
<div *ngFor="let d of discoveryLayers" [leafletLayer]="d"></div>
</div>

View file

@ -0,0 +1,119 @@
import { Component, OnInit } from '@angular/core';
import { Circle, CRS, ImageOverlay, LatLng, LatLngBounds, Rectangle } from "leaflet";
import { Server } from "../../../server";
import { IslandInstanceModel } from "../../../models/atlasData/island-instance.model";
import * as L from 'leaflet';
import 'leaflet-imageoverlay-rotated';
const RADIAN_RATIO = Math.PI / 180;
@Component({
selector: 'app-map',
templateUrl: './map.component.html',
styleUrls: ['./map.component.scss']
})
export class MapComponent implements OnInit {
options = {
crs: CRS.Simple,
zoom: -16,
maxBounds: undefined,
maxBoundsViscosity: 0.5,
center: undefined,
layers: [],
minZoom: -16,
};
serverLayers = [];
islandLayers = [];
discoveryLayers = [];
maxX: number;
maxY: number;
constructor(private server: Server) {
}
ngOnInit() {
// all in Y X format. Also, upside down! (0,0 is bottom left instead of top left)
// Also, due to insanely large sizes, divide by 100_000
this.maxX = this.server.serverGrid.gridSize * this.server.serverGrid.totalGridsX;
this.maxY = this.server.serverGrid.gridSize * this.server.serverGrid.totalGridsY;
const northEast = new LatLng(0, this.maxX);
const southWest = new LatLng(this.maxY, 0);
this.options.maxBounds = new LatLngBounds(southWest, northEast);
this.options.center = new LatLng(0, 0);
this.options.layers.push(new ImageOverlay('./assets/server-grid-editor/WaterTiles/instinctworld-autoscaling.png', this.options.maxBounds));
this.server.serverGrid.servers.forEach(s => {
// Server Layer
const servMinX = this.server.serverGrid.gridSize * s.gridX;
const servMaxX = servMinX + this.server.serverGrid.gridSize;
const servMinY = this.server.serverGrid.gridSize * s.gridY;
const servMaxY = servMinY + this.server.serverGrid.gridSize;
const servNE = new LatLng(servMinY, servMaxX);
const servSW = new LatLng(servMaxY, servMinX);
const servBounds = new LatLngBounds(servSW, servNE);
this.serverLayers.push(new Rectangle(servBounds,{color: 'black', weight: 1}));
s.islandInstances.forEach(i => {
const imgUrl = this.server.islands.islandData[i.name].imagePath;
const islandLocation = this.islandToImageRotation(i);
this.islandLayers.push(L.imageOverlay.rotated(imgUrl, islandLocation[0], islandLocation[1], islandLocation[2]));
});
s.discoZones.forEach(d => {
if (d.ManualVolumeName !== undefined) return;
const imgUrl = './assets/server-grid-editor/Resources/discoZoneBox.png';
const discoLocation = this.discoveryZoneToImageRotation(d);
this.discoveryLayers.push(L.imageOverlay.rotated(imgUrl, discoLocation[0], discoLocation[1], discoLocation[2]));
});
})
}
gridCoordinateToMapLatLng(gridX: number, gridY: number): LatLng {
// Need to invert gridY for the Latitude.
const newY = this.maxY - gridY;
return new LatLng(newY, gridX);
}
islandToImageRotation(island: IslandInstanceModel): LatLng[] {
return this.gridCoordinateToImageRotation(island.worldX, island.worldY, island.rotation, island.islandWidth, island.islandHeight);
}
discoveryZoneToImageRotation(disco): LatLng[] {
return this.gridCoordinateToImageRotation(disco.worldX, disco.worldY, disco.rotation, disco.sizeX, disco.sizeY);
}
gridCoordinateToImageRotation(worldX, worldY, rotation, width, height): LatLng[] {
const halfWidth = width / 2;
const halfHeight = height / 2;
const radians = (rotation + 90) * RADIAN_RATIO;
const cosR = Math.cos(radians);
const sinR = Math.sin(radians);
// https://gamedev.stackexchange.com/questions/86755/how-to-calculate-corner-positions-marks-of-a-rotated-tilted-rectangle
// Rotate any point around
// Nx = x * cos(a) - y * sin(a)
// Ny = x * sin(a) + y * cos(a)
// REMEMBER everything is upside down
// Top Left -1, 1
const topLeftX = worldX + (-halfWidth * cosR) - (halfHeight * sinR);
const topLeftY = worldY + (-halfWidth * sinR) + (halfHeight * cosR);
// Bottom Left -1, -1 BUT mirrored for some reason so 1, 1
const botLeftX = worldX + (halfWidth * cosR) - (halfHeight * sinR);
const botLeftY = worldY + (halfWidth * sinR) + (halfHeight * cosR);
// Top Right 1, 1 BUT mirrored for some reason so -1, -1
const topRightX = worldX + (-halfWidth * cosR) - (-halfHeight * sinR);
const topRightY = worldY + (-halfWidth * sinR) + (-halfHeight * cosR);
return [
this.gridCoordinateToMapLatLng(topLeftX, topLeftY),
this.gridCoordinateToMapLatLng(topRightX, topRightY),
this.gridCoordinateToMapLatLng(botLeftX, botLeftY)
];
}
}

View file

@ -0,0 +1,67 @@
<div class="row">
<div class="col-12 mt-3 mb-3">
<h2>Quest Entries</h2>
<ngb-accordion>
<ngb-panel *ngFor="let quest of quests; let i = index" id="toggle-{{ i }}" title="{{ quest.QuestName }}">
<ng-template ngbPanelContent>
<app-text id="{{ i }}-QuestName" label="Name" [(text)]="quest.QuestName"></app-text>
<app-number id="{{ i }}-QuestID" label="ID" [(number)]="quest.QuestID"></app-number>
<app-text id="{{ i }}-QuestDescription" label="Description"
[(text)]="quest.QuestDescription"></app-text>
<h6>Feat Unlocks
<button class="btn btn-primary btn-sm" (click)="addQuestFeat(quest)">+</button>
</h6>
<p *ngIf="quest.UnlockFeatNames === undefined">
<em>Click + above to add Feat Unlocks</em>
</p>
<app-text id="{{ i }}-{{ j }}-feat"
*ngFor="let feat of quest.UnlockFeatNames; index as j; trackBy: trackByFn"
label="Unlock {{ j }}" [(text)]="quest.UnlockFeatNames[j]" postButtonText="-"
(postButton)="removeQuestFeat(quest, j)"></app-text>
<h6>Engram Unlocks
<button class="btn btn-primary btn-sm" (click)="addEngramClass(quest)">+</button>
</h6>
<p *ngIf="quest.CompleteGiveEngramClasses === undefined">
<em>Click + above to add Engram Unlocks</em>
</p>
<app-text id="{{ i }}-{{ j }}-engram"
*ngFor="let engram of quest.CompleteGiveEngramClasses; index as j"
label="Engram {{ j }}" [(text)]="engram.blueprint" postButtonText="-"
(postButton)="removeEngramClass(quest, j)"></app-text>
<h6>Points of Interest
<button class="btn btn-primary btn-sm">+</button>
</h6>
<p *ngIf="quest.QuestPointsOfInterest === undefined">
<em>Click + above to add Point of Interest</em>
</p>
<div class="card mb-3" *ngFor="let poi of quest.QuestPointsOfInterest; index as j">
<div class="card-body">
<app-text id="{{ i }}-{{ j }}-poi-PointOfInterestName"
label="Name" [(text)]="poi.PointOfInterestName"></app-text>
<app-number id="{{ i }}-{{ j }}-poi-PointOfInterestID"
label="ID" [(number)]="poi.PointOfInterestID"></app-number>
<app-number id="{{ i }}-{{ j }}-poi-WorldMapPosition-X"
label="Map X Pos" [(number)]="poi.WorldMapPosition.X"></app-number>
<app-number id="{{ i }}-{{ j }}-poi-WorldMapPosition-Y"
label="Map Y Pos" [(number)]="poi.WorldMapPosition.Y"></app-number>
<app-text id="{{ i }}-{{ j }}-poi-CompletedIcon"
label="Completed Icon" [(text)]="poi.CompletedIcon.texture"></app-text>
<app-text id="{{ i }}-{{ j }}-poi-UncompletedIcon"
label="Uncompleted Icon" [(text)]="poi.UncompletedIcon.texture"></app-text>
<h6>Feat Unlocks
<button class="btn btn-primary btn-sm" (click)="addQuestFeat(poi)">+</button>
</h6>
<p *ngIf="poi.UnlockFeatNames === undefined">
<em>Click + above to add Feat Unlocks</em>
</p>
<app-text id="{{ i }}-{{ j }}-={{ k }}feat"
*ngFor="let feat of poi.UnlockFeatNames; index as k; trackBy: trackByFn"
label="Unlock {{ k }}" [(text)]="poi.UnlockFeatNames[k]" postButtonText="-"
(postButton)="removeQuestFeat(poi, k)"></app-text>
</div>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>
</div>

View file

@ -0,0 +1,53 @@
import { Component, OnInit } from '@angular/core';
import { Server } from "../../../server";
import { QuestEntryModel } from "../../../models/atlasData/questEntry.model";
import { BlueprintGeneratedClassModel } from "../../../models/atlasData/blueprintGeneratedClass.model";
@Component({
selector: 'app-quests',
templateUrl: './quests.component.html'
})
export class QuestsComponent implements OnInit {
quests: QuestEntryModel[];
constructor(private server: Server) {
this.quests = server.serverGrid.globalGameplaySetup.QuestEntries;
}
ngOnInit() {
}
// Issue with text arrays and editing jumps on each change.
trackByFn(index: any, item: any) {
return index;
}
private addQuestFeat(quest) {
if (quest.UnlockFeatNames === undefined) {
quest.UnlockFeatNames = [];
}
quest.UnlockFeatNames.push('');
}
private removeQuestFeat(quest, j) {
quest.UnlockFeatNames.splice(j, 1);
if (quest.UnlockFeatNames.length === 0) {
quest.UnlockFeatNames = undefined;
}
}
private addEngramClass(quest) {
if (quest.CompleteGiveEngramClasses === undefined) {
quest.CompleteGiveEngramClasses = [];
}
quest.CompleteGiveEngramClasses.push(new BlueprintGeneratedClassModel());
}
private removeEngramClass(quest, j) {
quest.CompleteGiveEngramClasses.splice(j, 1);
if (quest.CompleteGiveEngramClasses.length === 0) {
quest.UnlockFeatNames = undefined;
}
}
}

View file

@ -0,0 +1,55 @@
<div class="mt-3">
<h2>{{ discovery.name }}
<button class="btn btn-primary btn-sm" [routerLink]="['/config', 'server', cellIndex]">Back to Cell</button>
</h2>
<p>{{ discovery.sizeX }} by {{ discovery.sizeY }} by {{ discovery.sizeZ }}</p>
<ngb-accordion>
<ngb-panel title="General Settings">
<ng-template ngbPanelContent>
<app-text id="name" label="Name" [(text)]="discovery.name"></app-text>
<app-number id="worldX" label="World X" [(number)]="discovery.worldX"></app-number>
<app-number id="worldY" label="World Y" [(number)]="discovery.worldY"></app-number>
<app-number id="rotation" label="Rotation" [(number)]="discovery.rotation" step="0.000001"
min="{{ minRot }}" max="{{ maxRot }}"></app-number>
<h4>Zone Size</h4>
<app-number id="sizeX" label="Size X" [(number)]="discovery.sizeX"></app-number>
<app-number id="sizeY" label="Size Y" [(number)]="discovery.sizeY"></app-number>
<app-number id="sizeZ" label="Size Z" [(number)]="discovery.sizeZ"
help="If the discovery zone has any size, this is normally 40,000"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Discovery Settings">
<ng-template ngbPanelContent>
<app-number id="xp" label="XP" [(number)]="discovery.xp"
help="XP Gained for discovering this zone. Note this also alters the max XP you can gain by levelling"></app-number>
<app-checkbox id="allowSea" label="Allow Sea Discovery" [(checkbox)]="discovery.allowSea"
help="May not do what you think - Always false on Official Server"></app-checkbox>
<app-select-object id="explorerNoteIndex" label="Explorer Note" [optionObject]="explorerNotes"
[option]="discovery.explorerNoteIndex" (optionChange)="setExplorerNote($event)"
help="Opens a page in the players Discoveries menu on unlocking"></app-select-object>
</ng-template>
</ngb-panel>
<ngb-panel title="Manual Volumes">
<ng-template ngbPanelContent>
<p>Manual Volumes are either specific points on a map, or a particular 'discovery' available in a
cell. For example, certain Forests and Pools on an island are discovery zones in the official
server. Also the Powerstone locations are a discovery point, however the names for these aren't tied
to a specific island.</p>
<p>When a player enters the area of the 'Volume' or otherwise triggers it (in the case of Powerstones),
the discovery zone will be unlocked. This happens whether the player is on land, gliding, or
swimming. This only happens with these Manual Volume Discoveries - Normal discovery zones require
you to set foot on the area.</p>
<p>If the Zone size (under general settings) is set to 0 for XYZ, then no shimmer will show around the
discovery. Any other size will show a shimmer of that set size, and location, in game.</p>
<p>Note as well, the location of a Manual Volume discovery doesnt really matter, however it defaults to
the center of the cell.</p>
<app-checkbox id="bIsManuallyPlaced" label="Enable Manual Volume"
[(checkbox)]="discovery.bIsManuallyPlaced"></app-checkbox>
<app-select-list id="ManualVolumeName" label="Manual Volume Name" *ngIf="discovery.bIsManuallyPlaced"
[optionList]="discoveryVolumeNameOptionKeys"
[option]="discovery.ManualVolumeName" (optionChange)="setDiscoveryVolumeName($event)"></app-select-list>
<button class="btn btn-warning" (click)="setLocationCenterOfCell()">Set Center to Cell Center</button>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>

View file

@ -0,0 +1,69 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Server } from "../../../../server";
import { ServerModel } from "../../../../models/atlasData/server.model";
import { DiscoveryZoneModel } from "../../../../models/atlasData/discovery-zone.model";
import * as ExplorerNotes from '../../../../../data/explorer-notes.json';
@Component({
selector: 'app-discovery-zone',
templateUrl: './discovery-zone.component.html'
})
export class DiscoveryZoneComponent implements OnInit {
private cell: ServerModel;
private cellIndex;
private discovery: DiscoveryZoneModel;
private discoIndex;
private minX: number;
private maxX: number;
private minY: number;
private maxY: number;
private minRot: number = 0;
private maxRot: number = 360;
private explorerNotes;
private discoveryVolumeNameOptions: object = {};
private discoveryVolumeNameOptionKeys: string[] = [];
constructor(private server: Server, private route: ActivatedRoute) {
}
ngOnInit() {
this.cellIndex = this.route.snapshot.paramMap.get('index');
this.discoIndex = this.route.snapshot.paramMap.get('discoIndex');
this.cell = this.server.serverGrid.servers[this.cellIndex];
this.discovery = this.cell.discoZones[this.discoIndex];
this.minX = this.cell.gridX * this.server.serverGrid.gridSize;
this.maxX = this.minX + this.server.serverGrid.gridSize;
this.minY = this.cell.gridY * this.server.serverGrid.gridSize;
this.maxY = this.minY + this.server.serverGrid.gridSize;
this.explorerNotes = (ExplorerNotes as any).default;
this.cell.islandInstances.forEach(i => {
const islandData = this.server.islands.islandData[i.name];
if (islandData.discoveryVolumeNames.length === 0 ) return;
islandData.discoveryVolumeNames.forEach(
i => {
this.discoveryVolumeNameOptions[i.name] = i;
this.discoveryVolumeNameOptionKeys.push(i.name);
}
)
});
this.discoveryVolumeNameOptionKeys = this.discoveryVolumeNameOptionKeys.sort();
}
private setExplorerNote(option: string) {
this.discovery.explorerNoteIndex = parseInt(option);
}
private setDiscoveryVolumeName(option: string) {
console.log(this.discoveryVolumeNameOptions[option]);
}
private setLocationCenterOfCell() {
this.discovery.worldX = this.minX + ( this.server.serverGrid.gridSize / 2 );
this.discovery.worldY = this.minY + ( this.server.serverGrid.gridSize / 2 );
}
}

View file

@ -0,0 +1,93 @@
<div class="mt-3">
<h2>{{ islandIndex }} - {{ server.islands[island.name].customDisplayName() }}
<button class="btn btn-primary btn-sm" [routerLink]="['/config', 'server', cellIndex]">Back to Cell</button>
</h2>
<p>{{ island.islandWidth }} by {{ island.islandHeight }}</p>
<ngb-accordion>
<ngb-panel title="General Settings">
<ng-template ngbPanelContent>
<app-number id="worldX" label="World X" [(number)]="island.worldX"></app-number>
<app-number id="worldY" label="World Y" [(number)]="island.worldY"></app-number>
<app-number id="rotation" label="Rotation" [(number)]="island.rotation" step="0.000001" min="{{ minRot }}" max="{{ maxRot }}"></app-number>
<app-number id="islandPoints" label="Island Points" [(number)]="island.islandPoints"></app-number>
<app-number id="spawnPointRegionOverride" label="Spawn Override"
[(number)]="island.spawnPointRegionOverride" min="-1"
help="Default -1. Set to 0 or more to allow for this island to be spawned on. Must be a unique, consecutive number across the server"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Treasure Settings">
<ng-template ngbPanelContent>
<app-number id="minTreasureQuality" label="Min Quality" [(number)]="island.minTreasureQuality"
help="Default -1. -1 means no limit"></app-number>
<app-number id="maxTreasureQuality" label="Max Quality" [(number)]="island.maxTreasureQuality"
help="Default -1. -1 means no limit"></app-number>
<app-checkbox id="useNpcVolumesForTreasures" label="Use NPC Volumes"
[(checkbox)]="island.useNpcVolumesForTreasures"
help="useNpcVolumesForTreasures"></app-checkbox>
<app-checkbox id="useLevelBoundsForTreasures" label="Use Level Bounds"
[(checkbox)]="island.useLevelBoundsForTreasures"
help="useLevelBoundsForTreasures"></app-checkbox>
<app-checkbox id="prioritizeVolumesForTreasures" label="Prioritise Volumes"
[(checkbox)]="island.prioritizeVolumesForTreasures"
help="prioritizeVolumesForTreasures"></app-checkbox>
<app-number id="instanceTreasureQualityMultiplier" label="Quality Multiplier"
[(number)]="island.instanceTreasureQualityMultiplier" min="0" step="0.000001"
help="Default 1"></app-number>
<app-number id="instanceTreasureQualityAddition" label="Quality Addition"
[(number)]="island.instanceTreasureQualityAddition" min="0" step="0.000001"
help="Default 0"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Spawn Settings">
<ng-template ngbPanelContent>
<app-number id="finalNPCLevelMultiplier" label="Level Multiplier"
[(number)]="island.finalNPCLevelMultiplier" min="0" step="0.000001"
help="Default 1"></app-number>
<app-number id="finalNPCLevelOffset" label="Level Addition"
[(number)]="island.finalNPCLevelOffset" min="0" step="0.000001"
help="Default 0"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Single Player Settings">
<ng-template ngbPanelContent>
<app-number id="singleSpawnPointX" label="X" [(number)]="island.singleSpawnPointX"></app-number>
<app-number id="singleSpawnPointY" label="Y" [(number)]="island.singleSpawnPointY"></app-number>
<app-number id="singleSpawnPointZ" label="Z" [(number)]="island.singleSpawnPointZ"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Treasure Locations">
<ng-template ngbPanelContent>
<div class="row" *ngFor="let treasurePos of island.treasureMapSpawnPoints; let i = index">
<div class="col-4">
<app-number id="{{ i }}-treasure-x" label="X" [(number)]="treasurePos.x"
step="0.000001"></app-number>
</div>
<div class="col-4">
<app-number id="{{ i }}-treasure-y" label="Y" [(number)]="treasurePos.y"
step="0.000001"></app-number>
</div>
<div class="col-4">
<app-number id="{{ i }}-treasure-z" label="Z" [(number)]="treasurePos.z" step="0.000001"
postButtonText="-" (postButton)="removeTreasureLocation(i)"></app-number>
</div>
</div>
</ng-template>
</ngb-panel>
<ngb-panel title="Pirate Camp Locations">
<ng-template ngbPanelContent>
<div class="row" *ngFor="let campPos of island.wildPirateCampSpawnPoints; let i = index">
<div class="col-4">
<app-number id="{{ i }}-camp-x" label="X" [(number)]="campPos.x" step="0.000001"></app-number>
</div>
<div class="col-4">
<app-number id="{{ i }}-camp-y" label="Y" [(number)]="campPos.y" step="0.000001"></app-number>
</div>
<div class="col-4">
<app-number id="{{ i }}-camp-z" label="Z" [(number)]="campPos.z" step="0.000001"
postButtonText="-" (postButton)="removeCampLocation(i)"></app-number>
</div>
</div>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>

View file

@ -0,0 +1,51 @@
import { Component, OnInit } from '@angular/core';
import { Server } from "../../../../server";
import { ServerModel } from "../../../../models/atlasData/server.model";
import { IslandInstanceModel } from "../../../../models/atlasData/island-instance.model";
import { ActivatedRoute } from "@angular/router";
@Component({
selector: 'app-island-instance',
templateUrl: './island-instance.component.html'
})
export class IslandInstanceComponent implements OnInit {
private cell: ServerModel;
private cellIndex;
private island: IslandInstanceModel;
private islandIndex;
private minX: number;
private maxX: number;
private minY: number;
private maxY: number;
private minRot: number = 0;
private maxRot: number = 360;
constructor(private server: Server, private route: ActivatedRoute) {
}
ngOnInit() {
this.cellIndex = this.route.snapshot.paramMap.get('index');
this.islandIndex = this.route.snapshot.paramMap.get('islandIndex');
this.cell = this.server.serverGrid.servers[this.cellIndex];
this.island = this.cell.islandInstances[this.islandIndex];
this.minX = this.cell.gridX * this.server.serverGrid.gridSize;
this.maxX = this.minX + this.server.serverGrid.gridSize;
this.minY = this.cell.gridY * this.server.serverGrid.gridSize;
this.maxY = this.minY + this.server.serverGrid.gridSize;
}
// TODO Need to update subLevels in parent Cell when updating X and Y...
// Issue with text arrays and editing jumps on each change.
trackByFn(index: any, item: any) {
return index;
}
private removeTreasureLocation(index: number) {
this.island.treasureMapSpawnPoints.splice(index, 1);
}
private removeCampLocation(index: number) {
this.island.wildPirateCampSpawnPoints.splice(index, 1);
}
}

View file

@ -0,0 +1,105 @@
<div class="mt-3">
<h2>{{ cell.name }}<em *ngIf="cell.name === '' || cell.name === undefined">No Name Set</em> - ({{ cell.gridX }}
, {{ cell.gridY }})</h2>
<ngb-accordion>
<ngb-panel title="General Settings">
<ng-template ngbPanelContent>
<app-text id="name" label="Name" [(text)]="cell.name"
help="Friendly name for this server. Can be blank"></app-text>
<app-number id="gridX" label="Grid X" [(number)]="cell.gridX"
help="X position in the Grid. 0 Indexed"></app-number>
<app-number id="gridY" label="Grid Y" [(number)]="cell.gridY"
help="Y position in the Grid. 0 Indexed"></app-number>
<app-checkbox id="isHomeServer" label="Home Server" [(checkbox)]="cell.isHomeServer"
help="Whether this cell can be used for players with no beds"></app-checkbox>
<app-number id="utcOffset" label="UTC Offset" [(number)]="cell.utcOffset"
help="Cell specific UTC offset"></app-number>
<app-select-list id="serverTemplate" label="Template" [optionList]="templateOptions"
[withEmptyOption]="true" [(option)]="cell.serverTemplateName"
help="Set base Server Template for Common Options"></app-select-list>
</ng-template>
</ngb-panel>
<ngb-panel title="Connection Settings">
<ng-template ngbPanelContent>
<app-text id="ip" label="IP" [(text)]="cell.ip" help="IP address of host this cell is on"></app-text>
<app-number id="port" label="Port" [(number)]="cell.port"
help="Port used for inter cell communication"></app-number>
<app-number id="gamePort" label="Game Port" [(number)]="cell.gamePort"
help="Port used for external UE4 traffic"></app-number>
<app-number id="seamlessDataPort" label="Seamless Port" [(number)]="cell.seamlessDataPort"
help="Used for transferring map images when no MetaWorldURL set"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Gameplay Settings">
<ng-template ngbPanelContent>
<app-number id="floorZDist" label="Z Floor Height" [(number)]="cell.floorZDist"></app-number>
<app-number id="transitionMinZ" label="Minimum Z Transition"
[(number)]="cell.transitionMinZ"></app-number>
<app-number id="serverIslandPointsMultiplier" label="Island Points Multiplier"
[(number)]="cell.serverIslandPointsMultiplier" help="Default 1.0"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Colour & Style Settings">
<ng-template ngbPanelContent>
<app-number id="waterColorR" label="Water Red" [(number)]="cell.waterColorR"></app-number>
<app-number id="waterColorG" label="Water Green" [(number)]="cell.waterColorG"></app-number>
<app-number id="waterColorB" label="Water Blue" [(number)]="cell.waterColorB"></app-number>
<app-number id="skyStyleIndex" label="Sky Style Index" [(number)]="cell.skyStyleIndex"></app-number>
</ng-template>
</ngb-panel>
<ngb-panel title="Island Settings">
<ng-template ngbPanelContent>
<p><em>Note that Sublevel settings are sorted in the background</em></p>
<table class="table table-hover">
<thead>
<tr>
<th>Name & Model</th>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td>
<app-select-object id="new-island" label="New Island" [optionObject]="islandOptions" [withEmptyOption]="true"
[(option)]="newIslandName"></app-select-object>
</td>
<td><button class="btn btn-warning" (click)="addNewIsland()" [disabled]="!newIslandName">Add</button></td>
</tr>
<tr *ngFor="let island of cell.islandInstances; let i = index">
<td>
<app-select-object id="{{ i }}-island-name" label="{{ island.name }}" [optionObject]="islandOptions"
[option]="island.name" (optionChange)="changeIslandModel(i, $event)"></app-select-object>
</td>
<td><button class="btn btn-primary" [routerLink]="['island', i]">Edit</button></td>
<td><button class="btn btn-danger" (click)="deleteIsland(i)">Delete</button></td>
</tr>
</tbody>
</table>
</ng-template>
</ngb-panel>
<ngb-panel title="Discovery Zone Settings">
<ng-template ngbPanelContent>
<table class="table table-hover">
<thead>
<tr>
<th>Name</th>
<th>Discovery Type</th>
<th><button class="btn btn-sm btn-warning" (click)="addNewDiscovery()">Add</button></th>
<th></th>
</tr>
</thead>
<tbody>
<tr *ngFor="let disco of cell.discoZones; let i = index">
<th>{{ disco.name }}</th>
<td>{{ disco.bIsManuallyPlaced ? "Location" : "Island" }}</td>
<td><button class="btn btn-sm btn-primary" [routerLink]="['discovery', i]">Edit</button></td>
<td><button class="btn btn-sm btn-danger" (click)="deleteDiscovery(i)">Delete</button></td>
</tr>
</tbody>
</table>
</ng-template>
</ngb-panel>
</ngb-accordion>
</div>

View file

@ -0,0 +1,106 @@
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from "@angular/router";
import { Server } from "../../../../server";
import { ServerModel } from "../../../../models/atlasData/server.model";
import { IslandDataModel } from "../../../../../data/models/island-data.model";
import { IslandInstanceModel } from "../../../../models/atlasData/island-instance.model";
import { ServerSublevelModel } from "../../../../models/atlasData/serverSublevel.model";
import { DiscoveryZoneModel } from "../../../../models/atlasData/discovery-zone.model";
@Component({
selector: 'app-server',
templateUrl: './server.component.html'
})
export class ServerComponent implements OnInit {
private cell: ServerModel;
private templateOptions: string[];
private islandOptions: object = {};
private newIslandName: string;
constructor(private server: Server, private route: ActivatedRoute) {
}
ngOnInit() {
let index = this.route.snapshot.paramMap.get('index');
this.cell = this.server.serverGrid.servers[index];
this.templateOptions = this.server.serverGrid.serverTemplates.map(i => i.name);
Object.values(this.server.islands.islandData).forEach((i: IslandDataModel) => {this.islandOptions[i.name] = i.customDisplayName()});
}
private changeIslandModel(index: number, name: string) {
let oldIsland = this.cell.islandInstances[index];
let newIsland = IslandInstanceModel.fromIslandData(this.server.islands[name]);
newIsland.id = this.server.serverGrid.getNextId();
// keep X, Y and Rotation the same
newIsland.worldX = oldIsland.worldX;
newIsland.worldY = oldIsland.worldY;
newIsland.rotation = oldIsland.rotation;
this.removeSublevelsForIsland(oldIsland);
this.addSublevelsForIsland(newIsland);
this.cell.islandInstances[index] = newIsland;
this.updateExtraSublevels();
}
private removeSublevelsForIsland(island: IslandInstanceModel) {
const islandData = this.server.islands[island.name];
islandData.sublevelNames.forEach(i => {
const j = this.cell.sublevels.findIndex(sublevel => sublevel.name === i);
if (j > -1) {
this.cell.sublevels.splice(j, 1);
}
});
}
private addSublevelsForIsland(island: IslandInstanceModel) {
const islandData = this.server.islands[island.name];
islandData.sublevelNames.forEach(i => {
let newSublevel = new ServerSublevelModel();
newSublevel.name = i;
newSublevel.setupFromIsland(island, islandData, this.cell, this.server.serverGrid);
this.cell.sublevels.push(newSublevel);
});
}
private updateExtraSublevels() {
// Extra Sublevels can be identical across multiple islands,
// so need to merge any changes. Easiest to just merge through
// all of the available islands instead.
this.cell.totalExtraSublevels = [];
this.cell.islandInstances.forEach(i => {
const islandData = this.server.islands[i.name];
islandData.extraSublevels.forEach(j => {
if (this.cell.totalExtraSublevels.indexOf(j) === -1) {
this.cell.totalExtraSublevels.push(j);
}
});
});
}
addNewIsland() {
const newIslandData = this.server.islands[this.newIslandName];
let newIsland = IslandInstanceModel.fromIslandData(newIslandData);
newIsland.id = this.server.serverGrid.getNextId();
this.addSublevelsForIsland(newIsland);
this.cell.islandInstances.push(newIsland);
this.updateExtraSublevels();
}
deleteIsland(index: number) {
let oldIsland = this.cell.islandInstances[index];
this.removeSublevelsForIsland(oldIsland);
this.cell.islandInstances.splice(index, 1);
this.updateExtraSublevels();
}
addNewDiscovery() {
let newDisco = new DiscoveryZoneModel();
newDisco.id = this.server.serverGrid.getNextId();
this.cell.discoZones.push(newDisco);
}
deleteDiscovery(index: number) {
this.cell.discoZones.splice(index, 1);
}
}

View file

@ -0,0 +1,26 @@
<div class="mt-3 mb-3">
<h2>Cell Grid</h2>
<table class="table">
<thead>
<tr>
<th>#</th>
<th *ngFor="let x of xArray">{{ x }}</th>
</tr>
</thead>
<tbody *ngFor="let y of xArray">
<tr>
<th>{{ y }}</th>
<td *ngFor="let x of xArray">
<button class="btn btn-sm btn-outline-primary" [routerLink]="['/config','server', serverLookup[x][y]]" *ngIf="serverLookup[x][y] !== undefined">{{ serverLookup[x][y] }}</button>
<button class="btn btn-sm btn-outline-danger" (click)="addNewServer(x, y)" *ngIf="serverLookup[x][y] === undefined">+</button>
</td>
</tr>
</tbody>
</table>
<h2 *ngIf="unknownServers.length > 0">Cells Outside Grid</h2>
<button class="btn btn-warning" *ngFor="let u of unknownServers">{{ u }}</button>
<h2>Server Templates</h2>
<p>Allows for modifying or applying settings to multiple servers at the same time. Mostly useful for environmental
changes, rather than Island position</p>
<button class="btn btn-primary" *ngFor="let template of grid.serverTemplates">{{ template.name }}</button>
</div>

View file

@ -0,0 +1,49 @@
import { Component, OnInit } from '@angular/core';
import { Server } from "../../../server";
import { ServerGridModel } from "../../../models/serverGrid.model";
import { ServerModel } from "../../../models/atlasData/server.model";
import { Router } from "@angular/router";
@Component({
selector: 'app-servers',
templateUrl: './servers.component.html'
})
export class ServersComponent implements OnInit {
private grid: ServerGridModel;
private xArray: number[];
private yArray: number[];
private serverLookup: number[][] = [];
private unknownServers: number[] = [];
constructor(private server: Server, private router: Router) {
this.grid = server.serverGrid;
this.xArray = Array.from(Array(this.grid.totalGridsX).keys());
this.yArray = Array.from(Array(this.grid.totalGridsY).keys());
// Initialise the serverLookup with x at least
this.xArray.forEach(i => this.serverLookup[i] = []);
this.grid.servers.forEach((i, index) => {
if (i.gridX < this.grid.totalGridsX && i.gridY < this.grid.totalGridsY) {
if (this.serverLookup[i.gridX] === undefined)
this.serverLookup[i.gridX] = [];
this.serverLookup[i.gridX][i.gridY] = index;
} else {
this.unknownServers.push(index);
}
});
}
ngOnInit() {
}
private addNewServer(x: number, y: number) {
let newServer = new ServerModel();
newServer.gridX = x;
newServer.gridY = y;
const newLength = this.grid.servers.push(newServer);
return this.router.navigate(['/config', 'server', newLength - 1]);
}
}

View file

@ -0,0 +1 @@
<p>template works!</p>

View file

@ -0,0 +1,14 @@
import { Component, OnInit } from '@angular/core';
@Component({
selector: 'app-template',
templateUrl: './template.component.html'
})
export class TemplateComponent implements OnInit {
constructor() { }
ngOnInit() {
}
}

View file

@ -0,0 +1,8 @@
<div class="row">
<div class="col-12">
<h2>Raw Editor</h2>
<textarea [(ngModel)]="rawData" rows="20" style="width: 100%"></textarea>
<button (click)="loadData()">Load from JSON</button>
<button (click)="saveData()">Save to JSON</button>
</div>
</div>

View file

@ -0,0 +1,21 @@
import { Component } from "@angular/core";
import { Server } from "../../server";
@Component({
templateUrl: "./raw-editor.component.html",
})
export class RawEditorComponent {
rawData = "{}";
constructor(private server: Server) {
this.server = server;
}
private loadData() {
this.server.load(this.rawData);
}
private saveData() {
this.rawData = this.server.save();
}
}

30
src/app/server.ts Normal file
View file

@ -0,0 +1,30 @@
import { Injectable } from '@angular/core';
import { ServerGridModel } from "./models/serverGrid.model";
import { Islands } from "../data/models/islands.model";
@Injectable()
export class Server {
private rawData: string = "";
private jsonData: object = {};
public serverGrid: ServerGridModel = new ServerGridModel();
public islands: Islands;
constructor() {
this.islands = new Islands();
}
public load(raw) {
this.rawData = raw;
try {
this.jsonData = JSON.parse(this.rawData);
this.serverGrid = new ServerGridModel().deserialize(this.jsonData);
} catch (e) {
console.log(e);
}
}
public save() {
this.rawData = JSON.stringify(this.serverGrid, null, 2);
return this.rawData;
}
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,137 @@
import { Injectable } from '@angular/core';
import { parse } from './atlas.pegjs';
const beginObject = '(';
const endObject = ')';
const nameSeparator = '=';
const valueSeparator = ',';
@Injectable({
providedIn: 'root'
})
export class PegjsService {
constructor() {}
public static parse(input: string): object {
let output = {};
try {
output = parse(input);
} catch (e) {
// TODO put a popover somewhere with a location of the error
console.log(e);
console.log(e.location);
}
return output;
}
public static format(input: object, skipCurrentObject?: boolean): string {
return PegjsService.deparseJson(input, undefined, skipCurrentObject)
}
private static deparseJson(input, key?, skip?: boolean) {
// Call possible toJSON on object
if(!skip && typeof input === 'object' && typeof input.toJSON === 'function') {
input = input.toJSON();
}
switch (typeof input) {
case "object":
return PegjsService.deparseObject(input);
case "number":
return PegjsService.deparseNumber(input, key);
case "string":
return PegjsService.deparseString(input);
case "undefined":
return "";
case "boolean":
return PegjsService.deparseBoolean(input);
default:
console.log("Unknown type for input", input);
return "";
}
}
private static deparseObject(input) {
if (Array.isArray(input)) {
return this.deparseArray(input);
}
if (input === null) {
return "None";
}
let output = '';
output += beginObject;
Object.keys(input).forEach(function (key, idx, array) {
output += key;
output += nameSeparator;
output += PegjsService.deparseJson(input[key], key);
if (idx !== array.length - 1) {
// Not the last item
output += valueSeparator
}
});
output += endObject;
return output;
}
private static deparseArray(input) {
// We have one funky array option, where its 2 values and one of them is a recognised class.
// These are here for easier maintenance, as adding any new ones should just work. These all
// Work off string equality, see found below.
const specialCases = [
'BlueprintGeneratedClass',
'Blueprint',
'SoundWave',
'Texture2D'
];
const found = specialCases.indexOf(input[0]);
if (found > -1) {
// We found one of our special cases!
const val = specialCases[found];
return val + "'" + input[1] + "'";
}
let output = '';
output += beginObject;
input.forEach(function (data, idx, array) {
output += PegjsService.deparseJson(data);
if (idx !== array.length - 1) {
output += valueSeparator;
}
});
output += endObject;
return output;
}
private static deparseNumber(input, key) {
// Any number which has a decimal is stored to 6 decimal places
// Except for IDs. Unless they actually have such a number... erm...
if (key.substr(key.length - 2, 2) === "ID") {
if (input % 1 === 0) {
return Number(input).toString();
}
// Else drop out and return as normal
}
return Number(input).toFixed(6);
}
private static deparseString(input) {
// We need to re-encode the strings properly
return JSON.stringify(input);
}
private static deparseBoolean(input) {
if ( input ) {
return 'True';
} else {
return 'False';
}
}
}

0
src/assets/.gitkeep Normal file
View file

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 158 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 141 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 140 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 135 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 132 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 136 KiB

@ -0,0 +1 @@
Subproject commit cffac3d0dff6391908bde5d2180efb6bbc76a101

Some files were not shown because too many files have changed in this diff Show more