From 705e7b1dc06ddbdee39fd6468890a9a3c6f74bc6 Mon Sep 17 00:00:00 2001 From: insects Date: Tue, 11 Mar 2025 14:57:28 +0100 Subject: [PATCH] feat: add weather forecasting --- Gemfile | 2 +- Gemfile.lock | 7 +-- app/assets/stylesheets/application.css | 28 +++++++++++ app/javascript/application.js | 6 --- app/views/instance/_list.html.erb | 8 ++-- app/views/instance/show.html.erb | 21 +++++++- config/initializers/data.rb | 10 +++- data/anemos.toml | 7 +++ data/hydatos.toml | 7 +++ data/pagos.toml | 8 ++++ data/pyros.toml | 8 ++++ lib/clock.rb | 24 ++++++++++ lib/weather.rb | 64 +++++++++++++++++++++++++ public/weather/fair.png | Bin 0 -> 2124 bytes public/weather/fog.png | Bin 0 -> 2211 bytes public/{ => weather}/gales.png | Bin public/weather/showers.png | Bin 0 -> 2450 bytes public/weather/snow.png | Bin 0 -> 2307 bytes public/weather/thunder.png | Bin 0 -> 4890 bytes public/weather/umbral_wind.png | Bin 0 -> 2261 bytes 20 files changed, 181 insertions(+), 19 deletions(-) create mode 100644 data/hydatos.toml create mode 100644 data/pagos.toml create mode 100644 data/pyros.toml create mode 100644 lib/clock.rb create mode 100644 lib/weather.rb create mode 100644 public/weather/fair.png create mode 100644 public/weather/fog.png rename public/{ => weather}/gales.png (100%) create mode 100644 public/weather/showers.png create mode 100644 public/weather/snow.png create mode 100644 public/weather/thunder.png create mode 100644 public/weather/umbral_wind.png diff --git a/Gemfile b/Gemfile index 198b7d8..3bdd24e 100644 --- a/Gemfile +++ b/Gemfile @@ -8,7 +8,7 @@ gem "importmap-rails" gem "turbo-rails" gem "stimulus-rails" gem "jbuilder" -gem "toml-rb" +gem "tomlrb" gem "nanoid" gem "spicy-proton" diff --git a/Gemfile.lock b/Gemfile.lock index 5db998e..1b3aad3 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -94,7 +94,6 @@ GEM rack-test (>= 0.6.3) regexp_parser (>= 1.5, < 3.0) xpath (~> 3.2) - citrus (3.0.2) concurrent-ruby (1.3.5) connection_pool (2.5.0) crass (1.0.6) @@ -299,9 +298,7 @@ GEM thruster (0.1.12-x86_64-darwin) thruster (0.1.12-x86_64-linux) timeout (0.4.3) - toml-rb (3.0.1) - citrus (~> 3.0, > 3.0) - racc (~> 1.7) + tomlrb (2.0.3) turbo-rails (2.0.13) actionpack (>= 7.1.0) railties (>= 7.1.0) @@ -358,7 +355,7 @@ DEPENDENCIES spicy-proton stimulus-rails thruster - toml-rb + tomlrb turbo-rails tzinfo-data web-console diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css index 92566ae..c2f4c80 100644 --- a/app/assets/stylesheets/application.css +++ b/app/assets/stylesheets/application.css @@ -29,6 +29,11 @@ header .muted { font-weight: normal; } +main { + display: grid; + grid-template-columns: 5fr 1fr; +} + .new-buttons { display: flex; gap: 5px; @@ -169,3 +174,26 @@ span#password { .copyable:hover { cursor: pointer; } + +.sidebar { + display: flex; + flex-direction: column; + align-items: center; + margin: 10px 0; +} + +.weather-list { + display: flex; + align-items: center; + gap: 3px; +} + +.weather { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: 12px; + color: #555; +} + diff --git a/app/javascript/application.js b/app/javascript/application.js index de6bf57..165bdb5 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -1,8 +1,2 @@ // Configure your import map in config/importmap.rb. Read more: https://github.com/rails/importmap-rails import "htmx.org" -import htmx from "htmx.org" -htmx.logger = function(elt, event, data) { - if(console) { - console.log(event, elt, data); - } -} diff --git a/app/views/instance/_list.html.erb b/app/views/instance/_list.html.erb index 6c30549..c506db9 100644 --- a/app/views/instance/_list.html.erb +++ b/app/views/instance/_list.html.erb @@ -1,4 +1,4 @@ -
+
<% APP_DATA[instance.zone.to_sym][:nms].each do |nm| %> <% is_popped = instance.pops.filter { |pop| (Time.current - 120.minutes) <= pop.created_at }.any? { |pop| pop.name == nm[:name].parameterize } %>
@@ -10,7 +10,7 @@ LV<%= nm[:level].to_s.rjust(2, "0") %> <%= nm[:name] %> <% if nm[:weather] %> - + <% end %>
@@ -20,7 +20,7 @@ ๐ŸŒ™ <% end %> <% if nm[:spawned_by][:weather] %> - + <% end %> LV<%= nm[:spawned_by][:level].to_s.rjust(2, "0") %>
@@ -52,4 +52,4 @@
<% end %> -
+ diff --git a/app/views/instance/show.html.erb b/app/views/instance/show.html.erb index 9673813..acdc8d7 100644 --- a/app/views/instance/show.html.erb +++ b/app/views/instance/show.html.erb @@ -22,7 +22,26 @@ - <%= render partial: "list", locals: { instance: @instance } %> +
+ <%= render partial: "list", locals: { instance: @instance } %> + + +
<%= javascript_include_tag "list" %> diff --git a/config/initializers/data.rb b/config/initializers/data.rb index e534c94..fedf8a3 100644 --- a/config/initializers/data.rb +++ b/config/initializers/data.rb @@ -1,5 +1,11 @@ -anemos_data = TomlRB.load_file("./data/anemos.toml", symbolize_keys: true) +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) APP_DATA = { - anemos: anemos_data + anemos: anemos_data, + pagos: pagos_data, + pyros: pyros_data, + hydatos: hydatos_data } diff --git a/data/anemos.toml b/data/anemos.toml index 19dfaa8..dad6b0a 100644 --- a/data/anemos.toml +++ b/data/anemos.toml @@ -1,3 +1,10 @@ +weather = [ + ["fair", 30], + ["gales", 30], + ["showers", 30], + ["snow", 10] +] + [[nms]] name = "Sabotender Corrido" level = 1 diff --git a/data/hydatos.toml b/data/hydatos.toml new file mode 100644 index 0000000..bc61492 --- /dev/null +++ b/data/hydatos.toml @@ -0,0 +1,7 @@ +weather = [ + ["fair", 12], + ["showers", 22], + ["gloom", 22], + ["thunder", 22], + ["snow", 22] +] diff --git a/data/pagos.toml b/data/pagos.toml new file mode 100644 index 0000000..fff082c --- /dev/null +++ b/data/pagos.toml @@ -0,0 +1,8 @@ +weather = [ + ["fair", 10], + ["fog", 18], + ["heat", 18], + ["snow", 18], + ["thunder", 18], + ["blizzards", 18] +] diff --git a/data/pyros.toml b/data/pyros.toml new file mode 100644 index 0000000..930ec6d --- /dev/null +++ b/data/pyros.toml @@ -0,0 +1,8 @@ +weather = [ + ["fair", 10], + ["heat", 18], + ["thunder", 18], + ["blizzards", 18], + ["umbral_wind", 18], + ["snow", 18] +] diff --git a/lib/clock.rb b/lib/clock.rb new file mode 100644 index 0000000..b4c548c --- /dev/null +++ b/lib/clock.rb @@ -0,0 +1,24 @@ +class Clock + EARTH_TO_EORZEA = 3600.0 / 175.0 + EORZEA_TO_EARTH = 1.0 / EARTH_TO_EORZEA + + def self.get_current_eorzea_time + to_eorzea_time(Time.now.utc) + end + + def self.to_eorzea_time(earth_time) + et_ts = earth_time.to_i + new_ts = et_ts.abs * EARTH_TO_EORZEA + Time.at(new_ts) + end + + def self.to_earth_time(ez_time) + ez_ts = ez_time.to_i + new_ts = (ez_ts * EORZEA_TO_EARTH).ceil + Time.at(new_ts) + end + + def self.display_ez_time(ez_time) + ez_time.strftime("%H:%M") + end +end diff --git a/lib/weather.rb b/lib/weather.rb new file mode 100644 index 0000000..c09fe84 --- /dev/null +++ b/lib/weather.rb @@ -0,0 +1,64 @@ +class Weather + def self.get_weather(zone, hash = hash(get_seed)) + total = 0 + APP_DATA[zone][:weather].each do |arr| + name, rate = arr + if (total += rate) > hash + return name + end + end + end + + def self.forecast(zone, seed = get_seed, count = 10) + res = [] + prev_hash = hash(seed - 1) + prev_weather = get_weather(zone, prev_hash) + count.times do |_| + curr_hash = hash(seed) + curr_weather = get_weather(zone, curr_hash) + res.push({ zone: zone, prev_weather: prev_weather, curr_weather: curr_weather, weather_name: get_weather_name(curr_weather), seed: seed, time: Time.at(seed * 1400000.0 / 1000) }) + prev_hash = curr_hash + prev_weather = curr_weather + seed += 1 + end + res + end + + def self.round_to_last_weather_time(time) + last_hour = time.hour / 8 * 8 + time.change(hour: last_hour) + end + + def self.get_weather_name(name) + case name + when "fair" then "Fair Skies" + when "showers" then "Showers" + when "gales" then "Gales" + when "blizzards" then "Blizzards" + when "heat" then "Heat" + when "thunder" then "Thunderstorms" + when "gloom" then "Gloom" + when "snow" then "Snow" + when "fog" then "Fog" + when "umbral_wind" then "Umbral Wind" + else "Unknown Weather" + end + end + + + def self.get_seed + (Time.now.utc.to_i * 1000 / 1400000.0).floor + end + + def self.hash(seed = get_seed) + base = (seed / 3).floor * 100 + ((seed + 1) % 3) * 8 + # Ruby has no other convenient way to convert a signed integer into an unsigned one + step1 = [ ((base << 11) ^ base) ].pack("L").unpack("L").first + step2 = [ (([ (step1 >> 8) ].pack("L").unpack("L").first.to_i ^ step1)) ].pack("L").unpack("L").first.to_i + step2 % 100 + end + + def self.unsigned_right(input, by) + input << by & 0xFF00 | input >> by & 0xFF + end +end diff --git a/public/weather/fair.png b/public/weather/fair.png new file mode 100644 index 0000000000000000000000000000000000000000..cf667677bc9c74efb2d86b420cb7656475048ce3 GIT binary patch literal 2124 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 literal 0 HcmV?d00001 diff --git a/public/gales.png b/public/weather/gales.png similarity index 100% rename from public/gales.png rename to public/weather/gales.png diff --git a/public/weather/showers.png b/public/weather/showers.png new file mode 100644 index 0000000000000000000000000000000000000000..d145962c4a6c0c6e2a0a92da362bb5a3f00c7ae8 GIT binary patch literal 2450 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 Qw*UYD07*qoM6N<$f)gvM@c;k- literal 0 HcmV?d00001 diff --git a/public/weather/snow.png b/public/weather/snow.png new file mode 100644 index 0000000000000000000000000000000000000000..18f6f3297532ffb34e4f00fdf2133ca22d6d09ca GIT binary patch literal 2307 zcmV+e3Ht7!z~CNBEj+ zzJp1OJV044J0895%$pkTKWZV3`}qp`$>LQs{|~DfZ<_bxe(^`j<2m6Ye8n2E*5r6d zvjdj7k}auP0reNW7v*dhUYwJ<6NpK0yZxXfx@^75|Cj4#K_@~^9el`xH ziUxuRuvh>^wz5zXsWic=$WZJm+-^5KJ-b8GT<}M9B$8>29ySO|o|%G?lc%`_m*5oK zU$dvyxmvGL-ZArDr?=lfM!OH3q(dK9(5bUk^w!S9v}Vgb#(rA2{X?EVNjvs^PW6rL zlr&Ow=tL#`YhO8S+5IW)JWx&t`T5<$r)lDE|4xqHkyjO8X7}2#;KGhG9CJ9X;tXgHq_&)gqlwpOvk?nMVQYkYNlI_qkv!G}iY^n^7NthX2 z3RWc2gd`DI7diw830M%*(=Y&DV~0TmS&`5uN5g=}hAEQbm>@I-)gxN>$M;WL`1=v_ zpB{-cM}DKW9-8WeGt12)W@b=%u}f26I{=g3-CZfP%SUh7iREG_U zq9Dy#`r-ycx&c;4>EM3o*Q*=sVy_)s8O;u`@WkRgJTrM1GMFpOzMbKiIo~Tg6TN$8 zL34X@O-%qc*{*61rwyuV7o1F@p)DAJ)*dn&NVXX$=#`C}3=QY+w!%meT0=1qtnj#9 zAXuRD_M|xT=0F@k5GfI8SVyxzjDenPPRhaosIWPh1W>GClXvm3R*ftIDMxX08>i< zF}Gxsz|^qH3ik<-sR=N5G8r?7G9BzNAjge}PLP?%s>nQI88RGW%I2 z88VmWzL~iowZKXe?7XJ*k?yEz3!=Z8g~xlj;ZPOCxEg2cL(mh61c0R3Tp6CC{WD*A zWyA}!#-OLFna$qFCF_V7NE!w#EXG4DBq5D7M{i-I!CFvc2}vfk%T|OF9Hh;P8O8n0 z29$W6#zC^1X`4^gV%hu!mC?r2vt@w!{daG6GCS)0uldI(4^`ZnOodno;kIQk3W7NfV@Vml>%v2@6Jb z9!n8d!W;`HlGN;t5q? zPPz+VP*-!ARugHzx}&_TVnx;1BB_Ks70DPnI|Jy`-GNXzindrteDux6%I%(o*7hI> z0CyzV%m!)-#}JO{Fw(^3B^31am_2gWo5YFZr&EF2)9(SO!~a;m>Karvf7LE(^93mq ziBn5QfNs~el0OinOIK^?=&35&^YKO6!C##-m+w$bLkk`Jn6N z9p*38_4;;-#}izUFuk_#HaWW&RSAv%a}co1^$h-z_tm${D8%J941?M_{M6XgMptgt zvUoL}tf->T&t0eMx9X_wUMpSWai2d(7q3)PFchY`hWiwa#Yxw7;v*7m{H%ov#xDNi zAn-MZJZV|~k_FK}zf-~c6{Zkp8;-^)p3v!jhmRUtJLuSFm2~~~Jvw*!Hdn>R^D(+r z(@4pr!QxTA>g4nJXKwg^r#2oKSo66}@O(#X7VTy1zVXi_u zm;Y9EBVD^$PvsY`QB!j}wR;0B7@-*Z1^qs%s`b;K_Egj0@xSv5F2VT_$6L&GWDcpE z{^G{u#^ZH#v6{2J-$`v=Kly_J3WS3cVj*w9&%%Cc;<^9S253uJ1I>78SJIVJTq!sN z*S9{^*Py=xh-eFq>BSfo^5_&;)_9nfh7x?#h92AKEl^r^F5k{p)vY0Mm+G-Hlk}x ddzhcL{{q{J_Yt99w}Sux002ovPDHLkV1i6BYXtxR literal 0 HcmV?d00001 diff --git a/public/weather/thunder.png b/public/weather/thunder.png new file mode 100644 index 0000000000000000000000000000000000000000..fae193201aa78ad359a6ab590da6f0429b4e31a8 GIT binary patch literal 4890 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 M07*qoM6N<$f>`r<5dZ)H literal 0 HcmV?d00001 diff --git a/public/weather/umbral_wind.png b/public/weather/umbral_wind.png new file mode 100644 index 0000000000000000000000000000000000000000..83d3ee6c5d2e74907143596e2fb575a5e07d2fe9 GIT binary patch literal 2261 zcmV;`2rBo9P)WFTUBAZ9~vWMy(7M{;3sXlY|} zAW(8|V`X!5Z*qUb?Oy-@2u?{vK~#9!tyf!Y9A_2&=AYZHcfGs5#g6Sb7rU`jH)$+Y z2$c$zx{;s)As+0$@PY)wJOF)06;F8u<)xy46ds^0q!OT(NR2Df^Z~V8N+J@cPHkd4 zzTejNuGc%WbK^TR>&Unigw&CaW_RbG|2yY9-#O=B1ONNdXgbZL*|an*RsSxRM{{UZ znp^)K(4vp+bl@`n%QLP1yn)t6dz5ybc81nL%huQI(w1qHv|F?Z+B$8I=Hn9__#|M7 zP@)omr)i^T%MYWoMMqaFaI_g{$#E44*eL@`8^FREu(%Omxpe`>~Ucld{FGMpduk-zv zX*ZFu@^8@}2A1yy+B3+eF5RPr=b;^Mhx*2s5Vkk>g>Qdw37e%CxNw|SLY?+~V4+xYIuu9xxicMjmO9E2 zbh83%ktv7Zewg?d@vdnJa2Up0X=For{GrD0F7GE>V2nP6(AGPG-@kV;+Np@({)|L) z(LS);MMz^fcd8G=Lrh&8%yPUlJgt`Sp&~?)BCh;Si|-1$X~E7F7`P7CsUTc0Q348T zuoLR}r{EMzeg2z&8ROzP+5?(r)T5P>`2vo&kKl`+f!TWsEtyA9=*}W^_OLqfS5!6@ zV?YU1Fe4HWF-k3p2-<}9GmpnxZB9?1=IlVIx0J>q%?#k=@oQKL+H~3TBL=j6j@Gm!uLJ; z#piASwN^n?pjJEEVSN4^YLoM$#JNGM2(WaZL9MH8SW%I@!z!0y6goh*NVEncWkXp) zv5G;Ju3~*DNv{)9>^{n?3o&)#sBWk@1*V=x#^}Z_t0Mf01AqL3XTsT)K|Wj*U}=Nr zRezT1wQtL(-~4@A}~$$ebff=ytjx{1Y^_u=OnVdt9>4)&w=*82m3DZo-j zN3GuDXvh|zxgN9(RceGxepymT66CWD%d*kb(hMWl3eRQ!sxBh(t;sfA7*hPgvd;W# zDFcD-p;TIgo;v|6dko>}UhWuIPijBMS-Z0v4Hh@GtI)DJ800~tileHZ^av)Tfy-^% zMY*y%d>RT>0K$`hS61bi`IoB?BXFATqX={h0@`GR;&YRE@>YSWK$ZB#kyr z#Wk(bAq`oBs->FsOa>{d0EeK(wM~>N)C2`ss|8qCoI%wUW;i|xEPH#?vjQx$+Yw+n zIZTH4W?-?`8clhaeibTk9>%Y+k1$~0ip&@`QqYp@gnIg4$ffeh5>nUzkn(J)m^ zNsu!6nZ3R-hmiAo>$h*fy+89uZgY1($Kr*Itlw;mzjNW(*S-pM`Vn@XJUfxgQefH0 z=tI&XHLBZCQXUjb=u-zKYHT7KdwX!{l$B}Zdy3*?o6TkrntaX_l*iweEw0qD0s$5h zWk%j8-@fzu{g-||GVsG6z&OHTPGCr`RX_$fZ%L`-*OHb=CyF%>TNIK*ZR|Q!xM4xd zQRh+;X$wIn4Yj_8rI&t!y*m>!m2TkBJeKp29L!!`y*56S?(FL8{m!?c*rjxzn+42u zkSN?`e)Y>0Z+lzLT{r6((LrFjB-c{h*PZ2+7iKPBzUVG3_4R-Md&uV(0ch zuyc1Jtj^6#$^733`u$55iu&YW7OSx`GdoB-zjXDg%u}A?<0&JTL?Zwn;#2qwBiF&` jdB(*e?Ol9If7<>9p9+a4__rXd00000NkvXXu0mjfNL@Mw literal 0 HcmV?d00001