chore: initial commit

This commit is contained in:
Anna 2023-10-05 00:04:02 -04:00
commit 5e6cffbb9c
Signed by: anna
GPG Key ID: D0943384CD9F87D1
12 changed files with 2287 additions and 0 deletions

5
client/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
bin/
obj/
/packages/
riderModule.iml
/_ReSharper.Caches/

60
client/PlayerMap.csproj Normal file
View File

@ -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>

16
client/PlayerMap.sln Normal file
View File

@ -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

108
client/Plugin.cs Normal file
View File

@ -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;
};

13
client/packages.lock.json Normal file
View File

@ -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=="
}
}
}
}

1
server/.env Normal file
View File

@ -0,0 +1 @@
DATABASE_URL=sqlite://./database.sqlite

2
server/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/target
/database.sqlite

1909
server/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

13
server/Cargo.toml Normal file
View File

@ -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"] }

View File

@ -0,0 +1 @@
drop table players;

View File

@ -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);

138
server/src/main.rs Normal file
View File

@ -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())
}
}