support fisher's intuition
This commit is contained in:
parent
b966d78be3
commit
a99ee46d26
7 changed files with 194 additions and 98 deletions
|
@ -10,7 +10,7 @@ The data for fish is based on the data used in [Carbuncle Plushy's Fish Tracker]
|
||||||
|
|
||||||
Current major missing features include:
|
Current major missing features include:
|
||||||
|
|
||||||
- [ ] Fisher's Intution (catching other fish to spawn a big fish)
|
- [x] Fisher's Intuition (catching other fish to spawn a big fish)
|
||||||
- [ ] Spearfishing
|
- [ ] Spearfishing
|
||||||
- [x] Better condition display, show the weather, etc
|
- [x] Better condition display, show the weather, etc
|
||||||
- [ ] Folklore requirement display
|
- [ ] Folklore requirement display
|
||||||
|
|
|
@ -6,6 +6,13 @@ pub fn changelog_page() -> Markup {
|
||||||
layout(html! {
|
layout(html! {
|
||||||
h1 { "Beacon Changelog" }
|
h1 { "Beacon Changelog" }
|
||||||
|
|
||||||
|
section {
|
||||||
|
h2 { "1.4.0" }
|
||||||
|
ul {
|
||||||
|
li { "Implemented support for Fisher's Intuition" }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
section {
|
section {
|
||||||
h2 { "1.3.0, 11.02.2025" }
|
h2 { "1.3.0, 11.02.2025" }
|
||||||
ul {
|
ul {
|
||||||
|
|
17
src/data.rs
17
src/data.rs
|
@ -189,6 +189,7 @@ pub struct CombinedFish<'a> {
|
||||||
pub is_always_up: bool,
|
pub is_always_up: bool,
|
||||||
pub windows: Vec<Window>,
|
pub windows: Vec<Window>,
|
||||||
pub rarity: f32,
|
pub rarity: f32,
|
||||||
|
pub filtered: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
|
@ -490,6 +491,7 @@ impl Data {
|
||||||
let mut cfish = CombinedFish {
|
let mut cfish = CombinedFish {
|
||||||
entry: v,
|
entry: v,
|
||||||
meta: m,
|
meta: m,
|
||||||
|
filtered: false,
|
||||||
is_up: false, // fake default values for now
|
is_up: false, // fake default values for now
|
||||||
is_always_up: false, // dito
|
is_always_up: false, // dito
|
||||||
windows: Vec::new(), // dito
|
windows: Vec::new(), // dito
|
||||||
|
@ -528,9 +530,10 @@ impl Filters {
|
||||||
&'a self,
|
&'a self,
|
||||||
fish: Vec<&'a CombinedFish>,
|
fish: Vec<&'a CombinedFish>,
|
||||||
caught_fish_ids: &[i32],
|
caught_fish_ids: &[i32],
|
||||||
) -> Vec<&'a CombinedFish> {
|
) -> Vec<CombinedFish> {
|
||||||
fish.into_iter()
|
fish.into_iter()
|
||||||
.filter(|fish| {
|
.cloned()
|
||||||
|
.map(|fish| {
|
||||||
let f_caught = if self.include_caught {
|
let f_caught = if self.include_caught {
|
||||||
true
|
true
|
||||||
} else {
|
} else {
|
||||||
|
@ -549,7 +552,10 @@ impl Filters {
|
||||||
true
|
true
|
||||||
};
|
};
|
||||||
|
|
||||||
f_caught && f_patch && f_big
|
CombinedFish {
|
||||||
|
filtered: !(f_caught && f_patch && f_big),
|
||||||
|
..fish
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
@ -566,3 +572,8 @@ pub fn get_weather_icon(data: &Data, id: &u32) -> String {
|
||||||
pub fn get_zone_name(data: &Data, id: u32) -> &str {
|
pub fn get_zone_name(data: &Data, id: u32) -> &str {
|
||||||
&data.db_data.zones.get(&id).unwrap().name_en
|
&data.db_data.zones.get(&id).unwrap().name_en
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn display_intuition_length(length: u32) -> String {
|
||||||
|
let dur = Duration::seconds(length as i64);
|
||||||
|
format!("{}m{}s", dur.num_minutes(), dur.num_seconds() % 60)
|
||||||
|
}
|
||||||
|
|
|
@ -85,7 +85,7 @@ async fn main_handler(
|
||||||
|
|
||||||
// Extract the list of fish we want to render.
|
// Extract the list of fish we want to render.
|
||||||
let meta = state.data.fish_with_meta();
|
let meta = state.data.fish_with_meta();
|
||||||
let mut values: Vec<&CombinedFish> = filters.filter(meta.values().collect(), &caught_fish);
|
let mut values: Vec<CombinedFish> = filters.filter(meta.values().collect(), &caught_fish);
|
||||||
values.sort_by(|afish, bfish| {
|
values.sort_by(|afish, bfish| {
|
||||||
pinned_fish
|
pinned_fish
|
||||||
.contains(&(bfish.entry.id as i32))
|
.contains(&(bfish.entry.id as i32))
|
||||||
|
|
215
src/templates.rs
215
src/templates.rs
|
@ -11,7 +11,7 @@ use crate::{
|
||||||
|
|
||||||
pub struct ViewData<'a> {
|
pub struct ViewData<'a> {
|
||||||
pub state: State<Arc<AppState>>,
|
pub state: State<Arc<AppState>>,
|
||||||
pub fish: Vec<&'a CombinedFish<'a>>,
|
pub fish: Vec<CombinedFish<'a>>,
|
||||||
pub caught_fish: Vec<i32>,
|
pub caught_fish: Vec<i32>,
|
||||||
pub pinned_fish: Vec<i32>,
|
pub pinned_fish: Vec<i32>,
|
||||||
pub acc_id: String,
|
pub acc_id: String,
|
||||||
|
@ -75,129 +75,158 @@ pub fn main_page(data: ViewData) -> Markup {
|
||||||
pub fn fish_list(data: &ViewData) -> Markup {
|
pub fn fish_list(data: &ViewData) -> Markup {
|
||||||
html! {
|
html! {
|
||||||
@for fish in data.fish.clone() {
|
@for fish in data.fish.clone() {
|
||||||
section.fish.up[fish.is_up].alwaysup[fish.is_always_up].pinned[data.pinned_fish.contains(&(fish.entry.id as i32))] {
|
@if !fish.filtered {
|
||||||
.title {
|
(fish_display(&fish, data))
|
||||||
div {
|
@if !fish.entry.predators.is_empty() {
|
||||||
@if !data.caught_fish.contains(&(fish.entry.id as i32)) {
|
.predators {
|
||||||
form action=(format!("/{}/catch/{}", data.acc_id, fish.entry.id)) method="post" {
|
div.predators-header { small { "⇖ Requirements" } }
|
||||||
button.catch-button type="submit" { (PreEscaped("✓")) }
|
@for predator in &fish.entry.predators {
|
||||||
|
@if let Some(pred_fish) = data.fish.iter().find(|f| f.entry.id == predator[0]) {
|
||||||
|
.predators-fish {
|
||||||
|
.amount { (predator[1]) }
|
||||||
|
(fish_display(pred_fish, data))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@if !data.pinned_fish.contains(&(fish.entry.id as i32)) {
|
|
||||||
form action=(format!("/{}/pin/{}", data.acc_id, fish.entry.id)) method="post" {
|
|
||||||
button.pin-button type="submit" { (PreEscaped("☆"))}
|
|
||||||
}
|
|
||||||
} @else {
|
|
||||||
form action=(format!("/{}/pin/{}/delete", data.acc_id, fish.entry.id)) method="post" {
|
|
||||||
button.pin-button type="submit" { (PreEscaped("★"))}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
div {
|
|
||||||
h3 {
|
|
||||||
(fish.meta.name_en)
|
|
||||||
span class=(format!("patch patch-{}", fish.entry.patch as u32)) { (fish.entry.patch) }
|
|
||||||
}
|
|
||||||
.subtitle {
|
|
||||||
span { "Rarity: " (format!("{:.2}", fish.rarity * 100.)) "%" }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.when {
|
}
|
||||||
@if let Some(window) = fish.windows.first() {
|
}
|
||||||
@if fish.is_up {
|
}
|
||||||
"closes " (window.display_end_time())
|
}
|
||||||
} @else {
|
|
||||||
"opens " (window.display_start_time())
|
|
||||||
}
|
|
||||||
br;
|
|
||||||
@if fish.is_up {
|
|
||||||
.date data-ts=(clock::to_earth_time(window.start_time + window.duration).timestamp_millis()) {
|
|
||||||
.inner id=(format!("date-{}", fish.entry.id)) hx-preserve {
|
|
||||||
(clock::to_earth_time(window.start_time + window.duration).format("%c %Z"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@if let Some(window2) = fish.windows.get(1) {
|
pub fn fish_display(fish: &CombinedFish, data: &ViewData) -> Markup {
|
||||||
.date.tiny data-ts=(clock::to_earth_time(window2.start_time).timestamp_millis()) {
|
html! {
|
||||||
"next: "
|
section.fish.up[fish.is_up].alwaysup[fish.is_always_up].pinned[data.pinned_fish.contains(&(fish.entry.id as i32))] {
|
||||||
.inner id=(format!("nextwindow-{}", fish.entry.id)) hx-preserve {
|
.title {
|
||||||
(clock::to_earth_time(window2.start_time).format("%c %Z"))
|
div {
|
||||||
}
|
@if !data.caught_fish.contains(&(fish.entry.id as i32)) {
|
||||||
}
|
form action=(format!("/{}/catch/{}", data.acc_id, fish.entry.id)) method="post" {
|
||||||
}
|
button.catch-button type="submit" { (PreEscaped("✓")) }
|
||||||
} @else {
|
|
||||||
.date data-ts=(clock::to_earth_time(window.start_time).timestamp_millis()) {
|
|
||||||
.inner id=(format!("date-{}", fish.entry.id)) hx-preserve {
|
|
||||||
(clock::to_earth_time(window.start_time).format("%c %Z"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@if !data.pinned_fish.contains(&(fish.entry.id as i32)) {
|
||||||
|
form action=(format!("/{}/pin/{}", data.acc_id, fish.entry.id)) method="post" {
|
||||||
|
button.pin-button type="submit" { (PreEscaped("☆"))}
|
||||||
|
}
|
||||||
|
} @else {
|
||||||
|
form action=(format!("/{}/pin/{}/delete", data.acc_id, fish.entry.id)) method="post" {
|
||||||
|
button.pin-button type="submit" { (PreEscaped("★"))}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.how {
|
div.name {
|
||||||
@for item_id in &fish.entry.best_catch_path {
|
h3 {
|
||||||
@if let Some(item) = data.state.data.db_data.items.get(item_id) {
|
(fish.meta.name_en)
|
||||||
span.catchpath title=(item.name_en) {
|
span class=(format!("patch patch-{}", fish.entry.patch as u32)) { (fish.entry.patch) }
|
||||||
img src=(item.get_icon_url()) width="35";
|
|
||||||
|
|
||||||
@if let Some(hookset) = item.get_hookset(&data.state.data) {
|
.subtitle {
|
||||||
img.hookset src=(hookset) width="20";
|
span { "Rarity: " (format!("{:.2}", fish.rarity * 100.)) "%" }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@if let Some(tug) = item.get_tug(&data.state.data) {
|
@if let Some(length) = fish.entry.intuition_length {
|
||||||
span.tug { (tug) }
|
.intuition {
|
||||||
|
img src="/static/intuition.png";
|
||||||
|
div { (data::display_intuition_length(length)) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.when {
|
||||||
|
@if let Some(window) = fish.windows.first() {
|
||||||
|
@if fish.is_up {
|
||||||
|
"closes " (window.display_end_time())
|
||||||
|
} @else {
|
||||||
|
"opens " (window.display_start_time())
|
||||||
|
}
|
||||||
|
br;
|
||||||
|
@if fish.is_up {
|
||||||
|
.date data-ts=(clock::to_earth_time(window.start_time + window.duration).timestamp_millis()) {
|
||||||
|
.inner id=(format!("date-{}", fish.entry.id)) hx-preserve {
|
||||||
|
(clock::to_earth_time(window.start_time + window.duration).format("%c %Z"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if let Some(window2) = fish.windows.get(1) {
|
||||||
|
.date.tiny data-ts=(clock::to_earth_time(window2.start_time).timestamp_millis()) {
|
||||||
|
"next: "
|
||||||
|
.inner id=(format!("nextwindow-{}", fish.entry.id)) hx-preserve {
|
||||||
|
(clock::to_earth_time(window2.start_time).format("%c %Z"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} @else {
|
||||||
|
.date data-ts=(clock::to_earth_time(window.start_time).timestamp_millis()) {
|
||||||
|
.inner id=(format!("date-{}", fish.entry.id)) hx-preserve {
|
||||||
|
(clock::to_earth_time(window.start_time).format("%c %Z"))
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@if let Some(hookset) = fish.entry.hookset {
|
}
|
||||||
span.catchpath {
|
|
||||||
img src=(hookset) width="35";
|
|
||||||
|
|
||||||
@if let Some(tug) = fish.entry.tug {
|
}
|
||||||
|
.how {
|
||||||
|
@for item_id in &fish.entry.best_catch_path {
|
||||||
|
@if let Some(item) = data.state.data.db_data.items.get(item_id) {
|
||||||
|
span.catchpath title=(item.name_en) {
|
||||||
|
img src=(item.get_icon_url()) width="35";
|
||||||
|
|
||||||
|
@if let Some(hookset) = item.get_hookset(&data.state.data) {
|
||||||
|
img.hookset src=(hookset) width="20";
|
||||||
|
}
|
||||||
|
|
||||||
|
@if let Some(tug) = item.get_tug(&data.state.data) {
|
||||||
span.tug { (tug) }
|
span.tug { (tug) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.meta {
|
@if let Some(hookset) = fish.entry.hookset {
|
||||||
@if let Some(location_id) = fish.entry.location {
|
span.catchpath {
|
||||||
@if let Some(location) = data.state.data.db_data.fishing_spots.get(&location_id) {
|
img src=(hookset) width="35";
|
||||||
div {
|
|
||||||
span.zone { (fish.meta.zone_en) }
|
@if let Some(tug) = fish.entry.tug {
|
||||||
(location.name_en)
|
span.tug { (tug) }
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@if fish.entry.start_hour.is_some() && fish.entry.end_hour.is_some() {
|
}
|
||||||
|
}
|
||||||
|
.meta {
|
||||||
|
@if let Some(location_id) = fish.entry.location {
|
||||||
|
@if let Some(location) = data.state.data.db_data.fishing_spots.get(&location_id) {
|
||||||
div {
|
div {
|
||||||
@if !fish.is_always_up {
|
span.zone { (fish.meta.zone_en) }
|
||||||
"ET "
|
(location.name_en)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@if fish.entry.start_hour.is_some() && fish.entry.end_hour.is_some() {
|
||||||
|
div {
|
||||||
|
@if !fish.is_always_up {
|
||||||
|
"ET "
|
||||||
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.start_hour.unwrap())))
|
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.start_hour.unwrap())))
|
||||||
"-"
|
"-"
|
||||||
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.end_hour.unwrap())))
|
(clock::display_eorzea_time(&clock::set_hm_from_float(&clock::get_current_eorzea_date(), fish.entry.end_hour.unwrap())))
|
||||||
} @else {
|
} @else {
|
||||||
"always up!"
|
"always up!"
|
||||||
}
|
|
||||||
}
|
}
|
||||||
div {
|
}
|
||||||
@if !fish.entry.weather_set.is_empty() {
|
div {
|
||||||
@if !fish.entry.previous_weather_set.is_empty() {
|
@if !fish.entry.weather_set.is_empty() {
|
||||||
@for weather in &fish.entry.previous_weather_set {
|
@if !fish.entry.previous_weather_set.is_empty() {
|
||||||
img src=(get_weather_icon(&data.state.data, weather)) width="20" title=(get_weather_name(&data.state.data, weather));
|
@for weather in &fish.entry.previous_weather_set {
|
||||||
}
|
|
||||||
|
|
||||||
" ➞ "
|
|
||||||
}
|
|
||||||
|
|
||||||
@for weather in &fish.entry.weather_set {
|
|
||||||
img src=(get_weather_icon(&data.state.data, weather)) width="20" title=(get_weather_name(&data.state.data, weather));
|
img src=(get_weather_icon(&data.state.data, weather)) width="20" title=(get_weather_name(&data.state.data, weather));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
" ➞ "
|
||||||
|
}
|
||||||
|
|
||||||
|
@for weather in &fish.entry.weather_set {
|
||||||
|
img src=(get_weather_icon(&data.state.data, weather)) width="20" title=(get_weather_name(&data.state.data, weather));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
BIN
static/intuition.png
Normal file
BIN
static/intuition.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 732 B |
|
@ -46,6 +46,7 @@ select {
|
||||||
.title .subtitle {
|
.title .subtitle {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
color: gray;
|
color: gray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -219,3 +220,51 @@ section.pinned {
|
||||||
.pinned *:not(button) {
|
.pinned *:not(button) {
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.pinned + .predators {
|
||||||
|
background: #ffd6cf;
|
||||||
|
}
|
||||||
|
|
||||||
|
.predators-header {
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.predators-fish {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.predators {
|
||||||
|
margin-left: 30px;
|
||||||
|
padding: 0 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.predators-fish section {
|
||||||
|
flex-grow: 1;
|
||||||
|
border: 1px gray dotted;
|
||||||
|
border-radius: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.predators-fish .amount {
|
||||||
|
font-size: 30px;
|
||||||
|
color: gray;
|
||||||
|
}
|
||||||
|
|
||||||
|
.name {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intuition {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.intuition div {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
Loading…
Add table
Reference in a new issue