This commit is contained in:
Anna 2022-09-05 04:02:34 -04:00
parent 7b512e78d5
commit 305b2bed4a
18 changed files with 247 additions and 26 deletions

View File

@ -1,4 +1,6 @@
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
namespace OrangeGuidanceTomestone; namespace OrangeGuidanceTomestone;
@ -19,16 +21,47 @@ internal class Commands : IDisposable {
private void OnCommand(string command, string arguments) { private void OnCommand(string command, string arguments) {
switch (arguments) { switch (arguments) {
case "ban": case "ban": {
var name = this.Plugin.DataManager.GetExcelSheet<TerritoryType>()?.GetRow(this.Plugin.ClientState.TerritoryType)
?.PlaceName
.Value
?.Name
?.ToDalamudString()
.TextValue;
if (this.Plugin.Config.BannedTerritories.Contains(this.Plugin.ClientState.TerritoryType)) {
this.Plugin.ChatGui.Print($"{name} is already on the ban list.");
return;
}
this.Plugin.Config.BannedTerritories.Add(this.Plugin.ClientState.TerritoryType); this.Plugin.Config.BannedTerritories.Add(this.Plugin.ClientState.TerritoryType);
this.Plugin.SaveConfig(); this.Plugin.SaveConfig();
this.Plugin.ChatGui.Print($"Added {name} to the ban list.");
this.Plugin.Messages.RemoveVfx(); this.Plugin.Messages.RemoveVfx();
this.Plugin.Messages.Clear();
break; break;
case "unban": }
case "unban": {
var name = this.Plugin.DataManager.GetExcelSheet<TerritoryType>()?.GetRow(this.Plugin.ClientState.TerritoryType)
?.PlaceName
.Value
?.Name
?.ToDalamudString()
.TextValue;
if (!this.Plugin.Config.BannedTerritories.Contains(this.Plugin.ClientState.TerritoryType)) {
this.Plugin.ChatGui.Print($"{name} is not on the ban list.");
return;
}
this.Plugin.Config.BannedTerritories.Remove(this.Plugin.ClientState.TerritoryType); this.Plugin.Config.BannedTerritories.Remove(this.Plugin.ClientState.TerritoryType);
this.Plugin.SaveConfig(); this.Plugin.SaveConfig();
this.Plugin.ChatGui.Print($"Removed {name} from the ban list.");
this.Plugin.Messages.SpawnVfx(); this.Plugin.Messages.SpawnVfx();
break; break;
}
case "refresh": case "refresh":
this.Plugin.Messages.SpawnVfx(); this.Plugin.Messages.SpawnVfx();
break; break;

View File

@ -8,4 +8,7 @@ public class Configuration : IPluginConfiguration {
public string ApiKey { get; set; } = string.Empty; public string ApiKey { get; set; } = string.Empty;
public HashSet<uint> BannedTerritories { get; set; } = new(); public HashSet<uint> BannedTerritories { get; set; } = new();
public bool DisableTrials = true;
public bool DisableDeepDungeon = true;
public bool RemoveGlow = true;
} }

View File

@ -1,6 +1,7 @@
using System.Numerics; using System.Numerics;
using Dalamud.Game; using Dalamud.Game;
using Dalamud.Logging; using Dalamud.Logging;
using Lumina.Excel.GeneratedSheets;
using Newtonsoft.Json; using Newtonsoft.Json;
using OrangeGuidanceTomestone.Helpers; using OrangeGuidanceTomestone.Helpers;
@ -15,9 +16,22 @@ internal class Messages : IDisposable {
private Dictionary<Guid, Message> Current { get; } = new(); private Dictionary<Guid, Message> Current { get; } = new();
private Queue<Message> SpawnQueue { get; } = new(); private Queue<Message> SpawnQueue { get; } = new();
private HashSet<uint> Trials { get; } = new();
private HashSet<uint> DeepDungeons { get; } = new();
internal Messages(Plugin plugin) { internal Messages(Plugin plugin) {
this.Plugin = plugin; this.Plugin = plugin;
foreach (var cfc in this.Plugin.DataManager.GetExcelSheet<ContentFinderCondition>()!) {
if (cfc.ContentType.Row == 4) {
this.Trials.Add(cfc.TerritoryType.Row);
}
if (cfc.ContentType.Row == 21) {
this.DeepDungeons.Add(cfc.TerritoryType.Row);
}
}
if (this.Plugin.Config.ApiKey != string.Empty) { if (this.Plugin.Config.ApiKey != string.Empty) {
this.SpawnVfx(); this.SpawnVfx();
} }
@ -34,7 +48,7 @@ internal class Messages : IDisposable {
this.Plugin.ClientState.Login -= this.SpawnVfx; this.Plugin.ClientState.Login -= this.SpawnVfx;
this.Plugin.Framework.Update -= this.HandleSpawnQueue; this.Plugin.Framework.Update -= this.HandleSpawnQueue;
this.RemoveVfx(null, null); this.RemoveVfx();
} }
private unsafe void HandleSpawnQueue(Framework framework) { private unsafe void HandleSpawnQueue(Framework framework) {
@ -42,10 +56,10 @@ internal class Messages : IDisposable {
return; return;
} }
PluginLog.Log($"spawning vfx for {message.Id}"); PluginLog.Debug($"spawning vfx for {message.Id}");
var rotation = Quaternion.CreateFromYawPitchRoll(message.Yaw, 0, 0); var rotation = Quaternion.CreateFromYawPitchRoll(message.Yaw, 0, 0);
if (this.Plugin.Vfx.SpawnStatic(message.Id, VfxPath, message.Position, rotation) == null) { if (this.Plugin.Vfx.SpawnStatic(message.Id, VfxPath, message.Position, rotation) == null) {
PluginLog.Log("trying again"); PluginLog.Debug("trying again");
this.SpawnQueue.Enqueue(message); this.SpawnQueue.Enqueue(message);
} }
} }
@ -64,6 +78,14 @@ internal class Messages : IDisposable {
return; return;
} }
if (this.Plugin.Config.DisableTrials && this.Trials.Contains(territory)) {
return;
}
if (this.Plugin.Config.DisableDeepDungeon && this.DeepDungeons.Contains(territory)) {
return;
}
this.RemoveVfx(null, null); this.RemoveVfx(null, null);
Task.Run(async () => { Task.Run(async () => {
@ -95,6 +117,12 @@ internal class Messages : IDisposable {
this.Plugin.Vfx.RemoveAll(); this.Plugin.Vfx.RemoveAll();
} }
internal void Clear() {
this.CurrentMutex.Wait();
this.Current.Clear();
this.CurrentMutex.Release();
}
internal IEnumerable<Message> Nearby() { internal IEnumerable<Message> Nearby() {
if (this.Plugin.ClientState.LocalPlayer is not { } player) { if (this.Plugin.ClientState.LocalPlayer is not { } player) {
return Array.Empty<Message>(); return Array.Empty<Message>();

View File

@ -58,15 +58,15 @@
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.8"/> <PackageReference Include="DalamudPackager" Version="2.1.8" />
<PackageReference Include="Fody" Version="6.6.3" PrivateAssets="all"/> <PackageReference Include="Fody" Version="6.6.3" PrivateAssets="all" />
<PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all"/> <PackageReference Include="Resourcer.Fody" Version="1.8.0" PrivateAssets="all" />
<PackageReference Include="YamlDotNet" Version="12.0.0"/> <PackageReference Include="YamlDotNet" Version="12.0.0" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<EmbeddedResource Include="../server/packs/*.yaml" LinkBase="packs"/> <EmbeddedResource Include="../server/packs/*.yaml" LinkBase="packs" />
<EmbeddedResource Remove="../server/packs/*_old*.yaml"/> <EmbeddedResource Remove="../server/packs/*_old*.yaml" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View File

@ -3,6 +3,7 @@ using Dalamud.Game;
using Dalamud.Game.ClientState; using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.Command; using Dalamud.Game.Command;
using Dalamud.Game.Gui;
using Dalamud.IoC; using Dalamud.IoC;
using Dalamud.Plugin; using Dalamud.Plugin;
@ -14,6 +15,9 @@ public class Plugin : IDalamudPlugin {
[PluginService] [PluginService]
internal DalamudPluginInterface Interface { get; init; } internal DalamudPluginInterface Interface { get; init; }
[PluginService]
internal ChatGui ChatGui { get; init; }
[PluginService] [PluginService]
internal ClientState ClientState { get; init; } internal ClientState ClientState { get; init; }

View File

@ -15,6 +15,7 @@ internal class MainWindow {
this.Tabs = new List<ITab> { this.Tabs = new List<ITab> {
new Write(this.Plugin), new Write(this.Plugin),
new MessageList(this.Plugin), new MessageList(this.Plugin),
new Settings(this.Plugin),
}; };
} }

View File

@ -0,0 +1,31 @@
using ImGuiNET;
namespace OrangeGuidanceTomestone.Ui.MainWindowTabs;
internal class Settings : ITab {
public string Name => "Settings";
private Plugin Plugin { get; }
internal Settings(Plugin plugin) {
this.Plugin = plugin;
}
public void Draw() {
var anyChanged = false;
var vfx = false;
anyChanged |= vfx |= ImGui.Checkbox("Disable in trials", ref this.Plugin.Config.DisableTrials);
anyChanged |= vfx |= ImGui.Checkbox("Disable in Deep Dungeons", ref this.Plugin.Config.DisableDeepDungeon);
anyChanged |= vfx |= ImGui.Checkbox("Remove glow effect from signs", ref this.Plugin.Config.RemoveGlow);
if (anyChanged) {
this.Plugin.SaveConfig();
}
if (vfx) {
this.Plugin.Messages.RemoveVfx();
this.Plugin.Messages.Clear();
}
}
}

1
server/Cargo.lock generated
View File

@ -1035,6 +1035,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"base64", "base64",
"bytes",
"chrono", "chrono",
"rand", "rand",
"serde", "serde",

View File

@ -8,6 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1" anyhow = "1"
base64 = "0.13" base64 = "0.13"
bytes = "1"
chrono = "0.4" chrono = "0.4"
rand = "0.8" rand = "0.8"
serde = { version = "1", features = ["derive"] } serde = { version = "1", features = ["derive"] }

View File

@ -0,0 +1,4 @@
create table extra_tokens (
id text not null primary key,
extra integer not null
);

View File

@ -288,6 +288,7 @@ words:
- core - core
- fingers - fingers
- horns - horns
- feet
- name: Affinities - name: Affinities
words: words:

View File

@ -48,4 +48,6 @@ pub struct RetrievedMessageTerritory {
pub positive_votes: i32, pub positive_votes: i32,
pub negative_votes: i32, pub negative_votes: i32,
pub user_vote: i64, pub user_vote: i64,
#[serde(skip)]
pub created: NaiveDateTime,
} }

View File

@ -16,6 +16,7 @@ mod get_location;
mod vote; mod vote;
mod get_mine; mod get_mine;
mod get_message; mod get_message;
mod claim;
pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> { pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
register::register(Arc::clone(&state)) register::register(Arc::clone(&state))
@ -26,6 +27,7 @@ pub fn routes(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.or(get_message::get_message(Arc::clone(&state))) .or(get_message::get_message(Arc::clone(&state)))
.or(get_location::get_location(Arc::clone(&state))) .or(get_location::get_location(Arc::clone(&state)))
.or(get_mine::get_mine(Arc::clone(&state))) .or(get_mine::get_mine(Arc::clone(&state)))
.or(claim::claim(Arc::clone(&state)))
.recover(handle_rejection) .recover(handle_rejection)
.boxed() .boxed()
} }
@ -62,6 +64,7 @@ pub enum WebError {
InvalidIndex, InvalidIndex,
TooManyMessages, TooManyMessages,
NoSuchMessage, NoSuchMessage,
InvalidExtraCode,
} }
impl Reject for WebError {} impl Reject for WebError {}
@ -72,21 +75,41 @@ pub struct AnyhowRejection(anyhow::Error);
impl Reject for AnyhowRejection {} impl Reject for AnyhowRejection {}
async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> { async fn handle_rejection(err: Rejection) -> Result<impl Reply, Infallible> {
let status = if let Some(AnyhowRejection(e)) = err.find::<AnyhowRejection>() { let (status, name, desc) = if let Some(AnyhowRejection(e)) = err.find::<AnyhowRejection>() {
eprintln!("{:#?}", e); eprintln!("{:#?}", e);
StatusCode::INTERNAL_SERVER_ERROR (
StatusCode::INTERNAL_SERVER_ERROR,
"internal_error",
"an internal logic error occured",
)
} else if let Some(e) = err.find::<WebError>() { } else if let Some(e) = err.find::<WebError>() {
match e { match e {
WebError::InvalidAuthToken => StatusCode::BAD_REQUEST, WebError::InvalidAuthToken => (StatusCode::BAD_REQUEST, "invalid_auth_token", "the auth token was not valid"),
WebError::InvalidPackId => StatusCode::NOT_FOUND, WebError::InvalidPackId => (StatusCode::NOT_FOUND, "invalid_pack_id", "the server does not have a pack registered with that id"),
WebError::InvalidIndex => StatusCode::NOT_FOUND, WebError::InvalidIndex => (StatusCode::NOT_FOUND, "invalid_index", "one of the provided indices was out of range"),
WebError::TooManyMessages => StatusCode::BAD_REQUEST, WebError::TooManyMessages => (StatusCode::BAD_REQUEST, "too_many_messages", "you have run out of messages - delete one and try again"),
WebError::NoSuchMessage => StatusCode::NOT_FOUND, WebError::NoSuchMessage => (StatusCode::NOT_FOUND, "no_such_message", "no message with that id was found"),
WebError::InvalidExtraCode => (StatusCode::BAD_REQUEST, "invalid_extra_code", "that extra code was not found"),
} }
} else { } else {
eprintln!("{:#?}", err); eprintln!("{:#?}", err);
StatusCode::INTERNAL_SERVER_ERROR (
StatusCode::INTERNAL_SERVER_ERROR,
"internal_error",
"an unhandled error was encountered",
)
}; };
Ok(warp::reply::with_status(warp::reply(), status)) #[derive(serde::Serialize)]
struct ErrorMessage {
code: &'static str,
message: &'static str,
}
let message = ErrorMessage {
code: name,
message: desc,
};
Ok(warp::reply::with_status(warp::reply::json(&message), status))
} }

59
server/src/web/claim.rs Normal file
View File

@ -0,0 +1,59 @@
use std::sync::Arc;
use anyhow::Context;
use bytes::Bytes;
use uuid::Uuid;
use warp::{Filter, Rejection, Reply};
use warp::filters::BoxedFilter;
use crate::message::RetrievedMessageTerritory;
use crate::State;
use crate::web::{AnyhowRejection, WebError};
pub fn claim(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
warp::post()
.and(warp::path("claim"))
.and(warp::path::end())
.and(super::get_id(Arc::clone(&state)))
.and(warp::body::content_length_limit(256))
.and(warp::body::bytes())
.and_then(move |(id, _), code: Bytes| logic(Arc::clone(&state), id, code))
.boxed()
}
async fn logic(state: Arc<State>, id: i64, bytes: Bytes) -> Result<impl Reply, Rejection> {
let bytes: Vec<u8> = bytes.into_iter().collect();
let code = String::from_utf8(bytes)
.context("invalid utf8 for extra code")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
let code = sqlx::query!(
// language=sqlite
r#"delete from extra_tokens where id = ? returning extra as "extra!: i64""#,
code,
)
.fetch_optional(&state.db)
.await
.context("could not get code from database")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
if let Some(code) = code {
sqlx::query!(
// language=sqlite
"update users set extra = ? where id = ?",
code.extra,
id,
)
.execute(&state.db)
.await
.context("could not update user")
.map_err(AnyhowRejection)
.map_err(warp::reject::custom)?;
return Ok(code.extra.to_string());
}
Err(warp::reject::custom(WebError::InvalidExtraCode))
}

View File

@ -34,7 +34,8 @@ async fn logic(state: Arc<State>, id: i64, message_id: Uuid) -> Result<impl Repl
m.message, m.message,
coalesce(sum(v.vote between 0 and 1), 0) as positive_votes, coalesce(sum(v.vote between 0 and 1), 0) as positive_votes,
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes, coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
v2.vote as user_vote v2.vote as user_vote,
m.created
from messages m from messages m
left join votes v on m.id = v.message left join votes v on m.id = v.message
left join votes v2 on m.id = v2.message and v2.user = ? left join votes v2 on m.id = v2.message and v2.user = ?

View File

@ -1,3 +1,4 @@
use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;
use anyhow::Context; use anyhow::Context;
@ -13,12 +14,18 @@ pub fn get_mine(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("messages")) .and(warp::path("messages"))
.and(warp::path::end()) .and(warp::path::end())
.and(super::get_id(Arc::clone(&state))) .and(super::get_id(Arc::clone(&state)))
.and_then(move |(id, _)| logic(Arc::clone(&state), id)) .and(warp::query())
.and_then(move |(id, extra), query: HashMap<String, String>| logic(Arc::clone(&state), id, extra, query))
.boxed() .boxed()
} }
async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> { async fn logic(state: Arc<State>, id: i64, extra: i64, mut query: HashMap<String, String>) -> Result<impl Reply, Rejection> {
let messages = sqlx::query_as!( let version = query.remove("v")
.unwrap_or_else(|| "1".to_string())
.parse::<u8>()
.unwrap_or(1);
let mut messages = sqlx::query_as!(
RetrievedMessageTerritory, RetrievedMessageTerritory,
// language=sqlite // language=sqlite
r#" r#"
@ -31,7 +38,8 @@ async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> {
m.message, m.message,
coalesce(sum(v.vote between 0 and 1), 0) as positive_votes, coalesce(sum(v.vote between 0 and 1), 0) as positive_votes,
coalesce(sum(v.vote between -1 and 0), 0) as negative_votes, coalesce(sum(v.vote between -1 and 0), 0) as negative_votes,
v2.vote as user_vote v2.vote as user_vote,
m.created
from messages m from messages m
left join votes v on m.id = v.message left join votes v on m.id = v.message
left join votes v2 on m.id = v2.message and v2.user = ? left join votes v2 on m.id = v2.message and v2.user = ?
@ -45,5 +53,24 @@ async fn logic(state: Arc<State>, id: i64) -> Result<impl Reply, Rejection> {
.context("could not get messages from database") .context("could not get messages from database")
.map_err(AnyhowRejection) .map_err(AnyhowRejection)
.map_err(warp::reject::custom)?; .map_err(warp::reject::custom)?;
Ok(warp::reply::json(&messages))
messages.sort_by_key(|msg| msg.created);
messages.reverse();
if version == 1 {
return Ok(warp::reply::json(&messages));
}
#[derive(serde::Serialize)]
struct Mine {
messages: Vec<RetrievedMessageTerritory>,
extra: i64,
}
let mine = Mine {
messages,
extra,
};
Ok(warp::reply::json(&mine))
} }

View File

@ -15,6 +15,7 @@ pub fn vote(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("votes")) .and(warp::path("votes"))
.and(warp::path::end()) .and(warp::path::end())
.and(super::get_id(Arc::clone(&state))) .and(super::get_id(Arc::clone(&state)))
.and(warp::body::content_length_limit(3))
.and(warp::body::json()) .and(warp::body::json())
.and_then(move |message_id: Uuid, (id, _), vote: i8| logic(Arc::clone(&state), id, message_id, vote)) .and_then(move |message_id: Uuid, (id, _), vote: i8| logic(Arc::clone(&state), id, message_id, vote))
.boxed() .boxed()

View File

@ -14,6 +14,7 @@ pub fn write(state: Arc<State>) -> BoxedFilter<(impl Reply, )> {
.and(warp::path("messages")) .and(warp::path("messages"))
.and(warp::path::end()) .and(warp::path::end())
.and(super::get_id(Arc::clone(&state))) .and(super::get_id(Arc::clone(&state)))
.and(warp::body::content_length_limit(1024))
.and(warp::body::json()) .and(warp::body::json())
.and_then(move |(id, extra), message: Message| logic(Arc::clone(&state), id, extra, message)) .and_then(move |(id, extra), message: Message| logic(Arc::clone(&state), id, extra, message))
.boxed() .boxed()