Compare commits
5 Commits
b19aa5247c
...
6c3954c455
Author | SHA1 | Date |
---|---|---|
Anna | 6c3954c455 | |
Anna | c8955ee58b | |
Anna | d792c9d09d | |
Anna | 630b2ddc0b | |
Anna | 03c67fa26c |
|
@ -0,0 +1,10 @@
|
|||
using Dalamud.Configuration;
|
||||
|
||||
namespace PlayerMap;
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration {
|
||||
public int Version { get; set; } = 1;
|
||||
|
||||
public int UpdateFrequency { get; set; } = 5;
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
namespace PlayerMap;
|
||||
|
||||
internal class OnDispose : IDisposable {
|
||||
private Action Action { get; }
|
||||
|
||||
private bool _disposed;
|
||||
|
||||
internal OnDispose(Action action) {
|
||||
this.Action = action;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (this._disposed) {
|
||||
return;
|
||||
}
|
||||
|
||||
this._disposed = true;
|
||||
this.Action();
|
||||
}
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<Version>1.0.1</Version>
|
||||
<Version>1.0.3</Version>
|
||||
<TargetFramework>net7.0-windows</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
|
@ -31,10 +31,6 @@
|
|||
<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>
|
||||
|
@ -54,6 +50,7 @@
|
|||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Blake3" Version="0.6.1"/>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.12"/>
|
||||
<PackageReference Include="MessagePack" Version="2.5.129"/>
|
||||
</ItemGroup>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
using System.Diagnostics;
|
||||
using System.IO.Compression;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Text;
|
||||
using Blake3;
|
||||
using Dalamud.Game.ClientState.Objects.Enums;
|
||||
using Dalamud.Game.ClientState.Objects.SubKinds;
|
||||
using Dalamud.IoC;
|
||||
|
@ -14,6 +16,9 @@ public class Plugin : IDalamudPlugin {
|
|||
[PluginService]
|
||||
internal static IPluginLog Log { get; private set; }
|
||||
|
||||
[PluginService]
|
||||
internal DalamudPluginInterface Interface { get; init; }
|
||||
|
||||
[PluginService]
|
||||
internal IFramework Framework { get; init; }
|
||||
|
||||
|
@ -23,20 +28,35 @@ public class Plugin : IDalamudPlugin {
|
|||
[PluginService]
|
||||
internal IClientState ClientState { get; init; }
|
||||
|
||||
internal Configuration Config { get; }
|
||||
internal PluginUi Ui { get; }
|
||||
|
||||
private HttpClient Http { get; } = new();
|
||||
private Stopwatch Stopwatch { get; } = new();
|
||||
private Blake3HashAlgorithm Hasher { get; } = new();
|
||||
|
||||
public Plugin() {
|
||||
this.Hasher.Initialize();
|
||||
|
||||
this.Framework!.Update += this.FrameworkUpdate;
|
||||
this.Config = this.Interface!.GetPluginConfig() as Configuration ?? new Configuration();
|
||||
this.Ui = new PluginUi(this);
|
||||
|
||||
this.Stopwatch.Start();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Ui.Dispose();
|
||||
this.Framework.Update -= this.FrameworkUpdate;
|
||||
this.Hasher.Dispose();
|
||||
}
|
||||
|
||||
internal void SaveConfig() {
|
||||
this.Interface.SavePluginConfig(this.Config);
|
||||
}
|
||||
|
||||
private void FrameworkUpdate(IFramework framework) {
|
||||
if (this.Stopwatch.Elapsed < TimeSpan.FromSeconds(5)) {
|
||||
if (this.Stopwatch.Elapsed < TimeSpan.FromSeconds(5) || this.ClientState.LocalPlayer is not { } localPlayer) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -46,17 +66,25 @@ public class Plugin : IDalamudPlugin {
|
|||
var players = this.ObjectTable
|
||||
.Where(obj => obj.ObjectKind == ObjectKind.Player && obj is PlayerCharacter)
|
||||
.Cast<PlayerCharacter>()
|
||||
.Where(player => player.IsValid() && player.Level > 0)
|
||||
.Where(
|
||||
player => player.IsValid()
|
||||
&& player.Level > 0
|
||||
&& player.HomeWorld.Id != 65535
|
||||
&& player.Name.TextValue.Trim().Length > 0
|
||||
)
|
||||
.Select(player => {
|
||||
var customizeHex = string.Join("", player.Customize.Select(x => x.ToString("x2")));
|
||||
var key = $"don't be creepy-{player.Name.TextValue.Trim()}-{player.HomeWorld.Id}-{customizeHex}";
|
||||
var hash = this.Hasher.ComputeHash(Encoding.UTF8.GetBytes(key));
|
||||
var pos = player.Position;
|
||||
return new PlayerInfo(
|
||||
player.Name.TextValue,
|
||||
hash,
|
||||
player.HomeWorld.Id,
|
||||
pos.X,
|
||||
pos.Y,
|
||||
pos.Z,
|
||||
player.Rotation,
|
||||
player.Customize.ToList(),
|
||||
player.Customize,
|
||||
player.Level,
|
||||
player.ClassJob.Id,
|
||||
player.CompanyTag.TextValue,
|
||||
|
@ -66,7 +94,7 @@ public class Plugin : IDalamudPlugin {
|
|||
})
|
||||
.ToList();
|
||||
|
||||
if (players.Count == 0 || this.ClientState.LocalPlayer is not { } player) {
|
||||
if (players.Count == 0) {
|
||||
Log.Verbose("no players to upload");
|
||||
return;
|
||||
}
|
||||
|
@ -75,7 +103,7 @@ public class Plugin : IDalamudPlugin {
|
|||
// shuffle so first player isn't always local player
|
||||
players.Shuffle();
|
||||
|
||||
var update = new Update(territory, player.CurrentWorld.Id, players);
|
||||
var update = new Update(territory, localPlayer.CurrentWorld.Id, players);
|
||||
var msgpack = MessagePackSerializer.Serialize(update, MessagePackSerializerOptions.Standard);
|
||||
|
||||
using var mem = new MemoryStream();
|
||||
|
@ -114,25 +142,28 @@ public class Plugin : IDalamudPlugin {
|
|||
[MessagePackObject]
|
||||
public struct Update(uint territory, uint world, List<PlayerInfo> players) {
|
||||
[Key(0)]
|
||||
public readonly uint Territory = territory;
|
||||
public readonly byte Version = 2;
|
||||
|
||||
[Key(1)]
|
||||
public readonly uint World = world;
|
||||
public readonly uint Territory = territory;
|
||||
|
||||
[Key(2)]
|
||||
public readonly uint World = world;
|
||||
|
||||
[Key(3)]
|
||||
public readonly List<PlayerInfo> Players = players;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[MessagePackObject]
|
||||
public struct PlayerInfo(
|
||||
string name,
|
||||
byte[] hash,
|
||||
uint world,
|
||||
float x,
|
||||
float y,
|
||||
float z,
|
||||
float w,
|
||||
List<byte> customize,
|
||||
byte[] customize,
|
||||
byte level,
|
||||
uint job,
|
||||
string freeCompany,
|
||||
|
@ -140,7 +171,7 @@ public struct PlayerInfo(
|
|||
uint maxHp
|
||||
) {
|
||||
[Key(0)]
|
||||
public readonly string Name = name;
|
||||
public readonly byte[] Hash = hash;
|
||||
|
||||
[Key(1)]
|
||||
public readonly uint World = world;
|
||||
|
@ -158,7 +189,7 @@ public struct PlayerInfo(
|
|||
public readonly float W = w;
|
||||
|
||||
[Key(6)]
|
||||
public readonly List<byte> Customize = customize;
|
||||
public readonly byte[] Customize = customize;
|
||||
|
||||
[Key(7)]
|
||||
public readonly byte Level = level;
|
||||
|
|
|
@ -0,0 +1,48 @@
|
|||
using ImGuiNET;
|
||||
|
||||
namespace PlayerMap;
|
||||
|
||||
internal class PluginUi : IDisposable {
|
||||
private Plugin Plugin { get; }
|
||||
|
||||
private bool _visible;
|
||||
|
||||
internal PluginUi(Plugin plugin) {
|
||||
this.Plugin = plugin;
|
||||
|
||||
this.Plugin.Interface.UiBuilder.Draw += this.Draw;
|
||||
this.Plugin.Interface.UiBuilder.OpenConfigUi += this.OpenConfig;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
this.Plugin.Interface.UiBuilder.OpenConfigUi -= this.OpenConfig;
|
||||
this.Plugin.Interface.UiBuilder.Draw -= this.Draw;
|
||||
}
|
||||
|
||||
private void OpenConfig() {
|
||||
this._visible = true;
|
||||
}
|
||||
|
||||
private void Draw() {
|
||||
if (!this._visible) {
|
||||
return;
|
||||
}
|
||||
|
||||
using var end = new OnDispose(ImGui.End);
|
||||
if (!ImGui.Begin("Player Map settings", ref this._visible, ImGuiWindowFlags.AlwaysAutoResize)) {
|
||||
return;
|
||||
}
|
||||
|
||||
var anyChanged = false;
|
||||
|
||||
var freq = this.Plugin.Config.UpdateFrequency;
|
||||
if (ImGui.DragInt("Upload frequency (secs)", ref freq, 0.05f, 1, 60)) {
|
||||
anyChanged = true;
|
||||
this.Plugin.Config.UpdateFrequency = freq;
|
||||
}
|
||||
|
||||
if (anyChanged) {
|
||||
this.Plugin.SaveConfig();
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,12 @@
|
|||
"version": 1,
|
||||
"dependencies": {
|
||||
"net7.0-windows7.0": {
|
||||
"Blake3": {
|
||||
"type": "Direct",
|
||||
"requested": "[0.6.1, )",
|
||||
"resolved": "0.6.1",
|
||||
"contentHash": "YlEweiQX1iMXh9HPpoRzSKeLMuKPnMJJWTdqnP1NJV0XwwDwO7WrwLlj60pGk0vJWbeZLZOh06U70+2z0DQCsQ=="
|
||||
},
|
||||
"DalamudPackager": {
|
||||
"type": "Direct",
|
||||
"requested": "[2.1.12, )",
|
||||
|
|
|
@ -320,6 +320,12 @@ dependencies = [
|
|||
"typenum",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e66c9d817f1720209181c316d28635c050fa304f9c79e47a520882661b7308"
|
||||
|
||||
[[package]]
|
||||
name = "der"
|
||||
version = "0.7.8"
|
||||
|
@ -1057,9 +1063,13 @@ dependencies = [
|
|||
"axum",
|
||||
"bytes",
|
||||
"chrono",
|
||||
"data-encoding",
|
||||
"rand",
|
||||
"rmp-serde",
|
||||
"serde",
|
||||
"serde_bytes",
|
||||
"serde_path_to_error",
|
||||
"siphasher",
|
||||
"sqlx",
|
||||
"tokio",
|
||||
"tower",
|
||||
|
@ -1219,6 +1229,15 @@ dependencies = [
|
|||
"serde_derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_bytes"
|
||||
version = "0.11.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ab33ec92f677585af6d88c65593ae2375adde54efdbf16d597f2cbc7a6d368ff"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "serde_derive"
|
||||
version = "1.0.188"
|
||||
|
@ -1295,6 +1314,12 @@ dependencies = [
|
|||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "siphasher"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "54ac45299ccbd390721be55b412d41931911f654fa99e2cb8bfb57184b2061fe"
|
||||
|
||||
[[package]]
|
||||
name = "slab"
|
||||
version = "0.4.9"
|
||||
|
|
|
@ -10,9 +10,13 @@ anyhow = "1"
|
|||
axum = "0.6"
|
||||
bytes = "1"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
data-encoding = "2"
|
||||
rand = "0.8"
|
||||
rmp-serde = "1"
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_bytes = "0.11"
|
||||
serde_path_to_error = "0.1"
|
||||
siphasher = "1"
|
||||
sqlx = { version = "0.7", features = ["runtime-tokio", "sqlite", "chrono"] }
|
||||
tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
|
||||
tower = "0.4"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
create table players
|
||||
(
|
||||
name text not null,
|
||||
hash blob not null primary key,
|
||||
world int not null,
|
||||
timestamp timestamp with time zone not null,
|
||||
territory int not null,
|
||||
|
@ -14,9 +14,7 @@ create table players
|
|||
job int not null,
|
||||
free_company text,
|
||||
current_hp int not null,
|
||||
max_hp int not null,
|
||||
|
||||
primary key (name, world)
|
||||
max_hp int not null
|
||||
);
|
||||
|
||||
create index players_territory_idx on players (territory);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
use std::ops::{Deref, DerefMut};
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use anyhow::Result;
|
||||
use axum::{async_trait, BoxError, Router, Server};
|
||||
|
@ -13,8 +14,10 @@ use axum::http::Method;
|
|||
use axum::response::{IntoResponse, Response};
|
||||
use axum::routing::{get, post};
|
||||
use bytes::Bytes;
|
||||
use rand::distributions::{Alphanumeric, DistString};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde::de::DeserializeOwned;
|
||||
use siphasher::sip::SipHasher;
|
||||
use sqlx::SqlitePool;
|
||||
use tower::ServiceBuilder;
|
||||
use tower_http::compression::CompressionLayer;
|
||||
|
@ -23,11 +26,48 @@ use tower_http::decompression::RequestDecompressionLayer;
|
|||
|
||||
static MIGRATOR: sqlx::migrate::Migrator = sqlx::migrate!();
|
||||
|
||||
struct AppState {
|
||||
salt: String,
|
||||
pool: Arc<SqlitePool>,
|
||||
hasher: SipHasher,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<()> {
|
||||
let pool = SqlitePool::connect("./database.sqlite").await?;
|
||||
MIGRATOR.run(&pool).await?;
|
||||
|
||||
let pool = Arc::new(pool);
|
||||
|
||||
{
|
||||
let pool = Arc::clone(&pool);
|
||||
tokio::task::spawn(async move {
|
||||
let mut interval = tokio::time::interval(Duration::from_secs(60 * 30));
|
||||
loop {
|
||||
interval.tick().await;
|
||||
|
||||
println!("deleting old records");
|
||||
let result = sqlx::query!(
|
||||
// language=sqlite
|
||||
"delete from players where unixepoch('now') - unixepoch(timestamp) > 3600",
|
||||
)
|
||||
.execute(&*pool)
|
||||
.await;
|
||||
|
||||
match result {
|
||||
Ok(res) => println!("{} record(s) deleted", res.rows_affected()),
|
||||
Err(e) => eprintln!("could not delete old records: {e:#}"),
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let state = Arc::new(AppState {
|
||||
pool,
|
||||
salt: Alphanumeric.sample_string(&mut rand::thread_rng(), 8),
|
||||
hasher: SipHasher::new(),
|
||||
});
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin([
|
||||
|
@ -42,7 +82,7 @@ async fn main() -> Result<()> {
|
|||
.route("/:territory", get(territory))
|
||||
.route("/:territory/:world", get(territory_world))
|
||||
.route("/upload", post(upload))
|
||||
.with_state(Arc::new(pool))
|
||||
.with_state(state)
|
||||
.layer(cors)
|
||||
.layer(CompressionLayer::new())
|
||||
.layer(
|
||||
|
@ -60,15 +100,16 @@ async fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
async fn territory(
|
||||
State(pool): State<Arc<SqlitePool>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path(territory): Path<u32>,
|
||||
) -> Result<MsgPack<Vec<AnonymousPlayerInfo>>, AppError>
|
||||
{
|
||||
let info = sqlx::query_as!(
|
||||
AnonymousPlayerInfo,
|
||||
AnonymousPlayerInfoInternal,
|
||||
// language=sqlite
|
||||
r#"
|
||||
select world,
|
||||
select hash,
|
||||
world,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
@ -78,29 +119,35 @@ async fn territory(
|
|||
job,
|
||||
free_company,
|
||||
current_hp,
|
||||
max_hp
|
||||
max_hp,
|
||||
coalesce(unixepoch('now') - unixepoch(timestamp), 30) as age
|
||||
from players
|
||||
where territory = ?
|
||||
and unixepoch('now') - unixepoch(timestamp) < 30
|
||||
and age < 30
|
||||
"#,
|
||||
territory,
|
||||
)
|
||||
.fetch_all(&*pool)
|
||||
.fetch_all(&*state.pool)
|
||||
.await?;
|
||||
|
||||
let info = info.into_iter()
|
||||
.map(|player| AnonymousPlayerInfo::new_from(player, &state.hasher, &state.salt, territory))
|
||||
.collect();
|
||||
|
||||
Ok(MsgPack(info))
|
||||
}
|
||||
|
||||
async fn territory_world(
|
||||
State(pool): State<Arc<SqlitePool>>,
|
||||
State(state): State<Arc<AppState>>,
|
||||
Path((territory, world)): Path<(u32, u32)>,
|
||||
) -> Result<MsgPack<Vec<AnonymousPlayerInfo>>, AppError>
|
||||
{
|
||||
let info = sqlx::query_as!(
|
||||
AnonymousPlayerInfo,
|
||||
AnonymousPlayerInfoInternal,
|
||||
// language=sqlite
|
||||
r#"
|
||||
select world,
|
||||
select hash,
|
||||
world,
|
||||
x,
|
||||
y,
|
||||
z,
|
||||
|
@ -110,34 +157,37 @@ async fn territory_world(
|
|||
job,
|
||||
free_company,
|
||||
current_hp,
|
||||
max_hp
|
||||
max_hp,
|
||||
coalesce(unixepoch('now') - unixepoch(timestamp), 30) as age
|
||||
from players
|
||||
where territory = ?
|
||||
and current_world = ?
|
||||
and unixepoch('now') - unixepoch(timestamp) < 30
|
||||
and age < 30
|
||||
"#,
|
||||
territory,
|
||||
world,
|
||||
)
|
||||
.fetch_all(&*pool)
|
||||
.fetch_all(&*state.pool)
|
||||
.await?;
|
||||
|
||||
let info = info.into_iter()
|
||||
.map(|player| AnonymousPlayerInfo::new_from(player, &state.hasher, &state.salt, territory))
|
||||
.collect();
|
||||
|
||||
Ok(MsgPack(info))
|
||||
}
|
||||
|
||||
async fn upload(
|
||||
pool: State<Arc<SqlitePool>>,
|
||||
state: State<Arc<AppState>>,
|
||||
data: MsgPack<Update>,
|
||||
) -> Result<(), AppError> {
|
||||
let mut t = pool.begin().await?;
|
||||
let mut t = state.pool.begin().await?;
|
||||
|
||||
for player in &data.players {
|
||||
if !player.is_sane() {
|
||||
continue;
|
||||
}
|
||||
|
||||
let name = player.name.trim();
|
||||
|
||||
let fc = match &player.free_company {
|
||||
Some(x) if x.trim() == "" => None,
|
||||
Some(x) => Some(x),
|
||||
|
@ -147,10 +197,10 @@ async fn upload(
|
|||
sqlx::query!(
|
||||
// language=sqlite
|
||||
"
|
||||
insert into players (name, world, timestamp, territory, current_world, x, y, z, w, customize, level, job, free_company, current_hp,
|
||||
insert into players (hash, world, timestamp, territory, current_world, 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,
|
||||
on conflict (hash) do update set timestamp = current_timestamp,
|
||||
territory = ?,
|
||||
current_world = ?,
|
||||
x = ?,
|
||||
|
@ -164,7 +214,7 @@ async fn upload(
|
|||
current_hp = ?,
|
||||
max_hp = ?
|
||||
",
|
||||
name,
|
||||
player.hash,
|
||||
player.world,
|
||||
|
||||
data.territory,
|
||||
|
@ -210,12 +260,14 @@ struct Update {
|
|||
|
||||
#[derive(Deserialize)]
|
||||
struct PlayerInfo {
|
||||
name: String,
|
||||
#[serde(with = "serde_bytes")]
|
||||
hash: Vec<u8>,
|
||||
world: u32,
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
w: f64,
|
||||
#[serde(with = "serde_bytes")]
|
||||
customize: Vec<u8>,
|
||||
level: u8,
|
||||
job: u32,
|
||||
|
@ -226,32 +278,78 @@ struct PlayerInfo {
|
|||
|
||||
impl PlayerInfo {
|
||||
fn is_sane(&self) -> bool {
|
||||
if self.name.trim().is_empty() {
|
||||
return false;
|
||||
}
|
||||
|
||||
if self.world == 65535 || self.level == 0 {
|
||||
return false;
|
||||
}
|
||||
|
||||
true
|
||||
!self.hash.is_empty()
|
||||
&& self.world != 65535
|
||||
&& self.level != 0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AnonymousPlayerInfo {
|
||||
world: i64,
|
||||
// timestamp: DateTime<Utc>,
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
w: f64,
|
||||
#[serde(with = "serde_bytes")]
|
||||
customize: Vec<u8>,
|
||||
level: i64,
|
||||
job: i64,
|
||||
free_company: Option<String>,
|
||||
current_hp: i64,
|
||||
max_hp: i64,
|
||||
age: i64,
|
||||
territory_unique_id: u64,
|
||||
}
|
||||
|
||||
impl AnonymousPlayerInfo {
|
||||
fn new_from(value: AnonymousPlayerInfoInternal, hasher: &SipHasher, salt: &str, territory: u32) -> Self {
|
||||
Self {
|
||||
territory_unique_id: value.gen_hash(hasher, salt, territory),
|
||||
world: value.world,
|
||||
x: value.x,
|
||||
y: value.y,
|
||||
z: value.z,
|
||||
w: value.w,
|
||||
customize: value.customize,
|
||||
level: value.level,
|
||||
job: value.job,
|
||||
free_company: value.free_company,
|
||||
current_hp: value.current_hp,
|
||||
max_hp: value.max_hp,
|
||||
age: value.age,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize)]
|
||||
struct AnonymousPlayerInfoInternal {
|
||||
#[serde(skip)]
|
||||
hash: Vec<u8>,
|
||||
world: i64,
|
||||
x: f64,
|
||||
y: f64,
|
||||
z: f64,
|
||||
w: f64,
|
||||
#[serde(with = "serde_bytes")]
|
||||
customize: Vec<u8>,
|
||||
level: i64,
|
||||
job: i64,
|
||||
free_company: Option<String>,
|
||||
current_hp: i64,
|
||||
max_hp: i64,
|
||||
age: i64,
|
||||
}
|
||||
|
||||
impl AnonymousPlayerInfoInternal {
|
||||
pub fn gen_hash(&self, hasher: &SipHasher, salt: &str, territory: u32) -> u64 {
|
||||
hasher.hash(format!(
|
||||
"{}-{}-{}",
|
||||
salt,
|
||||
territory,
|
||||
data_encoding::HEXLOWER.encode(&self.hash),
|
||||
).as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
// Make our own error that wraps `anyhow::Error`.
|
||||
|
|
Loading…
Reference in New Issue