Merge pull request #44 from BenjaminGreene/location-plugin
Implement mod to provide an API to allow player position to be queried
This commit is contained in:
23 files changed
+1397
-19
No files matched your search
@@ -0,0 +1,405 @@
|
|||||||
|
## Ignore Visual Studio temporary files, build results, and
|
||||||
|
## files generated by popular Visual Studio add-ons.
|
||||||
|
##
|
||||||
|
## Get latest from https://github.com/github/gitignore/blob/main/VisualStudio.gitignore
|
||||||
|
|
||||||
|
# User-specific files
|
||||||
|
*.rsuser
|
||||||
|
*.suo
|
||||||
|
*.user
|
||||||
|
*.userosscache
|
||||||
|
*.sln.docstates
|
||||||
|
|
||||||
|
# User-specific files (MonoDevelop/Xamarin Studio)
|
||||||
|
*.userprefs
|
||||||
|
|
||||||
|
# Mono auto generated files
|
||||||
|
mono_crash.*
|
||||||
|
|
||||||
|
# Build results
|
||||||
|
[Dd]ebug/
|
||||||
|
[Dd]ebugPublic/
|
||||||
|
[Rr]elease/
|
||||||
|
[Rr]eleases/
|
||||||
|
x64/
|
||||||
|
x86/
|
||||||
|
[Ww][Ii][Nn]32/
|
||||||
|
[Aa][Rr][Mm]/
|
||||||
|
[Aa][Rr][Mm]64/
|
||||||
|
bld/
|
||||||
|
[Bb]in/
|
||||||
|
[Oo]bj/
|
||||||
|
[Ll]og/
|
||||||
|
[Ll]ogs/
|
||||||
|
|
||||||
|
# Visual Studio 2015/2017 cache/options directory
|
||||||
|
.vs/
|
||||||
|
# Uncomment if you have tasks that create the project's static files in wwwroot
|
||||||
|
#wwwroot/
|
||||||
|
|
||||||
|
# Visual Studio 2017 auto generated files
|
||||||
|
Generated\ Files/
|
||||||
|
|
||||||
|
# MSTest test Results
|
||||||
|
[Tt]est[Rr]esult*/
|
||||||
|
[Bb]uild[Ll]og.*
|
||||||
|
|
||||||
|
# NUnit
|
||||||
|
*.VisualState.xml
|
||||||
|
TestResult.xml
|
||||||
|
nunit-*.xml
|
||||||
|
|
||||||
|
# Build Results of an ATL Project
|
||||||
|
[Dd]ebugPS/
|
||||||
|
[Rr]eleasePS/
|
||||||
|
dlldata.c
|
||||||
|
|
||||||
|
# Benchmark Results
|
||||||
|
BenchmarkDotNet.Artifacts/
|
||||||
|
|
||||||
|
# .NET Core
|
||||||
|
project.lock.json
|
||||||
|
project.fragment.lock.json
|
||||||
|
artifacts/
|
||||||
|
|
||||||
|
# ASP.NET Scaffolding
|
||||||
|
ScaffoldingReadMe.txt
|
||||||
|
|
||||||
|
# StyleCop
|
||||||
|
StyleCopReport.xml
|
||||||
|
|
||||||
|
# Files built by Visual Studio
|
||||||
|
*_i.c
|
||||||
|
*_p.c
|
||||||
|
*_h.h
|
||||||
|
*.ilk
|
||||||
|
*.meta
|
||||||
|
*.obj
|
||||||
|
*.iobj
|
||||||
|
*.pch
|
||||||
|
*.pdb
|
||||||
|
*.ipdb
|
||||||
|
*.pgc
|
||||||
|
*.pgd
|
||||||
|
*.rsp
|
||||||
|
# but not Directory.Build.rsp, as it configures directory-level build defaults
|
||||||
|
!Directory.Build.rsp
|
||||||
|
*.sbr
|
||||||
|
*.tlb
|
||||||
|
*.tli
|
||||||
|
*.tlh
|
||||||
|
*.tmp
|
||||||
|
*.tmp_proj
|
||||||
|
*_wpftmp.csproj
|
||||||
|
*.log
|
||||||
|
*.tlog
|
||||||
|
*.vspscc
|
||||||
|
*.vssscc
|
||||||
|
.builds
|
||||||
|
*.pidb
|
||||||
|
*.svclog
|
||||||
|
*.scc
|
||||||
|
|
||||||
|
# Chutzpah Test files
|
||||||
|
_Chutzpah*
|
||||||
|
|
||||||
|
# Visual C++ cache files
|
||||||
|
ipch/
|
||||||
|
*.aps
|
||||||
|
*.ncb
|
||||||
|
*.opendb
|
||||||
|
*.opensdf
|
||||||
|
*.sdf
|
||||||
|
*.cachefile
|
||||||
|
*.VC.db
|
||||||
|
*.VC.VC.opendb
|
||||||
|
|
||||||
|
# Visual Studio profiler
|
||||||
|
*.psess
|
||||||
|
*.vsp
|
||||||
|
*.vspx
|
||||||
|
*.sap
|
||||||
|
|
||||||
|
# Visual Studio Trace Files
|
||||||
|
*.e2e
|
||||||
|
|
||||||
|
# TFS 2012 Local Workspace
|
||||||
|
$tf/
|
||||||
|
|
||||||
|
# Guidance Automation Toolkit
|
||||||
|
*.gpState
|
||||||
|
|
||||||
|
# ReSharper is a .NET coding add-in
|
||||||
|
_ReSharper*/
|
||||||
|
*.[Rr]e[Ss]harper
|
||||||
|
*.DotSettings.user
|
||||||
|
|
||||||
|
# TeamCity is a build add-in
|
||||||
|
_TeamCity*
|
||||||
|
|
||||||
|
# DotCover is a Code Coverage Tool
|
||||||
|
*.dotCover
|
||||||
|
|
||||||
|
# AxoCover is a Code Coverage Tool
|
||||||
|
.axoCover/*
|
||||||
|
!.axoCover/settings.json
|
||||||
|
|
||||||
|
# Coverlet is a free, cross platform Code Coverage Tool
|
||||||
|
coverage*.json
|
||||||
|
coverage*.xml
|
||||||
|
coverage*.info
|
||||||
|
|
||||||
|
# Visual Studio code coverage results
|
||||||
|
*.coverage
|
||||||
|
*.coveragexml
|
||||||
|
|
||||||
|
# NCrunch
|
||||||
|
_NCrunch_*
|
||||||
|
.*crunch*.local.xml
|
||||||
|
nCrunchTemp_*
|
||||||
|
|
||||||
|
# MightyMoose
|
||||||
|
*.mm.*
|
||||||
|
AutoTest.Net/
|
||||||
|
|
||||||
|
# Web workbench (sass)
|
||||||
|
.sass-cache/
|
||||||
|
|
||||||
|
# Installshield output folder
|
||||||
|
[Ee]xpress/
|
||||||
|
|
||||||
|
# DocProject is a documentation generator add-in
|
||||||
|
DocProject/buildhelp/
|
||||||
|
DocProject/Help/*.HxT
|
||||||
|
DocProject/Help/*.HxC
|
||||||
|
DocProject/Help/*.hhc
|
||||||
|
DocProject/Help/*.hhk
|
||||||
|
DocProject/Help/*.hhp
|
||||||
|
DocProject/Help/Html2
|
||||||
|
DocProject/Help/html
|
||||||
|
|
||||||
|
# Click-Once directory
|
||||||
|
publish/
|
||||||
|
|
||||||
|
# Publish Web Output
|
||||||
|
*.[Pp]ublish.xml
|
||||||
|
*.azurePubxml
|
||||||
|
# Note: Comment the next line if you want to checkin your web deploy settings,
|
||||||
|
# but database connection strings (with potential passwords) will be unencrypted
|
||||||
|
*.pubxml
|
||||||
|
*.publishproj
|
||||||
|
|
||||||
|
# Microsoft Azure Web App publish settings. Comment the next line if you want to
|
||||||
|
# checkin your Azure Web App publish settings, but sensitive information contained
|
||||||
|
# in these scripts will be unencrypted
|
||||||
|
PublishScripts/
|
||||||
|
|
||||||
|
# NuGet Packages
|
||||||
|
*.nupkg
|
||||||
|
# NuGet Symbol Packages
|
||||||
|
*.snupkg
|
||||||
|
# The packages folder can be ignored because of Package Restore
|
||||||
|
**/[Pp]ackages/*
|
||||||
|
# except build/, which is used as an MSBuild target.
|
||||||
|
!**/[Pp]ackages/build/
|
||||||
|
# Uncomment if necessary however generally it will be regenerated when needed
|
||||||
|
#!**/[Pp]ackages/repositories.config
|
||||||
|
# NuGet v3's project.json files produces more ignorable files
|
||||||
|
*.nuget.props
|
||||||
|
*.nuget.targets
|
||||||
|
|
||||||
|
# Microsoft Azure Build Output
|
||||||
|
csx/
|
||||||
|
*.build.csdef
|
||||||
|
|
||||||
|
# Microsoft Azure Emulator
|
||||||
|
ecf/
|
||||||
|
rcf/
|
||||||
|
|
||||||
|
# Windows Store app package directories and files
|
||||||
|
AppPackages/
|
||||||
|
BundleArtifacts/
|
||||||
|
Package.StoreAssociation.xml
|
||||||
|
_pkginfo.txt
|
||||||
|
*.appx
|
||||||
|
*.appxbundle
|
||||||
|
*.appxupload
|
||||||
|
|
||||||
|
# Visual Studio cache files
|
||||||
|
# files ending in .cache can be ignored
|
||||||
|
*.[Cc]ache
|
||||||
|
# but keep track of directories ending in .cache
|
||||||
|
!?*.[Cc]ache/
|
||||||
|
|
||||||
|
# Others
|
||||||
|
ClientBin/
|
||||||
|
~$*
|
||||||
|
*~
|
||||||
|
*.dbmdl
|
||||||
|
*.dbproj.schemaview
|
||||||
|
*.jfm
|
||||||
|
*.pfx
|
||||||
|
*.publishsettings
|
||||||
|
orleans.codegen.cs
|
||||||
|
|
||||||
|
# Including strong name files can present a security risk
|
||||||
|
# (https://github.com/github/gitignore/pull/2483#issue-259490424)
|
||||||
|
#*.snk
|
||||||
|
|
||||||
|
# Since there are multiple workflows, uncomment next line to ignore bower_components
|
||||||
|
# (https://github.com/github/gitignore/pull/1529#issuecomment-104372622)
|
||||||
|
#bower_components/
|
||||||
|
|
||||||
|
# RIA/Silverlight projects
|
||||||
|
Generated_Code/
|
||||||
|
|
||||||
|
# Backup & report files from converting an old project file
|
||||||
|
# to a newer Visual Studio version. Backup files are not needed,
|
||||||
|
# because we have git ;-)
|
||||||
|
_UpgradeReport_Files/
|
||||||
|
Backup*/
|
||||||
|
UpgradeLog*.XML
|
||||||
|
UpgradeLog*.htm
|
||||||
|
ServiceFabricBackup/
|
||||||
|
*.rptproj.bak
|
||||||
|
|
||||||
|
# SQL Server files
|
||||||
|
*.mdf
|
||||||
|
*.ldf
|
||||||
|
*.ndf
|
||||||
|
|
||||||
|
# Business Intelligence projects
|
||||||
|
*.rdl.data
|
||||||
|
*.bim.layout
|
||||||
|
*.bim_*.settings
|
||||||
|
*.rptproj.rsuser
|
||||||
|
*- [Bb]ackup.rdl
|
||||||
|
*- [Bb]ackup ([0-9]).rdl
|
||||||
|
*- [Bb]ackup ([0-9][0-9]).rdl
|
||||||
|
|
||||||
|
# Microsoft Fakes
|
||||||
|
FakesAssemblies/
|
||||||
|
|
||||||
|
# GhostDoc plugin setting file
|
||||||
|
*.GhostDoc.xml
|
||||||
|
|
||||||
|
# Node.js Tools for Visual Studio
|
||||||
|
.ntvs_analysis.dat
|
||||||
|
node_modules/
|
||||||
|
|
||||||
|
# Visual Studio 6 build log
|
||||||
|
*.plg
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace options file
|
||||||
|
*.opt
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated workspace file (contains which files were open etc.)
|
||||||
|
*.vbw
|
||||||
|
|
||||||
|
# Visual Studio 6 auto-generated project file (contains which files were open etc.)
|
||||||
|
*.vbp
|
||||||
|
|
||||||
|
# Visual Studio 6 workspace and project file (working project files containing files to include in project)
|
||||||
|
*.dsw
|
||||||
|
*.dsp
|
||||||
|
|
||||||
|
# Visual Studio 6 technical files
|
||||||
|
*.ncb
|
||||||
|
*.aps
|
||||||
|
|
||||||
|
# Visual Studio LightSwitch build output
|
||||||
|
**/*.HTMLClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/GeneratedArtifacts
|
||||||
|
**/*.DesktopClient/ModelManifest.xml
|
||||||
|
**/*.Server/GeneratedArtifacts
|
||||||
|
**/*.Server/ModelManifest.xml
|
||||||
|
_Pvt_Extensions
|
||||||
|
|
||||||
|
# Paket dependency manager
|
||||||
|
.paket/paket.exe
|
||||||
|
paket-files/
|
||||||
|
|
||||||
|
# FAKE - F# Make
|
||||||
|
.fake/
|
||||||
|
|
||||||
|
# CodeRush personal settings
|
||||||
|
.cr/personal
|
||||||
|
|
||||||
|
# Python Tools for Visual Studio (PTVS)
|
||||||
|
__pycache__/
|
||||||
|
*.pyc
|
||||||
|
|
||||||
|
# Cake - Uncomment if you are using it
|
||||||
|
# tools/**
|
||||||
|
# !tools/packages.config
|
||||||
|
|
||||||
|
# Tabs Studio
|
||||||
|
*.tss
|
||||||
|
|
||||||
|
# Telerik's JustMock configuration file
|
||||||
|
*.jmconfig
|
||||||
|
|
||||||
|
# BizTalk build output
|
||||||
|
*.btp.cs
|
||||||
|
*.btm.cs
|
||||||
|
*.odx.cs
|
||||||
|
*.xsd.cs
|
||||||
|
|
||||||
|
# OpenCover UI analysis results
|
||||||
|
OpenCover/
|
||||||
|
|
||||||
|
# Azure Stream Analytics local run output
|
||||||
|
ASALocalRun/
|
||||||
|
|
||||||
|
# MSBuild Binary and Structured Log
|
||||||
|
*.binlog
|
||||||
|
|
||||||
|
# NVidia Nsight GPU debugger configuration file
|
||||||
|
*.nvuser
|
||||||
|
|
||||||
|
# MFractors (Xamarin productivity tool) working folder
|
||||||
|
.mfractor/
|
||||||
|
|
||||||
|
# Local History for Visual Studio
|
||||||
|
.localhistory/
|
||||||
|
|
||||||
|
# Visual Studio History (VSHistory) files
|
||||||
|
.vshistory/
|
||||||
|
|
||||||
|
# BeatPulse healthcheck temp database
|
||||||
|
healthchecksdb
|
||||||
|
|
||||||
|
# Backup folder for Package Reference Convert tool in Visual Studio 2017
|
||||||
|
MigrationBackup/
|
||||||
|
|
||||||
|
# Ionide (cross platform F# VS Code tools) working folder
|
||||||
|
.ionide/
|
||||||
|
|
||||||
|
# Fody - auto-generated XML schema
|
||||||
|
FodyWeavers.xsd
|
||||||
|
|
||||||
|
# VS Code files for those working on multiple tools
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
*.code-workspace
|
||||||
|
|
||||||
|
# Local History for Visual Studio Code
|
||||||
|
.history/
|
||||||
|
|
||||||
|
# Windows Installer files from build outputs
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# JetBrains Rider
|
||||||
|
*.sln.iml
|
||||||
|
|
||||||
|
# External Libraries
|
||||||
|
lib/*.dll
|
||||||
|
|
||||||
|
# Custom
|
||||||
@@ -0,0 +1,148 @@
|
|||||||
|
using CommunityMapLocation.Config;
|
||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Reflection;
|
||||||
|
using System.Text;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Serialization;
|
||||||
|
using UnityModManagerNet;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Api
|
||||||
|
{
|
||||||
|
public class ApiServer
|
||||||
|
{
|
||||||
|
public ApiServer(Settings settings, UnityModManager.ModEntry.ModLogger logger)
|
||||||
|
{
|
||||||
|
listener = new HttpListener();
|
||||||
|
|
||||||
|
serializerOptions = new JsonSerializerOptions()
|
||||||
|
{
|
||||||
|
ReferenceHandler = ReferenceHandler.IgnoreCycles,
|
||||||
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
|
||||||
|
WriteIndented = true
|
||||||
|
};
|
||||||
|
|
||||||
|
this.settings = settings;
|
||||||
|
this.logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static string GetPrefix(int port) => $"http://localhost:{port}/";
|
||||||
|
|
||||||
|
private readonly Settings settings;
|
||||||
|
private readonly UnityModManager.ModEntry.ModLogger logger;
|
||||||
|
|
||||||
|
private readonly HttpListener listener;
|
||||||
|
|
||||||
|
private readonly JsonSerializerOptions serializerOptions;
|
||||||
|
|
||||||
|
private readonly List<IEndpoint> endpoints = new List<IEndpoint>();
|
||||||
|
|
||||||
|
public void AutoRegister(Assembly assembly = null)
|
||||||
|
{
|
||||||
|
if (assembly == null)
|
||||||
|
{
|
||||||
|
assembly = Assembly.GetExecutingAssembly();
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (var endpointType in assembly.ExportedTypes.Where(t => !t.IsInterface && !t.IsAbstract && typeof(IEndpoint).IsAssignableFrom(t)))
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
endpoints.Add((IEndpoint)Activator.CreateInstance(endpointType));
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Warning($"Could not register endpoint {endpointType.Name}: {ex}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void RegisterEndpoint(IEndpoint endpoint)
|
||||||
|
{
|
||||||
|
endpoints.Add(endpoint);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Start()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
listener.Prefixes.Add(GetPrefix(settings.Port));
|
||||||
|
listener.Start();
|
||||||
|
listener.BeginGetContext(HandleConnection, null);
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
logger.Warning(ex.ToString());
|
||||||
|
Stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void HandleConnection(IAsyncResult ar)
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var context = listener.EndGetContext(ar);
|
||||||
|
|
||||||
|
listener.BeginGetContext(HandleConnection, listener);
|
||||||
|
|
||||||
|
var request = context.Request;
|
||||||
|
var response = context.Response;
|
||||||
|
|
||||||
|
var matchedEndpoints = endpoints.Where(e => e.Matches(request.Url) &&
|
||||||
|
(request.HttpMethod == "OPTIONS" || request.HttpMethod.Equals(e.HttpMethod.ToString(), StringComparison.OrdinalIgnoreCase)));
|
||||||
|
|
||||||
|
if (!endpoints.Any())
|
||||||
|
{
|
||||||
|
response.StatusCode = (int)HttpStatusCode.NotFound;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var methods = matchedEndpoints.Select(e => e.HttpMethod).Distinct();
|
||||||
|
|
||||||
|
response.AppendHeader("Access-Control-Allow-Origin", settings.RequestSourceType == RequestSourceType.All ? "*" : settings.RequestSource);
|
||||||
|
|
||||||
|
if (request.HttpMethod == "OPTIONS")
|
||||||
|
{
|
||||||
|
response.AddHeader("Access-Control-Allow-Headers", "Content-Type, Accept, X-Requested-With");
|
||||||
|
response.AddHeader("Access-Control-Allow-Methods", String.Join(", ", methods.Select(m => m.ToString().ToUpper())));
|
||||||
|
response.AddHeader("Access-Control-Max-Age", "1728000");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
var responseBody = matchedEndpoints.First().Handle(logger, request);
|
||||||
|
|
||||||
|
var buffer = Encoding.UTF8.GetBytes(JsonSerializer.Serialize(responseBody, serializerOptions));
|
||||||
|
|
||||||
|
response.ContentLength64 = buffer.Length;
|
||||||
|
response.OutputStream.Write(buffer, 0, buffer.Length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response.OutputStream.Close();
|
||||||
|
}
|
||||||
|
catch (HttpListenerException)
|
||||||
|
{
|
||||||
|
// HttpListener is stopping, swallow exception
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void UpdateSettings()
|
||||||
|
{
|
||||||
|
var prefix = GetPrefix(settings.Port);
|
||||||
|
|
||||||
|
if (listener.Prefixes.Count != 1 || listener.Prefixes.Single() != prefix)
|
||||||
|
{
|
||||||
|
listener.Prefixes.Clear();
|
||||||
|
listener.Prefixes.Add(prefix);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Stop()
|
||||||
|
{
|
||||||
|
listener.Stop();
|
||||||
|
listener.Prefixes.Clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using UnityModManagerNet;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Api
|
||||||
|
{
|
||||||
|
public abstract class Endpoint<T> : IEndpoint
|
||||||
|
{
|
||||||
|
public abstract HttpMethod HttpMethod { get; }
|
||||||
|
public abstract string RoutePattern { get; }
|
||||||
|
public virtual bool Matches(Uri uri)
|
||||||
|
{
|
||||||
|
return String.Concat(uri.Segments).Equals(RoutePattern, StringComparison.OrdinalIgnoreCase);
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract T Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request);
|
||||||
|
|
||||||
|
object IEndpoint.Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request)
|
||||||
|
{
|
||||||
|
return Handle(logger, request);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
using System;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using UnityModManagerNet;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Api
|
||||||
|
{
|
||||||
|
public interface IEndpoint
|
||||||
|
{
|
||||||
|
HttpMethod HttpMethod { get; }
|
||||||
|
string RoutePattern { get; }
|
||||||
|
bool Matches(Uri uri);
|
||||||
|
object Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,42 @@
|
|||||||
|
using DV.OriginShift;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Api.Location
|
||||||
|
{
|
||||||
|
public class CarLocationResponse
|
||||||
|
{
|
||||||
|
public CarLocationResponse(TrainCar trainCar)
|
||||||
|
{
|
||||||
|
IsLoco = trainCar.IsLoco;
|
||||||
|
Name = trainCar.name;
|
||||||
|
Id = trainCar.ID;
|
||||||
|
CarGuid = trainCar.CarGUID;
|
||||||
|
|
||||||
|
// Probable not needed. Commented to remove additional dll dependency
|
||||||
|
//HandbrakePosition = trainCar.brakeSystem.handbrakePosition;
|
||||||
|
|
||||||
|
// TrainCar.Bounds does not seem to represent the actual bounds of the car.
|
||||||
|
// Approximate the car length as the distance between the couplers
|
||||||
|
Length = (trainCar.FrontCouplerAnchor.AbsolutePosition() - trainCar.RearCouplerAnchor.AbsolutePosition()).magnitude;
|
||||||
|
|
||||||
|
var transform = trainCar.transform.AbsolutePosition();
|
||||||
|
X = transform.x;
|
||||||
|
Y = transform.y;
|
||||||
|
Z = transform.z;
|
||||||
|
|
||||||
|
Rotation = trainCar.transform.rotation.eulerAngles.y * (Mathf.PI / 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
public string Id { get; }
|
||||||
|
public string CarGuid { get; }
|
||||||
|
public string Name { get; }
|
||||||
|
public bool IsLoco { get; }
|
||||||
|
//public float HandbrakePosition { get; }
|
||||||
|
|
||||||
|
public float X { get; }
|
||||||
|
public float Y { get; }
|
||||||
|
public float Z { get; }
|
||||||
|
public float Length { get; }
|
||||||
|
public float Rotation { get; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,51 @@
|
|||||||
|
using DV.HUD;
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Net;
|
||||||
|
using System.Net.Http;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityModManagerNet;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Api.Location
|
||||||
|
{
|
||||||
|
public class LocationEndpoint : Endpoint<LocationResponse>
|
||||||
|
{
|
||||||
|
public override HttpMethod HttpMethod => HttpMethod.Get;
|
||||||
|
|
||||||
|
public override string RoutePattern => "/Location";
|
||||||
|
|
||||||
|
public override LocationResponse Handle(UnityModManager.ModEntry.ModLogger logger, HttpListenerRequest request)
|
||||||
|
{
|
||||||
|
Transform rotationTransform;
|
||||||
|
var reverse = false;
|
||||||
|
|
||||||
|
// If the player is in a loco, we want to get the heading of the loco instead of the play
|
||||||
|
if (PlayerManager.Car?.IsLoco == true)
|
||||||
|
{
|
||||||
|
rotationTransform = PlayerManager.Car.transform;
|
||||||
|
|
||||||
|
// If the loco is in reverse, rotate the direction 180 degrees to match the direction of travel
|
||||||
|
if (PlayerManager.Car.interior.GetComponentInChildren<InteriorControlsManager>().TryGetControl(InteriorControlsManager.ControlType.Reverser, out var control) &&
|
||||||
|
control.controlImplBase.Value <= 0.25)
|
||||||
|
{
|
||||||
|
reverse = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
rotationTransform = PlayerManager.PlayerCamera.transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
var cars = Array.Empty<CarLocationResponse>();
|
||||||
|
if (PlayerManager.Car != null)
|
||||||
|
{
|
||||||
|
cars = PlayerManager.Car.trainset.cars.Select(c => new CarLocationResponse(c)).ToArray();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LocationResponse(PlayerManager.PlayerTransform, reverse)
|
||||||
|
{
|
||||||
|
Cars = cars
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
using DV.OriginShift;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Api.Location
|
||||||
|
{
|
||||||
|
public class LocationResponse
|
||||||
|
{
|
||||||
|
public LocationResponse(Transform playerTransform, bool reverse)
|
||||||
|
{
|
||||||
|
var position = playerTransform.AbsolutePosition();
|
||||||
|
|
||||||
|
X = position.x;
|
||||||
|
Y = position.y;
|
||||||
|
Z = position.z;
|
||||||
|
|
||||||
|
var angle = playerTransform.rotation.eulerAngles.y;
|
||||||
|
|
||||||
|
if (reverse)
|
||||||
|
{
|
||||||
|
angle += 180;
|
||||||
|
}
|
||||||
|
|
||||||
|
Rotation = angle * (Mathf.PI / 180);
|
||||||
|
}
|
||||||
|
|
||||||
|
public float X { get; }
|
||||||
|
public float Y { get; }
|
||||||
|
public float Z { get; }
|
||||||
|
public float Rotation { get; }
|
||||||
|
|
||||||
|
public IEnumerable<CarLocationResponse> Cars { get; set; }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,121 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
|
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
|
||||||
|
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
|
||||||
|
<ProjectGuid>{0A8392E4-B2FA-4018-BD0C-DD6261F10699}</ProjectGuid>
|
||||||
|
<OutputType>Library</OutputType>
|
||||||
|
<AppDesignerFolder>Properties</AppDesignerFolder>
|
||||||
|
<RootNamespace>CommunityMapLocation</RootNamespace>
|
||||||
|
<AssemblyName>CommunityMapLocation</AssemblyName>
|
||||||
|
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
|
||||||
|
<FileAlignment>512</FileAlignment>
|
||||||
|
<Deterministic>true</Deterministic>
|
||||||
|
<TargetFrameworkProfile />
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
|
||||||
|
<DebugSymbols>true</DebugSymbols>
|
||||||
|
<DebugType>full</DebugType>
|
||||||
|
<Optimize>false</Optimize>
|
||||||
|
<OutputPath>bin\Debug\</OutputPath>
|
||||||
|
<DefineConstants>DEBUG;TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
|
||||||
|
<DebugType>pdbonly</DebugType>
|
||||||
|
<Optimize>true</Optimize>
|
||||||
|
<OutputPath>bin\Release\</OutputPath>
|
||||||
|
<DefineConstants>TRACE</DefineConstants>
|
||||||
|
<ErrorReport>prompt</ErrorReport>
|
||||||
|
<WarningLevel>4</WarningLevel>
|
||||||
|
<Prefer32Bit>false</Prefer32Bit>
|
||||||
|
</PropertyGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Reference Include="0Harmony">
|
||||||
|
<HintPath>lib\0Harmony.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="Assembly-CSharp">
|
||||||
|
<HintPath>lib\Assembly-CSharp.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DV.Common">
|
||||||
|
<HintPath>lib\DV.Common.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DV.OriginShiftInfo">
|
||||||
|
<HintPath>lib\DV.OriginShiftInfo.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DV.RailTrack">
|
||||||
|
<HintPath>lib\DV.RailTrack.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DV.UserManagement">
|
||||||
|
<HintPath>lib\DV.UserManagement.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="DV.Utils">
|
||||||
|
<HintPath>lib\DV.Utils.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="System" />
|
||||||
|
<Reference Include="System.Core" />
|
||||||
|
<Reference Include="System.Xml.Linq" />
|
||||||
|
<Reference Include="System.Data.DataSetExtensions" />
|
||||||
|
<Reference Include="Microsoft.CSharp" />
|
||||||
|
<Reference Include="System.Data" />
|
||||||
|
<Reference Include="System.Net.Http" />
|
||||||
|
<Reference Include="System.Xml" />
|
||||||
|
<Reference Include="UnityEngine">
|
||||||
|
<HintPath>lib\UnityEngine.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.CoreModule">
|
||||||
|
<HintPath>lib\UnityEngine.CoreModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.IMGUIModule">
|
||||||
|
<HintPath>lib\UnityEngine.IMGUIModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.InputLegacyModule">
|
||||||
|
<HintPath>lib\UnityEngine.InputLegacyModule.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityEngine.UI">
|
||||||
|
<HintPath>lib\UnityEngine.UI.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
<Reference Include="UnityModManager">
|
||||||
|
<HintPath>lib\UnityModManager.dll</HintPath>
|
||||||
|
<Private>False</Private>
|
||||||
|
</Reference>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<Compile Include="Api\ApiServer.cs" />
|
||||||
|
<Compile Include="Api\Endpoint.cs" />
|
||||||
|
<Compile Include="Api\IEndpoint.cs" />
|
||||||
|
<Compile Include="Api\Location\CarLocationResponse.cs" />
|
||||||
|
<Compile Include="Api\Location\LocationEndpoint.cs" />
|
||||||
|
<Compile Include="Api\Location\LocationResponse.cs" />
|
||||||
|
<Compile Include="Config\RequestSourceType.cs" />
|
||||||
|
<Compile Include="Main.cs" />
|
||||||
|
<Compile Include="Properties\AssemblyInfo.cs" />
|
||||||
|
<Compile Include="Config\Settings.cs" />
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<None Include="Info.json">
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
</None>
|
||||||
|
</ItemGroup>
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="System.Text.Json">
|
||||||
|
<Version>9.0.6</Version>
|
||||||
|
</PackageReference>
|
||||||
|
</ItemGroup>
|
||||||
|
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
|
||||||
|
</Project>
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
|
||||||
|
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||||
|
# Visual Studio Version 17
|
||||||
|
VisualStudioVersion = 17.14.36109.1 d17.14
|
||||||
|
MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommunityMapLocation", "CommunityMapLocation.csproj", "{0A8392E4-B2FA-4018-BD0C-DD6261F10699}"
|
||||||
|
EndProject
|
||||||
|
Global
|
||||||
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
Debug|Any CPU = Debug|Any CPU
|
||||||
|
Release|Any CPU = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||||
|
{0A8392E4-B2FA-4018-BD0C-DD6261F10699}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{0A8392E4-B2FA-4018-BD0C-DD6261F10699}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{0A8392E4-B2FA-4018-BD0C-DD6261F10699}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{0A8392E4-B2FA-4018-BD0C-DD6261F10699}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
|
HideSolutionNode = FALSE
|
||||||
|
EndGlobalSection
|
||||||
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
|
SolutionGuid = {044646D0-2953-41CC-A775-C1C2BAECADCD}
|
||||||
|
EndGlobalSection
|
||||||
|
EndGlobal
|
||||||
@@ -0,0 +1,14 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Config
|
||||||
|
{
|
||||||
|
public enum RequestSourceType
|
||||||
|
{
|
||||||
|
Specific,
|
||||||
|
All
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.ComponentModel;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using UnityEngine;
|
||||||
|
using UnityModManagerNet;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation.Config
|
||||||
|
{
|
||||||
|
public class Settings : UnityModManager.ModSettings, INotifyPropertyChanged
|
||||||
|
{
|
||||||
|
private int port = 9090;
|
||||||
|
public int Port { get => port; set => Set(ref port, value); }
|
||||||
|
|
||||||
|
private RequestSourceType requestSourceType = RequestSourceType.Specific;
|
||||||
|
public RequestSourceType RequestSourceType { get => requestSourceType; set => Set(ref requestSourceType, value); }
|
||||||
|
|
||||||
|
private string requestSource = "https://pyronicampt.github.io";
|
||||||
|
public string RequestSource { get => requestSource; set => Set(ref requestSource, value); }
|
||||||
|
|
||||||
|
public void Draw()
|
||||||
|
{
|
||||||
|
GUILayout.BeginVertical(GUILayout.ExpandWidth(false));
|
||||||
|
|
||||||
|
GUILayout.Label("Listening Port:");
|
||||||
|
Port = Int32.TryParse(GUILayout.TextField(Port.ToString()), out var port) ? port : Port;
|
||||||
|
|
||||||
|
GUILayout.Label("Allow connections from websites hosted anywhere, or a specific website?");
|
||||||
|
|
||||||
|
UnityModManager.UI.ToggleGroup((int)RequestSourceType, Enum.GetNames(typeof(RequestSourceType)), i => RequestSourceType = (RequestSourceType)i);
|
||||||
|
|
||||||
|
if (RequestSourceType == RequestSourceType.Specific)
|
||||||
|
{
|
||||||
|
GUILayout.Label("Website:");
|
||||||
|
RequestSource = GUILayout.TextField(RequestSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
}
|
||||||
|
|
||||||
|
public event PropertyChangedEventHandler PropertyChanged;
|
||||||
|
|
||||||
|
// Todo: store and hold back property changes until Settings is saved to prevent rapidly reconfiguring server on each keypress?
|
||||||
|
// Not yet used anywhere, so could revert to using standard auto-properties
|
||||||
|
private void Set<T>(ref T field, T value, [CallerMemberName] string propertyName = null)
|
||||||
|
{
|
||||||
|
if (!EqualityComparer<T>.Default.Equals(field, value))
|
||||||
|
{
|
||||||
|
field = value;
|
||||||
|
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"Id":"CommunityMapLocation",
|
||||||
|
"DisplayName":"Derail Valley Community Map Location",
|
||||||
|
"Author":"BenjaminGreene",
|
||||||
|
"Version":"0.0.1",
|
||||||
|
"ManagerVersion":"0.31.1.0",
|
||||||
|
"EntryMethod":"CommunityMapLocation.Main.Load"
|
||||||
|
}
|
||||||
@@ -0,0 +1,53 @@
|
|||||||
|
using CommunityMapLocation.Api;
|
||||||
|
using CommunityMapLocation.Config;
|
||||||
|
using UnityModManagerNet;
|
||||||
|
|
||||||
|
namespace CommunityMapLocation
|
||||||
|
{
|
||||||
|
[EnableReloading]
|
||||||
|
public static class Main
|
||||||
|
{
|
||||||
|
public static Settings Settings { get; private set; }
|
||||||
|
private static ApiServer apiServer;
|
||||||
|
|
||||||
|
static bool Load(UnityModManager.ModEntry modEntry)
|
||||||
|
{
|
||||||
|
Settings = Settings.Load<Settings>(modEntry);
|
||||||
|
|
||||||
|
apiServer = new ApiServer(Settings, modEntry.Logger);
|
||||||
|
apiServer.AutoRegister();
|
||||||
|
|
||||||
|
modEntry.OnToggle = OnToggle;
|
||||||
|
modEntry.OnGUI = OnGUI;
|
||||||
|
modEntry.OnSaveGUI = OnSaveGUI;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static bool OnToggle(UnityModManager.ModEntry modEntry, bool value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
{
|
||||||
|
apiServer.Start();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
apiServer.Stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnGUI(UnityModManager.ModEntry modEntry)
|
||||||
|
{
|
||||||
|
Settings.Draw();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void OnSaveGUI(UnityModManager.ModEntry modEntry)
|
||||||
|
{
|
||||||
|
Settings.Save(modEntry);
|
||||||
|
|
||||||
|
apiServer.UpdateSettings();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
using System.Reflection;
|
||||||
|
using System.Runtime.CompilerServices;
|
||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
// General Information about an assembly is controlled through the following
|
||||||
|
// set of attributes. Change these attribute values to modify the information
|
||||||
|
// associated with an assembly.
|
||||||
|
[assembly: AssemblyTitle("CommunityMapLocation")]
|
||||||
|
[assembly: AssemblyDescription("")]
|
||||||
|
[assembly: AssemblyConfiguration("")]
|
||||||
|
[assembly: AssemblyCompany("")]
|
||||||
|
[assembly: AssemblyProduct("CommunityMapLocation")]
|
||||||
|
[assembly: AssemblyCopyright("Copyright © 2025")]
|
||||||
|
[assembly: AssemblyTrademark("")]
|
||||||
|
[assembly: AssemblyCulture("")]
|
||||||
|
|
||||||
|
// Setting ComVisible to false makes the types in this assembly not visible
|
||||||
|
// to COM components. If you need to access a type in this assembly from
|
||||||
|
// COM, set the ComVisible attribute to true on that type.
|
||||||
|
[assembly: ComVisible(false)]
|
||||||
|
|
||||||
|
// The following GUID is for the ID of the typelib if this project is exposed to COM
|
||||||
|
[assembly: Guid("0a8392e4-b2fa-4018-bd0c-dd6261f10699")]
|
||||||
|
|
||||||
|
// Version information for an assembly consists of the following four values:
|
||||||
|
//
|
||||||
|
// Major Version
|
||||||
|
// Minor Version
|
||||||
|
// Build Number
|
||||||
|
// Revision
|
||||||
|
//
|
||||||
|
[assembly: AssemblyVersion("1.0.0.0")]
|
||||||
|
[assembly: AssemblyFileVersion("1.0.0.0")]
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
Assuming Visual Studio Community:
|
||||||
|
|
||||||
|
1. Add the following to the `lib` folder from `[steam common dir]/Derail Valley/DerailValley_Data/Managed`:
|
||||||
|
- `Assembly-CSharp.dll`
|
||||||
|
- `DV.OriginShiftInfo.dll`
|
||||||
|
- `UnityEngine.dll`
|
||||||
|
- `UnityEngine.IMGUIModule.dll`
|
||||||
|
- `UnityEngine.CoreModule.dll`
|
||||||
|
- `/UnityModManager/0Harmony.dll`
|
||||||
|
- `/UnityModManager/UnityModManager.dll`
|
||||||
|
|
||||||
|
2. Build Solution
|
||||||
|
3. Copy the following files from `bin/Debug/` to `Derail Valley/Mods/CommunityMapLocation`
|
||||||
|
- `info.json`
|
||||||
|
- `CommunityMapLocation.dll`
|
||||||
|
- `Microsoft.Bcl.AsyncInterfaces.dll`
|
||||||
|
- `System.Buffers.dll`
|
||||||
|
- `System.IO.Pipelines.dll`
|
||||||
|
- `System.Memory.dll`
|
||||||
|
- `System.Numerics.Vectors.dll`
|
||||||
|
- `System.Runtime.CompilerServices.Unsafe.dll`
|
||||||
|
- `System.Text.Encodings.Web.dll`
|
||||||
|
- `System.Text.Json.dll`
|
||||||
|
- `System.Threading.Tasks.Extensions.dll`
|
||||||
|
- `System.ValueTuple.dll`
|
||||||
|
|
||||||
|
4. Run game using [Unity Mod Manager](https://www.nexusmods.com/site/mods/21)
|
||||||
|
|
||||||
|
Mod functions are all within the UMM settings screen.
|
||||||
Whitespace-only changes.
@@ -91,4 +91,5 @@ export const spriteBounds = {
|
|||||||
service_repair: new SpriteBounds(20,120,20,20),
|
service_repair: new SpriteBounds(20,120,20,20),
|
||||||
service_diesel: new SpriteBounds(40,120,20,20),
|
service_diesel: new SpriteBounds(40,120,20,20),
|
||||||
service_charger: new SpriteBounds(60,120,20,20),
|
service_charger: new SpriteBounds(60,120,20,20),
|
||||||
|
player: new SpriteBounds(20,0,20,20),
|
||||||
}
|
}
|
||||||
+2
-1
@@ -15,7 +15,8 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="mapContainer">
|
<div id="mapContainer">
|
||||||
<canvas id="mapCanvas" style="width:100%;height:100%"></canvas>
|
<canvas id="mapCanvas"></canvas>
|
||||||
|
<canvas id="dynCanvas"></canvas>
|
||||||
<div id="tooltip" class="panel" style="display:none"></div>
|
<div id="tooltip" class="panel" style="display:none"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -10,9 +10,13 @@ import * as Legend from './legend.js';
|
|||||||
|
|
||||||
const mapContainer = document.getElementById('mapContainer');
|
const mapContainer = document.getElementById('mapContainer');
|
||||||
const mapCanvas = document.getElementById('mapCanvas');
|
const mapCanvas = document.getElementById('mapCanvas');
|
||||||
|
const dynCanvas = document.getElementById('dynCanvas');
|
||||||
/** The main map drawing context.
|
/** The main map drawing context.
|
||||||
* @type {CanvasRenderingContext2D} */
|
* @type {CanvasRenderingContext2D} */
|
||||||
const mapctx = mapCanvas.getContext('2d');
|
const mapctx = mapCanvas.getContext('2d');
|
||||||
|
/** The dynamic map drawing context.
|
||||||
|
* @type {CanvasRenderingContext2D} */
|
||||||
|
const dynctx = dynCanvas.getContext('2d');
|
||||||
const mapSprites = document.getElementById('mapSprites');
|
const mapSprites = document.getElementById('mapSprites');
|
||||||
const mapTerrain = document.getElementById('mapTerrain');
|
const mapTerrain = document.getElementById('mapTerrain');
|
||||||
const zoomLevelDisplay = document.getElementById('zoomLevelDisplay');
|
const zoomLevelDisplay = document.getElementById('zoomLevelDisplay');
|
||||||
@@ -110,6 +114,9 @@ function mapNavigationSetup(){
|
|||||||
if(ts && MapData.view.dirty){
|
if(ts && MapData.view.dirty){
|
||||||
redrawMap();
|
redrawMap();
|
||||||
}
|
}
|
||||||
|
if(ts && MapData.view.dynDirty){
|
||||||
|
redrawDynamics();
|
||||||
|
}
|
||||||
requestAnimationFrame(navUpdate);
|
requestAnimationFrame(navUpdate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -297,10 +304,11 @@ function redrawMap(){
|
|||||||
let spriteSize;
|
let spriteSize;
|
||||||
let textMeasure;
|
let textMeasure;
|
||||||
let tempMeasure = {};
|
let tempMeasure = {};
|
||||||
|
//Draw Static Markers
|
||||||
for(const marker of MapData.markers){
|
for(const marker of MapData.markers){
|
||||||
curSprite = null;
|
curSprite = null;
|
||||||
marker.visible = false;
|
marker.visible = false;
|
||||||
if((marker.minZoom && MapData.view.scale < marker.minZoom) || (marker.maxZoom && MapData.view.scale > marker.maxZoom)) continue;
|
if(marker.hidden || (marker.minZoom && MapData.view.scale < marker.minZoom) || (marker.maxZoom && MapData.view.scale > marker.maxZoom)) continue;
|
||||||
let markerX = MapData.view.convertX(marker.position.x);
|
let markerX = MapData.view.convertX(marker.position.x);
|
||||||
let markerY = MapData.view.convertY(marker.position.z);
|
let markerY = MapData.view.convertY(marker.position.z);
|
||||||
if(markerX > mapCanvas.width + Config.viewCullMargin || markerX < -Config.viewCullMargin || markerY > mapCanvas.height + Config.viewCullMargin || markerY < -Config.viewCullMargin) continue; // View Culling
|
if(markerX > mapCanvas.width + Config.viewCullMargin || markerX < -Config.viewCullMargin || markerY > mapCanvas.height + Config.viewCullMargin || markerY < -Config.viewCullMargin) continue; // View Culling
|
||||||
@@ -475,5 +483,103 @@ function redrawMap(){
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
MapData.view.dirty = false;
|
MapData.view.dirty = false;
|
||||||
|
|
||||||
|
redrawDynamics();
|
||||||
|
}
|
||||||
|
|
||||||
|
function redrawDynamics(){
|
||||||
|
dynCanvas.width = mapCanvas.width;
|
||||||
|
dynCanvas.height = mapCanvas.height;
|
||||||
|
let curSprite;
|
||||||
|
let spriteSize;
|
||||||
|
for(const marker of MapData.dynamicMarkers){
|
||||||
|
curSprite = null;
|
||||||
|
marker.visible = false;
|
||||||
|
if(marker.hidden || (marker.minZoom && MapData.view.scale < marker.minZoom) || (marker.maxZoom && MapData.view.scale > marker.maxZoom)) continue;
|
||||||
|
let markerX = MapData.view.convertX(marker.position.x);
|
||||||
|
let markerY = MapData.view.convertY(marker.position.z);
|
||||||
|
if(markerX > mapCanvas.width + Config.viewCullMargin || markerX < -Config.viewCullMargin || markerY > mapCanvas.height + Config.viewCullMargin || markerY < -Config.viewCullMargin) continue; // View Culling
|
||||||
|
switch(marker.type){
|
||||||
|
case 'player':
|
||||||
|
if(!(MapData.layers.poi)) break;
|
||||||
|
marker.visible = true;
|
||||||
|
curSprite = Config.spriteBounds.player;
|
||||||
|
spriteSize = 45;
|
||||||
|
|
||||||
|
dynctx.translate(markerX, markerY);
|
||||||
|
dynctx.rotate(marker.rotation);
|
||||||
|
dynctx.translate(-0.5 * spriteSize * MapData.view.pixelRatio, -0.5 * spriteSize * MapData.view.pixelRatio);
|
||||||
|
dynctx.drawImage(
|
||||||
|
mapSprites,
|
||||||
|
curSprite.x,
|
||||||
|
curSprite.y,
|
||||||
|
curSprite.width,
|
||||||
|
curSprite.height,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
spriteSize * MapData.view.pixelRatio,
|
||||||
|
spriteSize * MapData.view.pixelRatio
|
||||||
|
);
|
||||||
|
dynctx.resetTransform();
|
||||||
|
curSprite = null;
|
||||||
|
break;
|
||||||
|
case 'car':
|
||||||
|
if(!(MapData.layers.poi)) break;
|
||||||
|
let scale = MapData.view.pixelRatio * MapData.view.scale;
|
||||||
|
let limitScaleRatio = (MapData.view.pixelRatio * Math.max(MapData.view.scale, 3)) / scale;
|
||||||
|
let width = marker.width * scale;
|
||||||
|
let length = marker.length * scale;
|
||||||
|
|
||||||
|
marker.visible = true;
|
||||||
|
dynctx.beginPath();
|
||||||
|
dynctx.translate(markerX, markerY);
|
||||||
|
dynctx.rotate(marker.rotation);
|
||||||
|
dynctx.translate(-0.5 * width, -0.5 * length);
|
||||||
|
dynctx.rect(0, 0, width, length);
|
||||||
|
dynctx.moveTo(0, width);
|
||||||
|
dynctx.lineTo(0.5 * width, 0);
|
||||||
|
dynctx.lineTo(width, width);
|
||||||
|
dynctx.moveTo(0.5 * width, 0);
|
||||||
|
dynctx.arc(0.5 * width, 0.5 * length, 15, -0.5*Math.PI, 1.5*Math.PI);
|
||||||
|
dynctx.resetTransform();
|
||||||
|
dynctx.strokeStyle = '#fff';
|
||||||
|
dynctx.lineWidth = 0.75 * scale * limitScaleRatio;
|
||||||
|
dynctx.miterLimit = 1.5;
|
||||||
|
dynctx.stroke();
|
||||||
|
|
||||||
|
dynctx.fillStyle = marker.isLoco ? '#e79830aa' : '#559bd4aa';
|
||||||
|
dynctx.fill();
|
||||||
|
dynctx.strokeStyle = '#000';
|
||||||
|
dynctx.lineWidth = 0.3 * scale * limitScaleRatio;
|
||||||
|
dynctx.stroke();
|
||||||
|
if(!marker.tooltipHitzone){
|
||||||
|
marker.tooltipHitzone = {
|
||||||
|
radius: 15,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if(!MapData.layers.poi) break;
|
||||||
|
marker.visible = true;
|
||||||
|
curSprite = Config.spriteBounds.unknown;
|
||||||
|
spriteSize = 30;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
if(curSprite != null){
|
||||||
|
dynctx.drawImage(
|
||||||
|
mapSprites,
|
||||||
|
curSprite.x,
|
||||||
|
curSprite.y,
|
||||||
|
curSprite.width,
|
||||||
|
curSprite.height,
|
||||||
|
markerX-(0.5*spriteSize)*MapData.view.pixelRatio,
|
||||||
|
markerY-(0.5*spriteSize)*MapData.view.pixelRatio,
|
||||||
|
spriteSize*MapData.view.pixelRatio,
|
||||||
|
spriteSize*MapData.view.pixelRatio
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
MapData.view.dynDirty = false;
|
||||||
}
|
}
|
||||||
+125
-2
@@ -8,6 +8,7 @@ import * as Config from './../config.js';
|
|||||||
|
|
||||||
export let railTracks = [];
|
export let railTracks = [];
|
||||||
export let markers = [];
|
export let markers = [];
|
||||||
|
export let dynamicMarkers = [];
|
||||||
|
|
||||||
let visibleMarkers = [];
|
let visibleMarkers = [];
|
||||||
|
|
||||||
@@ -15,6 +16,16 @@ let uniqueTrackNames = {};
|
|||||||
|
|
||||||
export let testPoint = {x:0,y:0};
|
export let testPoint = {x:0,y:0};
|
||||||
|
|
||||||
|
let locationData = null;
|
||||||
|
export let playerMarker = {
|
||||||
|
type: 'player',
|
||||||
|
position: {x:8192, y:200, z:8192},
|
||||||
|
rotation: 0.0,
|
||||||
|
minZoom: 0.0,
|
||||||
|
hidden: true,
|
||||||
|
};
|
||||||
|
let locationUpdateRate = 500;
|
||||||
|
|
||||||
export let Shops = {
|
export let Shops = {
|
||||||
Common:[]
|
Common:[]
|
||||||
}
|
}
|
||||||
@@ -59,6 +70,7 @@ export let view = {
|
|||||||
view.y = -(mapContainer.clientHeight*0.5 - (matrix.height * 0.5 + matrix.minY)*view.scale);
|
view.y = -(mapContainer.clientHeight*0.5 - (matrix.height * 0.5 + matrix.minY)*view.scale);
|
||||||
}
|
}
|
||||||
view.dirty = false;
|
view.dirty = false;
|
||||||
|
view.dynDirty = false;
|
||||||
|
|
||||||
view.convertX = x => {
|
view.convertX = x => {
|
||||||
return x * view.scale * view.pixelRatio + view.x * view.pixelRatio;
|
return x * view.scale * view.pixelRatio + view.x * view.pixelRatio;
|
||||||
@@ -92,7 +104,13 @@ export function sortTracks(){
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function sortMarkers(){
|
export function sortMarkers(){
|
||||||
markers.sort((a,b) => {
|
markers.sort(markerSortFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
function markerSortFunction(a, b){
|
||||||
|
(a,b) => {
|
||||||
|
if(a.type == 'player') return 1;
|
||||||
|
if(b.type == 'player') return -1;
|
||||||
if(a.type == b.type){
|
if(a.type == b.type){
|
||||||
switch(a.type){
|
switch(a.type){
|
||||||
case 'speed':
|
case 'speed':
|
||||||
@@ -113,7 +131,7 @@ export function sortMarkers(){
|
|||||||
if(a.type == 'demonstratorSpawnHint') return -1;
|
if(a.type == 'demonstratorSpawnHint') return -1;
|
||||||
if(b.type == 'demonstratorSpawnHint') return 1;
|
if(b.type == 'demonstratorSpawnHint') return 1;
|
||||||
return 0;
|
return 0;
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function sortShops(){
|
export function sortShops(){
|
||||||
@@ -498,6 +516,32 @@ export function testTooltip(cursorX, cursorY){
|
|||||||
let radius = 0;
|
let radius = 0;
|
||||||
let width = 0;
|
let width = 0;
|
||||||
let height = 0;
|
let height = 0;
|
||||||
|
for(const marker of dynamicMarkers){
|
||||||
|
if(!marker.visible) continue;
|
||||||
|
if(marker.boundsCheck != null){
|
||||||
|
if(marker.boundsCheck(cursorX, cursorY))
|
||||||
|
finalTooltip = marker.tooltip ?? finalTooltip;
|
||||||
|
}else if(marker.tooltipHitzone && marker.tooltipHitzone.width && marker.tooltipHitzone.height){
|
||||||
|
width = marker.tooltipHitzone.width * 0.5/view.scale;
|
||||||
|
height = marker.tooltipHitzone.height * 0.5/view.scale;
|
||||||
|
if( marker.position.x - cursorX + (marker.tooltipHitzone.offsetX ?? 0)/view.scale <= width
|
||||||
|
&& marker.position.x - cursorX + (marker.tooltipHitzone.offsetX ?? 0)/view.scale >= -width
|
||||||
|
&& marker.position.z - cursorY + (marker.tooltipHitzone.offsetY ?? 0)/view.scale <= height
|
||||||
|
&& marker.position.z - cursorY + (marker.tooltipHitzone.offsetY ?? 0)/view.scale >= -height){
|
||||||
|
finalTooltip = marker.tooltip ?? finalTooltip;
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
if(marker.tooltipHitzone)
|
||||||
|
radius = (marker.tooltipHitzone.radius ?? Config.tooltipHitzone.default.radius) / view.scale;
|
||||||
|
else
|
||||||
|
radius = Config.tooltipHitzone.default.radius / view.scale;
|
||||||
|
if(Math.pow(marker.position.x - cursorX, 2) + Math.pow(marker.position.z - cursorY, 2) <= radius * radius){
|
||||||
|
finalTooltip = marker.tooltip ?? finalTooltip;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(finalTooltip != '') return finalTooltip;
|
||||||
|
|
||||||
for(const marker of markers){
|
for(const marker of markers){
|
||||||
if(!marker.visible) continue;
|
if(!marker.visible) continue;
|
||||||
if(marker.boundsCheck != null){
|
if(marker.boundsCheck != null){
|
||||||
@@ -635,3 +679,82 @@ export function setTrackColorMode(mode){
|
|||||||
}
|
}
|
||||||
view.dirty = true;
|
view.dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setLocationUpdateRate(rate){
|
||||||
|
locationUpdateRate = rate;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function connectPlayerLocation(address, status){
|
||||||
|
if(!address.startsWith('http://')){
|
||||||
|
address.replaceAll(/.*:\/\//g, '');
|
||||||
|
address = 'http://' + address;
|
||||||
|
}
|
||||||
|
address += '/location';
|
||||||
|
console.log(`Connecting to game at ${address}`);
|
||||||
|
let started = false;
|
||||||
|
const endConnect = () => {
|
||||||
|
locationData = null;
|
||||||
|
status.value = 'Connect';
|
||||||
|
playerMarker.hidden = true;
|
||||||
|
view.dynDirty = true;
|
||||||
|
}
|
||||||
|
const pingLocation = async () => {
|
||||||
|
if(locationData == null && started){
|
||||||
|
endConnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status.value = 'Disconnect...';
|
||||||
|
try {
|
||||||
|
const fetchRequest = await fetch(new Request(address), {signal: AbortSignal.timeout(5000)});
|
||||||
|
if(!fetchRequest.ok || (locationData == null && started)){
|
||||||
|
endConnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
status.value = 'Disconnect ';
|
||||||
|
locationData = await (fetchRequest).json();
|
||||||
|
playerMarker.position = {
|
||||||
|
x: locationData.x,
|
||||||
|
y: locationData.y,
|
||||||
|
z: locationData.z,
|
||||||
|
};
|
||||||
|
playerMarker.rotation = locationData.rotation;
|
||||||
|
playerMarker.tooltip = `<h1>Player</h1>X: ${playerMarker.position.x.toFixed(2)}\nY: ${playerMarker.position.y.toFixed(2)}\nZ: ${playerMarker.position.z.toFixed(2)}\nBearing ${(playerMarker.rotation * (180/Math.PI)).toFixed(1)} (${Utils.angleToCardinalDirection(playerMarker.rotation)})`;
|
||||||
|
playerMarker.hidden = false;
|
||||||
|
|
||||||
|
dynamicMarkers = [];
|
||||||
|
if(!locationData.cars) locationData.cars = [];
|
||||||
|
for(let car of locationData.cars){
|
||||||
|
car.name = car.name.replaceAll('(Clone)', '').replaceAll('_',' ').replaceAll(/(Car|Loco)/g, '');
|
||||||
|
dynamicMarkers.push({
|
||||||
|
type: 'car',
|
||||||
|
name: 'car.id',
|
||||||
|
position: {
|
||||||
|
x: car.x,
|
||||||
|
y: car.y,
|
||||||
|
z: car.z
|
||||||
|
},
|
||||||
|
length: car.length,
|
||||||
|
width: 2.0,
|
||||||
|
rotation: car.rotation,
|
||||||
|
guid: car.carGuid,
|
||||||
|
isLoco: car.isLoco,
|
||||||
|
tooltip: `<h1>${car.id}</h1>Type: ${car.name}\nLength: ${car.length.toFixed(1)}m`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
dynamicMarkers.push(playerMarker);
|
||||||
|
|
||||||
|
started = true;
|
||||||
|
view.dynDirty = true;
|
||||||
|
setTimeout(pingLocation, locationUpdateRate);
|
||||||
|
}catch(e){
|
||||||
|
endConnect();
|
||||||
|
console.log(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pingLocation();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disconnectPlayerLocation(){
|
||||||
|
console.log('Disconnecting');
|
||||||
|
locationData = null;
|
||||||
|
}
|
||||||
@@ -162,6 +162,51 @@ let settingEntries = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '<hr><div class="quiet">Community Map Location Mod</div>',
|
||||||
|
id: 'header_cmlm',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Location Host',
|
||||||
|
id: 'field_locationHost',
|
||||||
|
state: 'localhost:9090',
|
||||||
|
button: 'Wait',
|
||||||
|
func: state =>{
|
||||||
|
let button = document.getElementById('field_locationHost').nextSibling;
|
||||||
|
if(button.value.startsWith('Connect')){
|
||||||
|
MapData.connectPlayerLocation(state, button);
|
||||||
|
}else if(button.value.startsWith('Disconnect')){
|
||||||
|
button.value = 'Disconnect';
|
||||||
|
MapData.disconnectPlayerLocation();
|
||||||
|
}else if(!button.value.startsWith('Fetch')){
|
||||||
|
button.value = 'Connect';
|
||||||
|
MapData.disconnectPlayerLocation();
|
||||||
|
}else{
|
||||||
|
MapData.disconnectPlayerLocation();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Location Update Rate (seconds)',
|
||||||
|
id: 'dropdown_locationFrequency',
|
||||||
|
tooltip: 'Lower update intervals may cause poor performance',
|
||||||
|
state: '0.2',
|
||||||
|
options: [
|
||||||
|
'0.05',
|
||||||
|
'0.1',
|
||||||
|
'0.2',
|
||||||
|
'0.25',
|
||||||
|
'0.5',
|
||||||
|
'1',
|
||||||
|
'2',
|
||||||
|
'5',
|
||||||
|
'10',
|
||||||
|
'30',
|
||||||
|
],
|
||||||
|
func: state =>{
|
||||||
|
MapData.setLocationUpdateRate(Number(state) * 1000);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -194,8 +239,8 @@ function addSettingEntry(thisSetting, parent, indent=0){
|
|||||||
thisSetting.labelElement = document.createElement('label');
|
thisSetting.labelElement = document.createElement('label');
|
||||||
thisSetting.divContainer.appendChild(thisSetting.labelElement);
|
thisSetting.divContainer.appendChild(thisSetting.labelElement);
|
||||||
|
|
||||||
let settingType = thisSetting.id.split('_')[0];
|
thisSetting.settingType = thisSetting.id.split('_')[0];
|
||||||
switch(settingType){
|
switch(thisSetting.settingType){
|
||||||
case 'toggle':
|
case 'toggle':
|
||||||
thisSetting.inputElement = document.createElement('input');
|
thisSetting.inputElement = document.createElement('input');
|
||||||
thisSetting.inputElement.type = 'checkbox';
|
thisSetting.inputElement.type = 'checkbox';
|
||||||
@@ -244,6 +289,23 @@ function addSettingEntry(thisSetting, parent, indent=0){
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
case 'field':
|
||||||
|
thisSetting.inputElement = document.createElement('input');
|
||||||
|
thisSetting.inputElement.type = 'text';
|
||||||
|
thisSetting.inputElement.value = thisSetting.state;
|
||||||
|
if(thisSetting.button){
|
||||||
|
thisSetting.buttonElement = document.createElement('input');
|
||||||
|
thisSetting.buttonElement.type = 'button';
|
||||||
|
thisSetting.buttonElement.value = thisSetting.button;
|
||||||
|
if(thisSetting.func){
|
||||||
|
thisSetting.buttonElement.addEventListener('click', e => {
|
||||||
|
thisSetting.func(thisSetting.inputElement.value);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case 'header':
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
thisSetting.divContainer.remove();
|
thisSetting.divContainer.remove();
|
||||||
return;
|
return;
|
||||||
@@ -251,10 +313,18 @@ function addSettingEntry(thisSetting, parent, indent=0){
|
|||||||
parent.appendChild(thisSetting.divContainer);
|
parent.appendChild(thisSetting.divContainer);
|
||||||
thisSetting.labelElement.innerHTML = thisSetting.label;
|
thisSetting.labelElement.innerHTML = thisSetting.label;
|
||||||
thisSetting.labelElement.htmlFor = thisSetting.id;
|
thisSetting.labelElement.htmlFor = thisSetting.id;
|
||||||
thisSetting.inputElement.id = thisSetting.id;
|
if(thisSetting.tooltip) thisSetting.labelElement.title = thisSetting.tooltip;
|
||||||
|
|
||||||
thisSetting.inputElement.style.margin = `0 0.5em 0 ${indent}em`;
|
if(thisSetting.settingType == 'field'){
|
||||||
thisSetting.divContainer.prepend(thisSetting.inputElement);
|
thisSetting.buttonElement.style.margin = `0 0.5em 0 ${indent}em`;
|
||||||
|
thisSetting.buttonElement.style.padding = `0 0.2em`;
|
||||||
|
thisSetting.divContainer.prepend(thisSetting.buttonElement);
|
||||||
|
}
|
||||||
|
if(thisSetting.inputElement){
|
||||||
|
thisSetting.inputElement.id = thisSetting.id;
|
||||||
|
thisSetting.inputElement.style.margin = `0 0.5em 0 ${indent}em`;
|
||||||
|
thisSetting.divContainer.prepend(thisSetting.inputElement);
|
||||||
|
}
|
||||||
|
|
||||||
if(thisSetting.func){
|
if(thisSetting.func){
|
||||||
thisSetting.func(thisSetting.state);
|
thisSetting.func(thisSetting.state);
|
||||||
@@ -304,10 +374,11 @@ function populateKey(){
|
|||||||
addKeyEntry('landmark', 'Landmark');
|
addKeyEntry('landmark', 'Landmark');
|
||||||
addKeyEntry('garage', 'Garage');
|
addKeyEntry('garage', 'Garage');
|
||||||
addKeyEntry('demonstrator', 'Demonstrator Spawn');
|
addKeyEntry('demonstrator', 'Demonstrator Spawn');
|
||||||
|
addKeyEntry('player', 'Player');
|
||||||
//Add empty entries to make entries align right in the columns.
|
//Add empty entries to make entries align right in the columns.
|
||||||
for(let i=0; i<3; i++){
|
/*for(let i=0; i<1; i++){
|
||||||
addKeyEntry();
|
addKeyEntry();
|
||||||
}
|
}*/
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
+18
-7
@@ -12,7 +12,8 @@
|
|||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
xmlns:svg="http://www.w3.org/2000/svg"><defs
|
||||||
id="defs1" /><g
|
id="defs1" /><g
|
||||||
id="g33"><rect
|
id="g33"
|
||||||
|
style="display:inline"><rect
|
||||||
style="fill:#bb3f3f;stroke:#ebebeb;stroke-width:0.9"
|
style="fill:#bb3f3f;stroke:#ebebeb;stroke-width:0.9"
|
||||||
id="rect32"
|
id="rect32"
|
||||||
width="9.9940004"
|
width="9.9940004"
|
||||||
@@ -30,7 +31,8 @@
|
|||||||
ry="0.98900002"
|
ry="0.98900002"
|
||||||
transform="rotate(22.53)"
|
transform="rotate(22.53)"
|
||||||
rx="0.98900002" /></g><g
|
rx="0.98900002" /></g><g
|
||||||
id="g34"><circle
|
id="g34"
|
||||||
|
style="display:inline"><circle
|
||||||
style="opacity:1;fill:#702012;fill-opacity:1;stroke:#e73819;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#702012;fill-opacity:1;stroke:#e73819;stroke-width:3;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
id="path33"
|
id="path33"
|
||||||
cx="10"
|
cx="10"
|
||||||
@@ -163,14 +165,15 @@
|
|||||||
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.24675;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
style="display:inline;opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.24675;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="M 0,-3.4155845 1.3234947,0.4155845 0,-0.44155834 -1.3234947,0.4155845 Z"
|
d="M 0,-3.4155845 1.3234947,0.4155845 0,-0.44155834 -1.3234947,0.4155845 Z"
|
||||||
id="path60" /></g></g><g
|
id="path60" /></g></g><g
|
||||||
id="layer1"><circle
|
id="layer1"
|
||||||
|
style="display:inline"><circle
|
||||||
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.99998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#000000;fill-opacity:1;stroke:none;stroke-width:1.99998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
id="path54"
|
id="path54"
|
||||||
cx="10"
|
cx="10"
|
||||||
cy="10"
|
cy="10"
|
||||||
r="10" /><path
|
r="10" /><path
|
||||||
style="opacity:1;fill:#fff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:none;stroke-width:2;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
d="m8.92 1.99q3.29 0 4.68 1.13 1.39 1.1 1.39 2.74 0 2.06-1.78 3.36-.67.5-1.63.91l-.38.17q-.17.07-.34 1.01-.14.91-.14 1.32-.12.12-.7.29-.55.14-.94.14-.36 0-.55-.05-.26-.43-.26-1.87 0-1.44.12-1.85 3.79-.91 3.79-3.07 0-1.87-3.46-1.87-1.22 0-1.78.24-.14-.24-.14-1.01 0-.77.22-1.3.65-.29 1.9-.29zm-.7 15.5q-.46-.48-.46-1.15 0-.67.46-1.13.48-.48 1.15-.48.67 0 1.13.48.48.46.48 1.13 0 .67-.48 1.15-.46.46-1.13.46-.67 0-1.15-.46z"
|
d="m 8.92,1.99 q 3.29,0 4.68,1.13 1.39,1.1 1.39,2.74 0,2.06 -1.78,3.36 -0.67,0.5 -1.63,0.91 L 11.2,10.3 q -0.17,0.07 -0.34,1.01 -0.14,0.91 -0.14,1.32 -0.12,0.12 -0.7,0.29 -0.55,0.14 -0.94,0.14 -0.36,0 -0.55,-0.05 Q 8.27,12.58 8.27,11.14 8.27,9.7 8.39,9.29 12.18,8.38 12.18,6.22 12.18,4.35 8.72,4.35 7.5,4.35 6.94,4.59 6.8,4.35 6.8,3.58 6.8,2.81 7.02,2.28 7.67,1.99 8.92,1.99 Z m -0.7,15.5 q -0.46,-0.48 -0.46,-1.15 0,-0.67 0.46,-1.13 0.48,-0.48 1.15,-0.48 0.67,0 1.13,0.48 0.48,0.46 0.48,1.13 0,0.67 -0.48,1.15 -0.46,0.46 -1.13,0.46 -0.67,0 -1.15,-0.46 z"
|
||||||
id="path55" /><circle
|
id="path55" /><circle
|
||||||
style="opacity:1;fill:#c4693e;fill-opacity:1;stroke:none;stroke-width:1.99998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#c4693e;fill-opacity:1;stroke:none;stroke-width:1.99998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
id="circle1"
|
id="circle1"
|
||||||
@@ -190,7 +193,7 @@
|
|||||||
xlink:href="#path1"
|
xlink:href="#path1"
|
||||||
id="use2"
|
id="use2"
|
||||||
transform="translate(20)" /><circle
|
transform="translate(20)" /><circle
|
||||||
style="opacity:1;fill:#b44;fill-opacity:1;stroke:none;stroke-width:1.99998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
style="opacity:1;fill:#bb4444;fill-opacity:1;stroke:none;stroke-width:1.99998;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
id="circle3"
|
id="circle3"
|
||||||
cx="50"
|
cx="50"
|
||||||
cy="90"
|
cy="90"
|
||||||
@@ -279,4 +282,12 @@
|
|||||||
r="10" /><path
|
r="10" /><path
|
||||||
id="path13"
|
id="path13"
|
||||||
style="fill:#ffffff;fill-opacity:1;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
style="fill:#ffffff;fill-opacity:1;stroke-width:2;stroke-linecap:round;stroke-linejoin:round"
|
||||||
d="m 115.6875,105.5 -0.125,2.5625 -2.5625,0.0625 -0.875,1.1875 0.875,-0.0625 -2.07583,2.73928 0.45267,-2.13259 -1.08081,0.0183 0.37814,-1.37814 -3.61167,0.19064 L 107,106.5 l 0.4375,0.0625 0.125,-0.9375 -5.25,0.375 0.8125,6.5625 12.6875,-0.4375 -0.125,1.5625 h 2.125 L 117.25,111.5 l 0.4375,-3.5625 h -0.875 l 0.25,-2.25 z m -9.48828,1.14062 0.0879,2.1211 -2.29883,0.13281 -0.4414,-1.94531 z m 8.17578,5.98438 c -0.51777,0 -0.9375,0.4757 -0.9375,1.0625 0,0.5868 0.41973,1.0625 0.9375,1.0625 0.51777,0 0.9375,-0.4757 0.9375,-1.0625 0,-0.5868 -0.41973,-1.0625 -0.9375,-1.0625 z m -2.125,0.0625 c -0.5868,0 -1.0625,0.51767 -1.0625,1.15625 0,0.63858 0.4757,1.15625 1.0625,1.15625 0.5868,0 1.0625,-0.51767 1.0625,-1.15625 0,-0.63858 -0.4757,-1.15625 -1.0625,-1.15625 z m -2.65625,0.125 c -0.63858,0 -1.15625,0.51767 -1.15625,1.15625 0,0.63858 0.51767,1.15625 1.15625,1.15625 0.63858,0 1.15625,-0.51767 1.15625,-1.15625 0,-0.63858 -0.51767,-1.15625 -1.15625,-1.15625 z m -2.625,0.25 c -0.63858,0 -1.15625,0.48969 -1.15625,1.09375 0,0.60406 0.51767,1.09375 1.15625,1.09375 0.63858,0 1.15625,-0.48969 1.15625,-1.09375 0,-0.60406 -0.51767,-1.09375 -1.15625,-1.09375 z m 9.125,0.75 c -0.25888,0 -0.46875,0.22386 -0.46875,0.5 0,0.27614 0.20987,0.5 0.46875,0.5 0.25888,0 0.46875,-0.22386 0.46875,-0.5 0,-0.27614 -0.20987,-0.5 -0.46875,-0.5 z m -11.9375,0.5 c -0.25888,0 -0.46875,0.22386 -0.46875,0.5 0,0.27614 0.20987,0.5 0.46875,0.5 0.25888,0 0.46875,-0.22386 0.46875,-0.5 0,-0.27614 -0.20987,-0.5 -0.46875,-0.5 z" /></g></svg>
|
d="m 115.6875,105.5 -0.125,2.5625 -2.5625,0.0625 -0.875,1.1875 0.875,-0.0625 -2.07583,2.73928 0.45267,-2.13259 -1.08081,0.0183 0.37814,-1.37814 -3.61167,0.19064 L 107,106.5 l 0.4375,0.0625 0.125,-0.9375 -5.25,0.375 0.8125,6.5625 12.6875,-0.4375 -0.125,1.5625 h 2.125 L 117.25,111.5 l 0.4375,-3.5625 h -0.875 l 0.25,-2.25 z m -9.48828,1.14062 0.0879,2.1211 -2.29883,0.13281 -0.4414,-1.94531 z m 8.17578,5.98438 c -0.51777,0 -0.9375,0.4757 -0.9375,1.0625 0,0.5868 0.41973,1.0625 0.9375,1.0625 0.51777,0 0.9375,-0.4757 0.9375,-1.0625 0,-0.5868 -0.41973,-1.0625 -0.9375,-1.0625 z m -2.125,0.0625 c -0.5868,0 -1.0625,0.51767 -1.0625,1.15625 0,0.63858 0.4757,1.15625 1.0625,1.15625 0.5868,0 1.0625,-0.51767 1.0625,-1.15625 0,-0.63858 -0.4757,-1.15625 -1.0625,-1.15625 z m -2.65625,0.125 c -0.63858,0 -1.15625,0.51767 -1.15625,1.15625 0,0.63858 0.51767,1.15625 1.15625,1.15625 0.63858,0 1.15625,-0.51767 1.15625,-1.15625 0,-0.63858 -0.51767,-1.15625 -1.15625,-1.15625 z m -2.625,0.25 c -0.63858,0 -1.15625,0.48969 -1.15625,1.09375 0,0.60406 0.51767,1.09375 1.15625,1.09375 0.63858,0 1.15625,-0.48969 1.15625,-1.09375 0,-0.60406 -0.51767,-1.09375 -1.15625,-1.09375 z m 9.125,0.75 c -0.25888,0 -0.46875,0.22386 -0.46875,0.5 0,0.27614 0.20987,0.5 0.46875,0.5 0.25888,0 0.46875,-0.22386 0.46875,-0.5 0,-0.27614 -0.20987,-0.5 -0.46875,-0.5 z m -11.9375,0.5 c -0.25888,0 -0.46875,0.22386 -0.46875,0.5 0,0.27614 0.20987,0.5 0.46875,0.5 0.25888,0 0.46875,-0.22386 0.46875,-0.5 0,-0.27614 -0.20987,-0.5 -0.46875,-0.5 z" /><path
|
||||||
|
id="circle14"
|
||||||
|
style="opacity:1;fill:#7bb5ff;fill-opacity:1;stroke:#000000;stroke-width:1;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1;paint-order:stroke fill markers"
|
||||||
|
d="M 30,1.2539062 C 27.479868,3.8850658 24.073484,6.0853211 23.789062,10 23.788759,13.430332 26.569668,16.211241 30,16.210938 33.430332,16.211241 36.211241,13.430332 36.210938,10 35.999554,6.1494588 32.53281,3.7856267 30,1.2539062 Z" /><circle
|
||||||
|
style="opacity:1;fill:#ffffff;fill-opacity:1;stroke:#000000;stroke-width:0.5;stroke-linecap:round;stroke-linejoin:round;stroke-dasharray:none;stroke-opacity:1"
|
||||||
|
id="circle15"
|
||||||
|
cx="30"
|
||||||
|
cy="10"
|
||||||
|
r="4.7519341" /></g></svg>
|
||||||
|
Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 42 KiB |
@@ -122,9 +122,12 @@ a:visited{
|
|||||||
|
|
||||||
#settingsPanel{
|
#settingsPanel{
|
||||||
user-select: none;
|
user-select: none;
|
||||||
input, label{
|
input:not([type=text]), label{
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
input[type=text], div{
|
||||||
|
cursor:unset;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#stats{
|
#stats{
|
||||||
@@ -161,6 +164,12 @@ a:visited{
|
|||||||
background: linear-gradient(#444, #333);
|
background: linear-gradient(#444, #333);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#mapCanvas, #dynCanvas{
|
||||||
|
position: fixed;
|
||||||
|
width:100%;
|
||||||
|
height:100%;
|
||||||
|
}
|
||||||
|
|
||||||
.colorKey, .gradientKey{
|
.colorKey, .gradientKey{
|
||||||
border:1px #858585 solid;
|
border:1px #858585 solid;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|||||||
Reference in new issue
Block a user