chore: initial commit
This commit is contained in:
commit
5e6cffbb9c
|
@ -0,0 +1,5 @@
|
|||
bin/
|
||||
obj/
|
||||
/packages/
|
||||
riderModule.iml
|
||||
/_ReSharper.Caches/
|
|
@ -0,0 +1,60 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.0-alpha.1</Version>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
|
||||
<ProduceReferenceAssembly>false</ProduceReferenceAssembly>
|
||||
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
|
||||
<RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
|
||||
<LangVersion>preview</LangVersion>
|
||||
<DebugSymbols>true</DebugSymbols>
|
||||
<DebugType>full</DebugType>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
<DalamudLibPath>$(AppData)\XIVLauncher\addon\Hooks\dev</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$([System.Runtime.InteropServices.RuntimeInformation]::IsOSPlatform($([System.Runtime.InteropServices.OSPlatform]::Linux)))'">
|
||||
<DalamudLibPath>$(DALAMUD_HOME)</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(IsCI)' == 'true'">
|
||||
<DalamudLibPath>$(HOME)/dalamud</DalamudLibPath>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Dalamud">
|
||||
<HintPath>$(DalamudLibPath)\Dalamud.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="FFXIVClientStructs">
|
||||
<HintPath>$(DalamudLibPath)\FFXIVClientStructs.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="ImGui.NET">
|
||||
<HintPath>$(DalamudLibPath)\ImGui.NET.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Lumina.Excel">
|
||||
<HintPath>$(DalamudLibPath)\Lumina.Excel.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
<Reference Include="Newtonsoft.Json">
|
||||
<HintPath>$(DalamudLibPath)\Newtonsoft.Json.dll</HintPath>
|
||||
<Private>false</Private>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
|
@ -0,0 +1,16 @@
|
|||
|
||||
Microsoft Visual Studio Solution File, Format Version 12.00
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PlayerMap", "PlayerMap.csproj", "{3F5E5BFF-B5B9-416C-BAD6-5F154B8A7A35}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
Release|Any CPU = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(ProjectConfigurationPlatforms) = postSolution
|
||||
{3F5E5BFF-B5B9-416C-BAD6-5F154B8A7A35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{3F5E5BFF-B5B9-416C-BAD6-5F154B8A7A35}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{3F5E5BFF-B5B9-416C-BAD6-5F154B8A7A35}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{3F5E5BFF-B5B9-416C-BAD6-5F154B8A7A35}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
EndGlobal
|
|
@ -0,0 +1,108 @@
|
|||
using System.Net.Http.Headers;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.IoC;
|
||||
using Dalamud.Plugin;
|
||||
using Dalamud.Plugin.Services;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace PlayerMap;
|
||||
|
||||
public class Plugin : IDalamudPlugin {
|
||||
[PluginService]
|
||||
internal IFramework Framework { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IObjectTable ObjectTable { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IClientState ClientState { get; init; }
|
||||
|
||||
private HttpClient Http { get; } = new();
|
||||
|
||||
public Plugin() {
|
||||
this.Framework!.Update += this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Framework.Update -= this.FrameworkUpdate;
|
||||
}
|
||||
|
||||
private void FrameworkUpdate(IFramework framework) {
|
||||
var territory = this.ClientState.TerritoryType;
|
||||
var players = this.ObjectTable
|
||||
.Where(obj => obj.ObjectKind == ObjectKind.Player && obj is PlayerCharacter)
|
||||
.Cast<PlayerCharacter>()
|
||||
.Select(player => {
|
||||
var pos = player.Position;
|
||||
return new PlayerInfo(
|
||||
player.Name.TextValue,
|
||||
player.HomeWorld.Id,
|
||||
pos.X,
|
||||
pos.Y,
|
||||
pos.Z,
|
||||
player.Rotation,
|
||||
player.Customize,
|
||||
player.Level,
|
||||
player.ClassJob.Id,
|
||||
player.CompanyTag.TextValue,
|
||||
player.CurrentHp,
|
||||
player.MaxHp
|
||||
);
|
||||
})
|
||||
.ToList();
|
||||
|
||||
var update = new Update(territory, players);
|
||||
var json = JsonConvert.SerializeObject(update, new JsonSerializerSettings {
|
||||
ContractResolver = new DefaultContractResolver {
|
||||
NamingStrategy = new SnakeCaseNamingStrategy(),
|
||||
},
|
||||
});
|
||||
|
||||
Task.Run(async () => {
|
||||
var req = new HttpRequestMessage(HttpMethod.Post, "http://localhost:30888/upload") {
|
||||
Content = new StringContent(json) {
|
||||
Headers = { ContentType = new MediaTypeHeaderValue("application/json") },
|
||||
},
|
||||
};
|
||||
|
||||
await this.Http.SendAsync(req);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct Update(uint territory, List<PlayerInfo> players) {
|
||||
public readonly uint Territory = territory;
|
||||
public readonly List<PlayerInfo> Players = players;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal struct PlayerInfo(
|
||||
string name,
|
||||
uint world,
|
||||
float x,
|
||||
float y,
|
||||
float z,
|
||||
float w,
|
||||
byte[] customize,
|
||||
byte level,
|
||||
uint job,
|
||||
string freeCompany,
|
||||
uint currentHp,
|
||||
uint maxHp
|
||||
) {
|
||||
public readonly string Name = name;
|
||||
public readonly uint World = world;
|
||||
public readonly float X = x;
|
||||
public readonly float Y = y;
|
||||
public readonly float Z = z;
|
||||
public readonly float W = w;
|
||||
public readonly byte[] Customize = customize;
|
||||
public readonly byte Level = level;
|
||||
public readonly uint Job = job;
|
||||
public readonly string FreeCompany = freeCompany;
|
||||
public readonly uint CurrentHp = currentHp;
|
||||
public readonly uint MaxHp = maxHp;
|
||||
};
|
|
@ -0,0 +1,13 @@
|
|||
{
|
||||
"version": 1,
|
||||
"dependencies": {
|
||||
"net7.0-windows7.0": {
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.1.12, )",
|
||||
"resolved": "2.1.12",
|
||||
"contentHash": "Sc0PVxvgg4NQjcI8n10/VfUQBAS4O+Fw2pZrAqBdRMbthYGeogzu5+xmIGCGmsEZ/ukMOBuAqiNiB5qA3MRalg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
DATABASE_URL=sqlite://./database.sqlite
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/database.sqlite
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,13 @@
|
|||
[package]
|
||||
name = "player-map-server"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1"
|
||||
axum = "0.6"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
sqlx = { version = "0.7", features = ["sqlite", "chrono"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
|
@ -0,0 +1 @@
|
|||
drop table players;
|
|
@ -0,0 +1,21 @@
|
|||
create table players
|
||||
(
|
||||
name text not null,
|
||||
world int not null,
|
||||
timestamp timestamp with time zone not null,
|
||||
territory int not null,
|
||||
x float not null,
|
||||
y float not null,
|
||||
z float not null,
|
||||
w float not null,
|
||||
customize blob not null,
|
||||
level smallint not null,
|
||||
job int not null,
|
||||
free_company text,
|
||||
current_hp int not null,
|
||||
max_hp int not null,
|
||||
|
||||
primary key (name, world)
|
||||
);
|
||||
|
||||
create index players_territory_idx on players (territory);
|
|
@ -0,0 +1,138 @@
|
|||
use std::sync::Arc;
|
||||
use anyhow::Result;
|
||||
use axum::{Json, Router, Server};
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use serde::Deserialize;
|
||||
use sqlx::SqlitePool;
|
||||
|
||||
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!();
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let pool = SqlitePool::connect("./database.sqlite").await?;
|
||||
MIGRATOR.run(&pool).await?;
|
||||
|
||||
let app = Router::new()
|
||||
.route("/", get(index))
|
||||
.route("/upload", post(upload))
|
||||
.with_state(Arc::new(pool));
|
||||
|
||||
Server::bind(&"127.0.0.1:30888".parse()?)
|
||||
.serve(app.into_make_service())
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn index() -> &'static str {
|
||||
"hi"
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
pool: State<Arc<SqlitePool>>,
|
||||
data: Json<Update>,
|
||||
) -> Result<(), AppError> {
|
||||
let mut t = pool.begin().await?;
|
||||
|
||||
for player in &data.players {
|
||||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"
|
||||
insert into players (name, world, timestamp, territory, x, y, z, w, customize, level, job, free_company, current_hp,
|
||||
max_hp)
|
||||
values (?, ?, current_timestamp, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
on conflict (name, world) do update set timestamp = current_timestamp,
|
||||
territory = ?,
|
||||
x = ?,
|
||||
y = ?,
|
||||
z = ?,
|
||||
w = ?,
|
||||
customize = ?,
|
||||
level = ?,
|
||||
job = ?,
|
||||
free_company = ?,
|
||||
current_hp = ?,
|
||||
max_hp = ?
|
||||
",
|
||||
player.name,
|
||||
player.world,
|
||||
data.territory,
|
||||
player.x,
|
||||
player.y,
|
||||
player.z,
|
||||
player.w,
|
||||
player.customize,
|
||||
player.level,
|
||||
player.job,
|
||||
player.free_company,
|
||||
player.current_hp,
|
||||
player.max_hp,
|
||||
|
||||
data.territory,
|
||||
player.x,
|
||||
player.y,
|
||||
player.z,
|
||||
player.w,
|
||||
player.customize,
|
||||
player.level,
|
||||
player.job,
|
||||
player.free_company,
|
||||
player.current_hp,
|
||||
player.max_hp,
|
||||
)
|
||||
.execute(&mut *t)
|
||||
.await?;
|
||||
}
|
||||
|
||||
t.commit().await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct Update {
|
||||
territory: u32,
|
||||
players: Vec<PlayerInfo>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct PlayerInfo {
|
||||
name: String,
|
||||
world: u32,
|
||||
x: f32,
|
||||
y: f32,
|
||||
z: f32,
|
||||
w: f32,
|
||||
customize: Vec<u8>,
|
||||
level: u8,
|
||||
job: u32,
|
||||
free_company: String,
|
||||
current_hp: u32,
|
||||
max_hp: u32,
|
||||
}
|
||||
|
||||
// Make our own error that wraps `anyhow::Error`.
|
||||
struct AppError(anyhow::Error);
|
||||
|
||||
// Tell axum how to convert `AppError` into a response.
|
||||
impl IntoResponse for AppError {
|
||||
fn into_response(self) -> Response {
|
||||
(
|
||||
StatusCode::INTERNAL_SERVER_ERROR,
|
||||
format!("Something went wrong: {}", self.0),
|
||||
)
|
||||
.into_response()
|
||||
}
|
||||
}
|
||||
|
||||
// This enables using `?` on functions that return `Result<_, anyhow::Error>` to turn them into
|
||||
// `Result<_, AppError>`. That way you don't need to do that manually.
|
||||
impl<E> From<E> for AppError
|
||||
where
|
||||
E: Into<anyhow::Error>,
|
||||
{
|
||||
fn from(err: E) -> Self {
|
||||
Self(err.into())
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue