diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
index 3115f9f..64fb55e 100644
--- a/app/assets/stylesheets/application.css
+++ b/app/assets/stylesheets/application.css
@@ -336,3 +336,58 @@ a:has(button) {
justify-content: space-between;
gap: 5px;
}
+
+.sprite-list {
+ display: flex;
+ align-self: start;
+ flex-direction: column;
+}
+
+.sprite {
+ display: flex;
+ gap: 5px;
+ margin-bottom: 5px;
+ align-items: center;
+}
+
+.sprite-name {
+ background-color: #3D9970;
+ color: #fff;
+ padding: 2px 4px;
+}
+
+.sprite .spawning {
+ color: #3D9970;
+}
+
+.sprite .not-spawning {
+ color: #b5443a;
+}
+
+.sprite-levels {
+ display: grid;
+ grid-template-columns: 1fr 1fr 1fr;
+ gap: 5px;
+}
+
+.sprite-map {
+ margin-bottom: 10px;
+ margin-top: 5px;
+}
+
+.sprite-map summary {
+ font-size: 12px;
+}
+
+.sprite-levels > div {
+ background-color: #111;
+ color: #fff;
+ font-family: monospace;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ text-align: center;
+ text-transform: uppercase;
+ font-weight: bold;
+ padding: 3px 5px;
+}
diff --git a/app/views/instance/_chlog.html.erb b/app/views/instance/_chlog.html.erb
new file mode 100644
index 0000000..54273c8
--- /dev/null
+++ b/app/views/instance/_chlog.html.erb
@@ -0,0 +1,63 @@
+
Challenge log
+
+
+ <% Bestiary.get_sprites_for_zone(instance.zone).each do |sprite| %>
+ <% is_spawning = forecast[0][:curr_weather].in?(sprite[:weather]) %>
+
+
+

+ <%= sprite[:name] %>
+
+ <% if is_spawning %>
+
is spawning!
+ <% else %>
+ <% next_pattern = forecast.find { |f| f[:curr_weather].in?(sprite[:weather]) } %>
+
spawns in
+ <% if next_pattern.nil? %>
+ >300m
+ <% else %>
+ <%= ((next_pattern[:time] - Time.now.utc) / 1.minutes).ceil %>m
+ <% end %>
+
+ <% end %>
+
+ <% if is_spawning %>
+
+ <% sprite[:sprite_levels].each do |lvl| %>
+
+
+
lv<%= lvl[:lv] %>
+
+ <% if lvl[:mutates] %>
+

+ <% elsif lvl[:adapts] %>
+

+ <% end %>
+
+
+ ~<%= lvl[:mx] %>/<%= lvl[:my] %>
+
+ <% end %>
+
+
+
+ show map
+
+
+
+

+ <% sprite[:sprite_levels].each do |lvl| %>
+
+ •
+
+ LV<%= lvl[:lv] %>
+
+ <% end %>
+
+
+ <% end %>
+ <% end %>
+
diff --git a/app/views/instance/_list.html.erb b/app/views/instance/_list.html.erb
index 6c5c329..d94a6e0 100644
--- a/app/views/instance/_list.html.erb
+++ b/app/views/instance/_list.html.erb
@@ -34,7 +34,7 @@
<% pop = instance.pops.find { |pop| pop.name == nm[:name].parameterize } %>
<% mins = ActiveSupport::Duration.build(Time.current - pop.created_at) %>
-
» <%= (120.minutes - mins).in_minutes.floor %>m
+
» <%= (120.minutes - mins).in_minutes.ceil %>m
@@ -55,14 +55,14 @@
<% end %>
<% if nm[:weather] && forecast[0][:curr_weather] != nm[:weather] %>
<% next_pattern = forecast.find { |f| f[:curr_weather] == nm[:weather] } %>
- <%= Weather.get_weather_name(nm[:weather]) %> in <%= ((next_pattern[:time] - Time.now.utc) / 1.minutes).floor %>m
+ <%= Weather.get_weather_name(nm[:weather]) %> in <%= ((next_pattern[:time] - Time.now.utc) / 1.minutes).ceil %>m
<% end %>
<% if nm[:spawned_by][:weather] && forecast[0][:curr_weather] != nm[:spawned_by][:weather] %>
<% next_pattern = forecast.find { |f| f[:curr_weather] == nm[:spawned_by][:weather] } %>
- <%= Weather.get_weather_name(nm[:spawned_by][:weather]) %> in <%= ((next_pattern[:time] - Time.now.utc) / 1.minutes).floor %>m
+ <%= Weather.get_weather_name(nm[:spawned_by][:weather]) %> in <%= ((next_pattern[:time] - Time.now.utc) / 1.minutes).ceil %>m
<% end %>
<% if (nm[:night_only] || nm[:spawned_by][:night_only]) && is_day? %>
-
Night in <%= ((Clock.to_earth_time(Clock.get_current_eorzea_time.change(hour: 18)) - Time.now.utc) / 1.minutes).floor %>m
+ Night in <%= ((Clock.to_earth_time(Clock.get_current_eorzea_time.change(hour: 18)) - Time.now.utc) / 1.minutes).ceil %>m
<% end %>
@@ -87,4 +87,12 @@
<% end %>
+
+
+
+
+
+ <%= form_with url: clone_instance_path(instance: @instance.public_id), html: { style: "display: inline-block;" } do |f| %>
+ <%= f.submit "clone instance" %>
+ <% end %>
diff --git a/app/views/instance/show.html.erb b/app/views/instance/show.html.erb
index a2854b0..c155c92 100644
--- a/app/views/instance/show.html.erb
+++ b/app/views/instance/show.html.erb
@@ -1,5 +1,5 @@
-
+
" hx-swap="outerHTML" hx-select="#container" hx-target="#container">
<%= link_to root_path do %>

<% end %>
@@ -36,13 +36,14 @@
»
![<%= @forecast[i + 1][:weather_name] %>](/weather/<%= @forecast[i + 1][:curr_weather] %>.png)
-
<%= ((@forecast[i + 1][:time] - Time.now.utc) / 1.minutes).floor %>m
+
<%= ((@forecast[i + 1][:time] - Time.now.utc) / 1.minutes).ceil %>m
<% end %>
<%= render partial: "fairies", locals: { instance: @instance } %>
+ <%= render partial: "chlog", locals: { instance: @instance, forecast: @forecast } %>
@@ -50,9 +51,3 @@
<%= javascript_include_tag "list" %>
-
-
-
-<%= form_with url: clone_instance_path(instance: @instance.public_id), html: { style: "display: inline-block;" } do |f| %>
- <%= f.submit "clone instance" %>
-<% end %>
diff --git a/config/initializers/data.rb b/config/initializers/data.rb
index fedf8a3..e334c63 100644
--- a/config/initializers/data.rb
+++ b/config/initializers/data.rb
@@ -2,10 +2,12 @@ anemos_data = Tomlrb.load_file("./data/anemos.toml", symbolize_keys: true)
pagos_data = Tomlrb.load_file("./data/pagos.toml", symbolize_keys: true)
pyros_data = Tomlrb.load_file("./data/pyros.toml", symbolize_keys: true)
hydatos_data = Tomlrb.load_file("./data/hydatos.toml", symbolize_keys: true)
+bestiary_data = Tomlrb.load_file("./data/bestiary.toml", symbolize_keys: true)
APP_DATA = {
anemos: anemos_data,
pagos: pagos_data,
pyros: pyros_data,
- hydatos: hydatos_data
+ hydatos: hydatos_data,
+ bestiary: bestiary_data
}
diff --git a/data/bestiary.toml b/data/bestiary.toml
new file mode 100644
index 0000000..ad0476e
--- /dev/null
+++ b/data/bestiary.toml
@@ -0,0 +1,236 @@
+[[bestiary]]
+name = "Blizzard Sprite"
+sprite = true
+weather = ["snow"]
+element = "ice"
+zone = "anemos"
+sprite_levels = [
+ { lv = 2, mx = 21.7, my = 31.7 },
+ { lv = 6, mx = 13.4, my = 23.9 },
+ { lv = 10, mx = 22.7, my = 24.6 },
+ { lv = 20, mx = 28.3, my = 14.4 },
+ { lv = 22, mx = 34.3, my = 16.0 }
+]
+
+[[bestiary]]
+name = "Typhoon Sprite"
+sprite = true
+weather = ["gales"]
+element = "wind"
+zone = "anemos"
+sprite_levels = [
+ { lv = 5, mx = 15.1, my = 24.9 },
+ { lv = 9, mx = 24.8, my = 23.7 },
+ { lv = 12, mx = 16.4, my = 19.0 },
+ { lv = 13, mx = 23.1, my = 20.4 },
+ { lv = 15, mx = 28.7, my = 20.5 },
+ { lv = 19, mx = 28.0, my = 16.6 },
+ { lv = 21, mx = 32.1, my = 17.3 },
+ { lv = 23, mx = 9.5, my = 17.6 },
+ { lv = 25, mx = 9.6, my = 21.4 }
+]
+
+[[bestiary]]
+name = "Rain Sprite"
+sprite = true
+weather = ["showers"]
+element = "water"
+zone = "anemos"
+sprite_levels = [
+ { lv = 7, mx = 13.9, my = 20.5 },
+ { lv = 9, mx = 22.2, my = 28.5 },
+ { lv = 11, mx = 19.0, my = 24.4 },
+ { lv = 14, mx = 26.1, my = 24.8 },
+ { lv = 17, mx = 18.3, my = 14.8 },
+ { lv = 18, mx = 20.8, my = 13.9 },
+ { lv = 24, mx = 9.1, my = 19.8 }
+]
+
+[[bestiary]]
+name = "Ember Sprite"
+sprite = true
+weather = ["heat"]
+element = "fire"
+zone = "pagos"
+sprite_levels = [
+ { lv = 23, mx = 15.6, my = 27.4 },
+ { lv = 27, mutates = true, mx = 28.6, my = 27.1 },
+ { lv = 31, adapts = true, mx = 15.6, my = 22.0 },
+ { lv = 35, adapts = true, mx = 8.4, my = 17.5 },
+ { lv = 39, adapts = true, mx = 24.0, my = 16.7 }
+]
+
+[[bestiary]]
+name = "Snowmelt Sprite"
+sprite = true
+weather = ["fog"]
+element = "water"
+zone = "pagos"
+sprite_levels = [
+ { lv = 22, mx = 13.4, my = 25.4 },
+ { lv = 26, mutates = true, mx = 27.3, my = 26.7 },
+ { lv = 29, mx = 32.8, my = 24.8 },
+ { lv = 34, mx = 6.9, my = 14.0 },
+ { lv = 38, adapts = true, mx = 22.2, my = 16.3 }
+]
+
+[[bestiary]]
+name = "Snows. Sprite"
+sprite = true
+weather = ["snow", "blizzards"]
+element = "ice"
+zone = "pagos"
+sprite_levels = [
+ { lv = 20, mx = 9.4, my = 23.6 },
+ { lv = 24, mx = 18.2, my = 28.1 },
+ { lv = 28, adapts = true, mx = 30.9, my = 27.9 },
+ { lv = 32, mutates = true, mx = 10.9, my = 12.9 },
+ { lv = 36, mx = 22.3, my = 18.1 },
+ { lv = 40, adapts = true, mx = 36.7, my = 15.5 }
+]
+
+[[bestiary]]
+name = "Thunders. Sprite"
+sprite = true
+weather = ["thunder"]
+element = "lightning"
+zone = "pagos"
+sprite_levels = [
+ { lv = 21, mx = 12.8, my = 27.9 },
+ { lv = 25, mutates = true, mx = 21.1, my = 28.7 },
+ { lv = 30, mx = 30.0, my = 22.8 },
+ { lv = 33, mutates = true, mx = 10.6, my = 16.3 },
+ { lv = 37, adapts = true, mx = 31.3, my = 17.3 }
+]
+
+[[bestiary]]
+name = "Typhoon Sprite"
+sprite = true
+weather = ["umbral_wind"]
+element = "wind"
+zone = "pyros"
+sprite_levels = [
+ { lv = 35, mx = 17.0, my = 25.8 },
+ { lv = 40, mx = 29.1, my = 27.5 },
+ { lv = 42, mx = 31.5, my = 32.5 },
+ { lv = 42, mx = 28.2, my = 32.9 },
+ { lv = 45, mx = 24.1, my = 18.7 },
+ { lv = 45, mx = 18.1, my = 15.5 },
+ { lv = 48, mx = 14.1, my = 9.1 },
+ { lv = 49, mx = 15.2, my = 7.3 },
+ { lv = 55, adapts = true, mx = 38.2, my = 16.4 },
+ { lv = 55, adapts = true, mx = 15.4, my = 36.9 }
+]
+
+[[bestiary]]
+name = "Ember Sprite"
+sprite = true
+weather = ["heat"]
+element = "fire"
+zone = "pyros"
+sprite_levels = [
+ { lv = 38, mx = 13.1, my = 29.0 },
+ { lv = 38, mx = 25.0, my = 24.4 },
+ { lv = 38, mx = 25.3, my = 27.0 },
+ { lv = 39, mx = 26.4, my = 23.9 },
+ { lv = 39, mx = 11.4, my = 29.7 },
+ { lv = 43, adapts = true, mx = 25.2, my = 37.7 },
+ { lv = 43, adapts = true, mx = 19.0, my = 34.4 },
+ { lv = 44, mx = 21.0, my = 32.2 },
+ { lv = 53, mx = 29.7 , my = 17.2 },
+ { lv = 53, mx = 35.8, my = 16.5 }
+]
+
+[[bestiary]]
+name = "Thunders. Sprite"
+sprite = true
+weather = ["thunder"]
+element = "lightning"
+zone = "pyros"
+sprite_levels = [
+ { lv = 36, mx = 17.5, my = 27.4 },
+ { lv = 37, mx = 23.7, my = 26.8 },
+ { lv = 41, mx = 24.0, my = 34.1 },
+ { lv = 46, adapts = true, mx = 12.0, my = 17.3 },
+ { lv = 51, mx = 25.7, my = 9.2 },
+ { lv = 54, adapts = true, mx = 36.5, my = 15.2 },
+ { lv = 54, adapts = true, mx = 12.1, my = 33.5 }
+]
+
+[[bestiary]]
+name = "Snows. Sprite"
+sprite = true
+weather = ["snow", "blizzards"]
+element = "ice"
+zone = "pyros"
+sprite_levels = [
+ { lv = 47, mx = 13.1, my = 14.5 },
+ { lv = 50, mx = 24.9, my = 6.5 },
+ { lv = 50, mx = 25.3, my = 12.2 },
+ { lv = 52, adapts = true, mx = 27.4, my = 17.5 },
+ { lv = 52, adapts = true, mx = 30.0, my = 12.3 }
+]
+
+[[bestiary]]
+name = "Thunders. Sprites"
+sprite = true
+weather = ["thunder"]
+element = "lightning"
+zone = "hydatos"
+sprite_levels = [
+ { lv = 53, adapts = true, mx = 11.6, my = 23.5 },
+ { lv = 53, adapts = true, mx = 14.7, my = 25.0 },
+ { lv = 57, mx = 7.7, my = 15.6 },
+ { lv = 57, mx = 5.7, my = 21.2 },
+ { lv = 61, mutates = true, mx = 25.6, my = 19.9 },
+ { lv = 65, mx = 34.6, my = 17.0 },
+ { lv = 65, mx = 30.9, my = 21.0 }
+]
+
+[[bestiary]]
+name = "Snows. Sprite"
+sprite = true
+weather = ["snow"]
+element = "ice"
+zone = "hydatos"
+sprite_levels = [
+ { lv = 52, mx = 16.4, my = 22.3 },
+ { lv = 52, mx = 13.0, my = 14.1 },
+ { lv = 56, adapts = true, mx = 10.4, my = 15.5 },
+ { lv = 56, adapts = true, mx = 8.4, my = 19.7 },
+ { lv = 56, adapts = true, mx = 10.8, my = 21.1 },
+ { lv = 60, adapts = true, mx = 24.4, my = 17.8 },
+ { lv = 64, adapts = true, mx = 34.9, my = 14.0 },
+ { lv = 65, mx = 32.4, my = 28.7 }
+]
+
+[[bestiary]]
+name = "Death Sprite"
+sprite = true
+weather = ["gloom"]
+element = "fire"
+zone = "hydatos"
+sprite_levels = [
+ { lv = 50, mx = 19.4, my = 16.1 },
+ { lv = 54, mx = 22.2, my = 23.2 },
+ { lv = 58, mx = 3.6, my = 14.1 },
+ { lv = 58, mx = 8.2, my = 12.7 },
+ { lv = 58, mx = 10.6, my = 19.4 },
+ { lv = 62, mx = 30.3, my = 27.2 },
+ { lv = 62, mx = 35.9, my = 27.1 },
+ { lv = 65, mx = 32.4, my = 28.7 }
+]
+
+[[bestiary]]
+name = "Snowmelt Sprite"
+sprite = true
+weather = ["showers"]
+element = "water"
+zone = "hydatos"
+sprite_levels = [
+ { lv = 51, mx = 16.2, my = 20.6 },
+ { lv = 55, adapts = true, mx = 10.8, my = 26.9 },
+ { lv = 59, mx = 3.8, my = 27.3 },
+ { lv = 63, mutates = true, mx = 36.1, my = 18.5 },
+ { lv = 65, mx = 32.4, my = 28.7 }
+]
diff --git a/lib/bestiary.rb b/lib/bestiary.rb
new file mode 100644
index 0000000..6b4b23e
--- /dev/null
+++ b/lib/bestiary.rb
@@ -0,0 +1,7 @@
+class Bestiary
+ def self.get_sprites_for_zone(zone)
+ b = APP_DATA[:bestiary][:bestiary]
+
+ b.filter { |m| m[:sprite] && m[:zone] == zone }
+ end
+end
diff --git a/public/adaptation.png b/public/adaptation.png
new file mode 100644
index 0000000..4575b5a
Binary files /dev/null and b/public/adaptation.png differ
diff --git a/public/maps/anemos_fw.jpg b/public/maps/anemos_fw.jpg
new file mode 100644
index 0000000..544b045
Binary files /dev/null and b/public/maps/anemos_fw.jpg differ
diff --git a/public/maps/hydatos_fw.jpg b/public/maps/hydatos_fw.jpg
new file mode 100644
index 0000000..f3bdb2f
Binary files /dev/null and b/public/maps/hydatos_fw.jpg differ
diff --git a/public/maps/pagos_fw.jpg b/public/maps/pagos_fw.jpg
new file mode 100644
index 0000000..226e9d7
Binary files /dev/null and b/public/maps/pagos_fw.jpg differ
diff --git a/public/maps/pyros_fw.jpg b/public/maps/pyros_fw.jpg
new file mode 100644
index 0000000..d6a3a7b
Binary files /dev/null and b/public/maps/pyros_fw.jpg differ
diff --git a/public/mutation.png b/public/mutation.png
new file mode 100644
index 0000000..90a82cc
Binary files /dev/null and b/public/mutation.png differ