From c038eeadeaa6b4cf8713d751da04ada3cfdc9609 Mon Sep 17 00:00:00 2001 From: Voyager Date: Tue, 11 Aug 2026 16:35:33 -0400 Subject: [PATCH] Refine shop, online sessions, and player feedback --- .../characters/faces/mouth/mouth_open_ah.png | Bin 0 -> 45463 bytes .../faces/mouth/mouth_open_ah.png.import | 40 +++ .../characters/faces/mouth/mouth_open_oh.png | Bin 0 -> 31772 bytes .../faces/mouth/mouth_open_oh.png.import | 40 +++ .../faces/mouth/mouth_open_smile.png | Bin 0 -> 29756 bytes .../faces/mouth/mouth_open_smile.png.import | 40 +++ art/patterns/pattern_moneyfish.png | Bin 1352 -> 1089 bytes economy/fishing_shop_stock.gd | 10 +- fish/fish_quality.gd | 8 +- items/catalog/item_catalog.tres | 2 +- items/catalog/luminous_roe.tres | 4 +- items/catalog/shrimp.tres | 4 +- items/catalog/snails.tres | 4 +- items/catalog/squid_chunks.tres | 4 +- items/catalog/whole_anchovy.tres | 8 +- items/catalog/whole_sardine.tres | 8 +- items/icons/bait/64_bait_minnow.png | Bin 0 -> 3931 bytes items/icons/bait/64_bait_minnow.png.import | 40 +++ items/icons/bait/64_bait_roe_luminous.png | Bin 0 -> 5552 bytes .../bait/64_bait_roe_luminous.png.import | 40 +++ items/icons/bait/64_bait_sardine.png | Bin 0 -> 4671 bytes items/icons/bait/64_bait_sardine.png.import | 40 +++ items/icons/bait/64_bait_shrimp.png | Bin 0 -> 5429 bytes items/icons/bait/64_bait_shrimp.png.import | 40 +++ items/icons/bait/64_bait_snail.png | Bin 0 -> 5978 bytes items/icons/bait/64_bait_snail.png.import | 40 +++ items/icons/bait/64_bait_squid.png | Bin 0 -> 4483 bytes items/icons/bait/64_bait_squid.png.import | 40 +++ network/discovery_client.gd | 50 ++- network/network_player_list_service.gd | 4 + network/network_protocol.gd | 101 ++++++ network/network_session.gd | 65 +++- player/player.gd | 22 ++ tests/dedicated_server_config_validation.gd | 37 +- tests/economy_regression_validation.gd | 11 +- tests/exported_decal_hotfix_validation.gd | 36 +- tests/job_system_validation.gd | 3 + ui/chat_ui.gd | 9 +- ui/fishing_shop.gd | 339 +++++++++++++++--- ui/fishing_shop.tscn | 67 ++-- ui/game_theme.tres | 14 + ui/game_ui.gd | 15 +- ui/hotbar.gd | 27 +- ui/network/join_game_page.gd | 42 ++- ui/network/join_game_page.tscn | 7 - ui/players_page.gd | 134 ++++++- ui/weather_icon.gd | 2 +- 47 files changed, 1208 insertions(+), 189 deletions(-) create mode 100644 art/exported/characters/faces/mouth/mouth_open_ah.png create mode 100644 art/exported/characters/faces/mouth/mouth_open_ah.png.import create mode 100644 art/exported/characters/faces/mouth/mouth_open_oh.png create mode 100644 art/exported/characters/faces/mouth/mouth_open_oh.png.import create mode 100644 art/exported/characters/faces/mouth/mouth_open_smile.png create mode 100644 art/exported/characters/faces/mouth/mouth_open_smile.png.import create mode 100644 items/icons/bait/64_bait_minnow.png create mode 100644 items/icons/bait/64_bait_minnow.png.import create mode 100644 items/icons/bait/64_bait_roe_luminous.png create mode 100644 items/icons/bait/64_bait_roe_luminous.png.import create mode 100644 items/icons/bait/64_bait_sardine.png create mode 100644 items/icons/bait/64_bait_sardine.png.import create mode 100644 items/icons/bait/64_bait_shrimp.png create mode 100644 items/icons/bait/64_bait_shrimp.png.import create mode 100644 items/icons/bait/64_bait_snail.png create mode 100644 items/icons/bait/64_bait_snail.png.import create mode 100644 items/icons/bait/64_bait_squid.png create mode 100644 items/icons/bait/64_bait_squid.png.import diff --git a/art/exported/characters/faces/mouth/mouth_open_ah.png b/art/exported/characters/faces/mouth/mouth_open_ah.png new file mode 100644 index 0000000000000000000000000000000000000000..b028f060ce07fc4568267c023a341bda86fc168d GIT binary patch literal 45463 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 z zB)%xoa|~>iZeW2WwX*S-UK1<%c9X?bbO+mEV@2?kM7o4Q&7qWQt=~|1jo2j>V5?|P z@0ke#7u|VUpN-QPABZwGm3(={YD&GQ4Y49In{aws@mIx*-Mur#dX`bM4g&V#_R97; zV!z9rcH$!4&(>vsrwnTO9fOY@A~#o;5)kuq#+Et9X}wfaQ{(Cg<Lv=%8(7z2l%# zzSfqOZ?tHsMSMv%nc--?5b2}~c7D!LF0d?Gxi##{4ZBEPe1R7@N*vq^b585dwW^%O z>?VmVT+)Ao=pDGeYK72;hSTduJ4{dl1gR?Or>(|Otv2qDUSTB`sT*UaeO~QfwTxD2 zZfjmr#v73AWPt;`Vr-WXc+b6hW)i~5tgP~c>uXb&3QH3KrwwiEhU5^C)6=-IcafX& zgcn;j=;92nV8JiM>=$N@b}nPI!>%kw*GAYav@QlRE+mb-7}=x@bo1CEaKxqEIauV4 zM8IEcHJ3QUjMH?YeZU5T;2XIXm4PlV+BYq>K*qu$A?{ZQ!El4ng)w2NR^-}svWFN^)aDD=uY;9j`TxpcT%64Mz30fGH z55GM(zTOW&s|$?LI=$Outgkh+Z(%w$Q~2SCdVC;-9>PSLF^t1$lPxzelE9RKS0hGf zC!lu=r;E!44zBpmnPJ)8aFD|>1k7dXz@=|be`v-ipAb@$y#zNs`pT!IZikiS8zDKT zOAD{eoDUjol`o$0yZ>U-{bvZ>KLFgo~BUAgq3O>#$P9oMCn7$w5sUQXM z!rhG@J}AhznFkZFo#;bw!Bywf1z$ke93|Ra3!AiTIq?xD_!@Q&Y^^&Bpp-T# zAjw56z&605qw+jgeQ35ex<^DfSIr*q=-g2$c<8~fr~uOr)8X(eLJM3|aPuGhPcJqB zwWY12%Ju!u1x%EMzHA=b@x>6l_%}ilD#xWb3BKiewEJ@K0@YmI(&%<}Ci=w%KFDF) zk{^UmX%9Tsy2n3L~PAikNH`D{*QuTAE98 zS~t@|43^%;ag95wV#tuBTBejC`|WcvuYwmsP}=;93Fv}lsl94!r5(8l*;DD8wU?t{ z6IpO;3|>?97npHtXA!h*Aw1w3DAHe-9V$Lur};SW9(9o$+zP>O;HYHqcx=%w{2@K% z@rMv*-9o0sA9_W>tgM__%-(TXlWtzuVhrIoLPcWqcJ5YEU=j1DQGCm^2k}smYY@`e zz`dGiR_+H@f3EEe%3AWhAE)I0eSJ0DWqz_=i@kx51t!$LI_Yn!)E9b#Fc4CL6%!S8#(G!Ai3Wl zOkf|&+kp)b&M`kkVPIU=PmDqk&VzUi*qx<0^EsW<<9XU4}bjEKjT#kZY1`F}s zku@GK{zisM%=$t(jY~5-Vt{v`k$tp%R z`?oK&%m-Skz2nq*yh1$-gDRDOsm`;{PawE7!dqGpOgWGsD2Y&Ai7tMx=HHz%4^LgT z{cyLd@-0t2+%y=h0@V;6lCBw=x1Myy?tq87<5(B}avNiGgO!!SLLR=XPZJj2VBrhA zv?hTNF~&?y7wel@4df*=<&p*R6>^TT~G%*RG=B0n+E9ObQ}x>dI;GjwVJpS{ac+Db%$ zVij&-%pE8A9BYFAi|c+(T$ucRA06uSNU00zumWXI1y@P<*vGh{x4m7Z95p)oP5ug;;jW;)C@D_A!@(WQXBkjrNn{!=^Xw=~8dhBbZ*i)Tp~ zZuCN@9i6xBXaTn05QgcvYxMojc?Rcu0HE=15d70F9v%3Y#SQ=<0-nLIw_uzjwO1~7AQmEZ zU+&S<(%Uz_u+m@@>DT&!Kv=M24ZKHLbPSL6i_jFSl%kkX#FL%P26f+4xcKRT#D=ZF zUO?l1@Y-zpgzDrYdL$s{m1NA-kFQ2zO6A90Oax%PPo`C4MZoVvu`YE;@J-km=Hm~I zhs0f+eSOincU*?{!jmCm9i85ny|7~VYr}SVGJQP!Wo(KqN&-wu5qgM~fR2`6#PU=q zpfx_Bp}>rg5*#DZS~mBjJ?|z&7t9%F8eq%I0GAHvjp^o`2gUukzS|eF(7E^&v`mEM z&-wUb7gz6V3dDJcb8JoM5Uf)%n=2Y=7@0R05LCA*vWdpkDTNv7>5y{J9wx@agPau-qF-PQv9 zNi0ak5!HxQsg6V+tg&LQmA#Hax*~3X@`T>4AY(OsBKi*cDMkRwjnT3ta4_no0{RZiO07! zwX8wnXCz&N1UJ`Ta}H}rSVO`Z3)Wb$#)35#tg&E?1#2u=W5F5=)>yE{g8z#JvH2Sq an{ro}o$)&B!LS|}3$ literal 0 HcmV?d00001 diff --git a/art/exported/characters/faces/mouth/mouth_open_oh.png.import b/art/exported/characters/faces/mouth/mouth_open_oh.png.import new file mode 100644 index 0000000..e4602aa --- /dev/null +++ b/art/exported/characters/faces/mouth/mouth_open_oh.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://c78hwv0t3ph47" +path="res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://art/exported/characters/faces/mouth/mouth_open_oh.png" +dest_files=["res://.godot/imported/mouth_open_oh.png-10fc59160b782879c415074831421088.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/art/exported/characters/faces/mouth/mouth_open_smile.png b/art/exported/characters/faces/mouth/mouth_open_smile.png new file mode 100644 index 0000000000000000000000000000000000000000..57675f8ca6da20e4c90a15803e56e59c96b2796c GIT binary patch literal 29756 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 delta 1230 zcmV;<1Tp);2*?VMihltC0Tl7XG3x*T1fEGmK~#9!?V8VP9Ay~CKkqy5{z?;@s;!k+ z>_r+hG-4spfJ7{^G^gODAiY!yy$T{;^i&blV|&!UK|BbJc(5S;O0E z$e`cPZSOl`@!nkrz?|O?uIhLUli0zZ^suKZaJYv4GK>Bw`#{I}e*!!%$Pv3^v--~I z>fq0uJIl3a7P)!gTl8WEeI)~4%O8$e>?>vew^bnsp!~(YP{p1u`RmPF0p{Fj`vpWC zKRmU-ZwFU6aewPYN(+Cpv{Gp9o18eo$@9}(-v15=IG2ZEO9c}^CN1)ki#_gw)K%32 z7SP~bTE}^9WY-gg8HLm$g2&xo4zcyKyM3L{dHi|mca|Rh30!bK-G^Fmf@MG(Xs&k# zjf~j#tcW`5=UUlVLa(L$YdLO_Q!d40KT_PdiSzjo>VM-@Fb0GLnn3ZWV9&Vi*R{{* zkW5suhw8r%c*qkqa23vHIn*bqU=R!wG=XUUkYJ}O0O;!(f1|GN91_$BPy;F&wVZ}E zecyxMRe&%-6DZp^75YX7JK>_14eASnyj6p$pcc~pjagTS*l-_%VS*-5+)?Nca>zvw zZ?=T?hJPR>s9z20mV^8y{-fvt!*l~QVTAMcFy4Ec*trrIL4WU{{?LJsYq|lN&_sB1 z81LdHGFF3S1I6HFr&~c|rz(&Wf4K)VDhH~f(aUM{wM^Fs-PuHysQ5>5t04VutJ zcw;2AW9e4Vs1<`ZTf#nH+X0%m3_k48JJM^-!GF$`kWq`e9S&#`BR~T*>UO=xLIO>q z1c(AOaSoN_j!HiF%s8B@*HEyue-}S@%!Y{Kw5|ia183xhOLpGFENgd7B5Dub}a>KxVdEOufpAXayLrwNE;VemU4Y zXtGT<_^Fufi4c*NfGU_~>f3y!Sq?H*_1DL?ABQ$yX%nO`GC*B*>UX+j+dV8$MT07s z9fI_Q0Iec)oGj&iNUDNC&u8&o?Lz@}tAFbT2W-BAp6|gohi*Iu|5KczX)9VvF(_ocmp9C+m1<$H!vdGj)|o=5V~gDF>&+;cEcaoQGmF31G{P4F|qUpLgfCy z&J2i|HxQ!p2R4WN$k*AXjCR8H>#LYT-ErC>Q|pa4un`S^p!4Af>KiAxbEOVhodj=S z6tp+WKn?)e1!p(nT} s0DIpBLTTM4Ux0wEyTq43(C-8P07k83lyd9J%>V!Z07*qoM6N<$g7f5A{{R30 diff --git a/economy/fishing_shop_stock.gd b/economy/fishing_shop_stock.gd index e74232e..87795ad 100644 --- a/economy/fishing_shop_stock.gd +++ b/economy/fishing_shop_stock.gd @@ -14,8 +14,8 @@ const ITEM_PRICES: Dictionary[StringName, int] = { &"snails": 3, &"shrimp": 7, &"squid_chunks": 15, - &"whole_anchovy": 30, - &"whole_sardine": 60, + &"whole_anchovy": 60, + &"whole_sardine": 30, &"luminous_roe": 125, &"the_standby": 750, &"coffee": 20, @@ -28,8 +28,8 @@ const BAIT_UNLOCK_PRICES: Dictionary[StringName, int] = { &"snails": 400, &"shrimp": 1200, &"squid_chunks": 3500, - &"whole_anchovy": 9000, - &"whole_sardine": 22000, + &"whole_anchovy": 22000, + &"whole_sardine": 9000, &"luminous_roe": 50000, } const ITEM_ORDER: Array[StringName] = [ @@ -37,8 +37,8 @@ const ITEM_ORDER: Array[StringName] = [ &"snails", &"shrimp", &"squid_chunks", - &"whole_anchovy", &"whole_sardine", + &"whole_anchovy", &"luminous_roe", &"the_standby", FISH_FINDER_ID, diff --git a/fish/fish_quality.gd b/fish/fish_quality.gd index 28ca93d..6ae4031 100644 --- a/fish/fish_quality.gd +++ b/fish/fish_quality.gd @@ -19,8 +19,8 @@ const WORM_ROLL_WEIGHTS: Array[float] = [80.0, 10.0, 5.0, 4.0, 1.0] const SNAIL_ROLL_WEIGHTS: Array[float] = [68.0, 16.0, 8.0, 6.0, 2.0] const SHRIMP_ROLL_WEIGHTS: Array[float] = [56.0, 21.0, 12.0, 8.0, 3.0] const SQUID_ROLL_WEIGHTS: Array[float] = [44.0, 25.0, 16.0, 11.0, 4.0] -const SARDINE_ROLL_WEIGHTS: Array[float] = [34.0, 27.0, 20.0, 13.0, 6.0] -const MINNOW_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0] +const MINNOW_ROLL_WEIGHTS: Array[float] = [34.0, 27.0, 20.0, 13.0, 6.0] +const SARDINE_ROLL_WEIGHTS: Array[float] = [25.0, 28.0, 23.0, 16.0, 8.0] const LUMINOUS_ROE_ROLL_WEIGHTS: Array[float] = [16.0, 26.0, 26.0, 20.0, 12.0] const SALE_MULTIPLIERS: Array[float] = [1.0, 1.1, 1.25, 1.5, 2.0] # Legacy profile multiplier retained for serialized/profile compatibility. @@ -158,9 +158,9 @@ static func rarity_weights_for_bait( elif active_bait_tags.has(&"bait_rarity_6"): weights.assign(LUMINOUS_ROE_ROLL_WEIGHTS) elif active_bait_tags.has(&"bait_rarity_5"): - weights.assign(MINNOW_ROLL_WEIGHTS) - elif active_bait_tags.has(&"bait_rarity_4"): weights.assign(SARDINE_ROLL_WEIGHTS) + elif active_bait_tags.has(&"bait_rarity_4"): + weights.assign(MINNOW_ROLL_WEIGHTS) elif active_bait_tags.has(&"bait_rarity_3"): weights.assign(SQUID_ROLL_WEIGHTS) elif active_bait_tags.has(&"bait_rarity_2"): diff --git a/items/catalog/item_catalog.tres b/items/catalog/item_catalog.tres index f8c0f5d..43377ba 100644 --- a/items/catalog/item_catalog.tres +++ b/items/catalog/item_catalog.tres @@ -20,4 +20,4 @@ [resource] script = ExtResource("1_script") -items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("13_anchovy"), ExtResource("14_sardine"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries")] +items = [ExtResource("2_rod"), ExtResource("3_coffee"), ExtResource("4_energy"), ExtResource("5_snack"), ExtResource("6_finder"), ExtResource("7_cooler"), ExtResource("8_art_kit"), ExtResource("9_worms"), ExtResource("10_snails"), ExtResource("11_shrimp"), ExtResource("12_squid"), ExtResource("14_sardine"), ExtResource("13_anchovy"), ExtResource("15_roe"), ExtResource("16_standby"), ExtResource("17_batteries")] diff --git a/items/catalog/luminous_roe.tres b/items/catalog/luminous_roe.tres index aae9fc6..7814f42 100644 --- a/items/catalog/luminous_roe.tres +++ b/items/catalog/luminous_roe.tres @@ -1,12 +1,14 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] [ext_resource type="Script" path="res://items/item_data.gd" id="1"] +[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_roe_luminous.png" id="2_icon"] [resource] script = ExtResource("1") item_id = &"luminous_roe" display_name = "luminous roe" description = "rare glowing roe with the strongest rare catch odds." +icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_6"]) stackable = true diff --git a/items/catalog/shrimp.tres b/items/catalog/shrimp.tres index a9a63d3..e308163 100644 --- a/items/catalog/shrimp.tres +++ b/items/catalog/shrimp.tres @@ -1,12 +1,14 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] [ext_resource type="Script" path="res://items/item_data.gd" id="1"] +[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_shrimp.png" id="2_icon"] [resource] script = ExtResource("1") item_id = &"shrimp" display_name = "shrimp" description = "lively bait that noticeably improves rare catch chances." +icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_2"]) stackable = true diff --git a/items/catalog/snails.tres b/items/catalog/snails.tres index 6bddbd4..12d4497 100644 --- a/items/catalog/snails.tres +++ b/items/catalog/snails.tres @@ -1,12 +1,14 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] [ext_resource type="Script" path="res://items/item_data.gd" id="1"] +[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_snail.png" id="2_icon"] [resource] script = ExtResource("1") item_id = &"snails" display_name = "snails" description = "slow-moving bait with a better chance for rarer catches." +icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_1"]) stackable = true diff --git a/items/catalog/squid_chunks.tres b/items/catalog/squid_chunks.tres index 108387b..dfe245f 100644 --- a/items/catalog/squid_chunks.tres +++ b/items/catalog/squid_chunks.tres @@ -1,12 +1,14 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] [ext_resource type="Script" path="res://items/item_data.gd" id="1"] +[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_squid.png" id="2_icon"] [resource] script = ExtResource("1") item_id = &"squid_chunks" display_name = "squid chunks" description = "rich cut bait that strongly favors rarer catches." +icon = ExtResource("2_icon") category = 2 bait_tags = Array[StringName]([&"bait_rarity_3"]) stackable = true diff --git a/items/catalog/whole_anchovy.tres b/items/catalog/whole_anchovy.tres index 0aee243..cbc60b6 100644 --- a/items/catalog/whole_anchovy.tres +++ b/items/catalog/whole_anchovy.tres @@ -1,14 +1,16 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] [ext_resource type="Script" path="res://items/item_data.gd" id="1"] +[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_sardine.png" id="2_icon"] [resource] script = ExtResource("1") item_id = &"whole_anchovy" display_name = "sardine" -description = "oily schooling bait with high rare catch potential." +description = "valuable oily schooling bait tuned for exceptional catches." +icon = ExtResource("2_icon") category = 2 -bait_tags = Array[StringName]([&"bait_rarity_4"]) +bait_tags = Array[StringName]([&"bait_rarity_5"]) stackable = true max_stack = 10 hotbar_allowed = false diff --git a/items/catalog/whole_sardine.tres b/items/catalog/whole_sardine.tres index d03ac81..c5d0163 100644 --- a/items/catalog/whole_sardine.tres +++ b/items/catalog/whole_sardine.tres @@ -1,14 +1,16 @@ -[gd_resource type="Resource" script_class="ItemData" load_steps=2 format=3] +[gd_resource type="Resource" script_class="ItemData" load_steps=3 format=3] [ext_resource type="Script" path="res://items/item_data.gd" id="1"] +[ext_resource type="Texture2D" path="res://items/icons/bait/64_bait_minnow.png" id="2_icon"] [resource] script = ExtResource("1") item_id = &"whole_sardine" display_name = "minnow" -description = "valuable live bait tuned for exceptional catches." +description = "small live bait with high rare catch potential." +icon = ExtResource("2_icon") category = 2 -bait_tags = Array[StringName]([&"bait_rarity_5"]) +bait_tags = Array[StringName]([&"bait_rarity_4"]) stackable = true max_stack = 10 hotbar_allowed = false diff --git a/items/icons/bait/64_bait_minnow.png b/items/icons/bait/64_bait_minnow.png new file mode 100644 index 0000000000000000000000000000000000000000..f083d657f351dea746a4d066ab341254d195cfa5 GIT binary patch literal 3931 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 K0A8hGd+R^gZ^dZ< literal 0 HcmV?d00001 diff --git a/items/icons/bait/64_bait_minnow.png.import b/items/icons/bait/64_bait_minnow.png.import new file mode 100644 index 0000000..67f57f5 --- /dev/null +++ b/items/icons/bait/64_bait_minnow.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://oc1nckfxeir0" +path="res://.godot/imported/64_bait_minnow.png-25bb645caffd0cad87b30d6b38b80ca4.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://items/icons/bait/64_bait_minnow.png" +dest_files=["res://.godot/imported/64_bait_minnow.png-25bb645caffd0cad87b30d6b38b80ca4.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/items/icons/bait/64_bait_roe_luminous.png b/items/icons/bait/64_bait_roe_luminous.png new file mode 100644 index 0000000000000000000000000000000000000000..dc89fbd1a5b93b92cff75e5799db8cc43fa4d388 GIT binary patch literal 5552 zcmaJ_XH-+$whlcs=~4vgT_iv#f(i+t_bR<75=f|_BS?vWA|0gnjuaapNKue-1gRn- zkO+wM-VwOrob%3o_uVnx*kjE($Nau;%{BMf^T*n8hWZ+`R2)}}0|0`B0Dy7IQ@B;w=D-HmFVkQ8<>XqAKbejNVz%8^8 zdU^mcLJS0u6OjW*2oVv0m|$oxE#Puvb0Q7^*`Ij;z=r7BKd}pu$iMk9NC4^I92CI= zoE(uD`^&_Qzp{UOl0u@tHADbFA@TochyjHp|Bf&9*X-m@369)JM*{}9xO@uRDpCm< zN-s@IUjX1H|K%bIEK~L;Bq=ajdTJCaKx!rueriKUP5^)y6l`jNF?I~#@$&IRAW?80 zOpq6xClKug04O}oGt){b1<|}aW!WVyNi*K!f- zdvxpw73h_bzp63q$oc~YFB@(+IJBQIgZU_aXx`zsK~2oO zbRnyu){_U@kAOc;-* zNv4YEn~mz**un}roZbi#h+QT56swt@!5K~cOdahAPyP+U!yNs5X`}guB0l&2!tk*~ z1gh0Iip5MD1I;&#x$i>IZhRA)yIjka-Pp)mZ{K<;El{ggfjwS}c)9)rT9!Gq7-aUu zPkkYNEsxC}vMq_&sF)#vrmT3CX}4V&5z9`N`ZO!{PPKU2nPsdhlkP=HZu*v_8wYd~ z%&ZhqEV~FRFn>>7~)1U1=&t65**$bW>D)Fr{SF4H9}I@ zNe&fm#mI%~+;4}*67y8Js_T*zU5nXdEL+UHB}4qB-P!2?xE|$xvp~Q(mX0|pdpSJP zhW80=kfGIvY;zjkrC`>5xaajc`Pai0kzAv|KH+v(Ml*k+J=-=358){@t zSNHLG(kW%x6`1=0HiaqJ2AyAlPm&$)<=<({`SNmQVQ%I66M^k6{fPV@7XqDK%C@{g z#Vj*>I}r-uGcD*%l#y8vJmhTWC)(+pgn_sNFUFBTNBEosF=`eV7e@?S(b)%1Z~$qL zw44M;K|)H(RO*%@2&4#-6Th?|Va>9L{}7-(U66>N{}3oBf(tim^I#Qbm0+D|z zV326IFUFDZUH+dUGKw;?e`(ANvkM3shD!}R5Q$J!^+b94n0Prl!vRusImB87ko6xB z=IQK*hI?T29ntW=#xRlS{BpvX{-LN5HZedTeBl^?6i6ZUS?5~<%<~Uy=<12_M8h#Y zNM|h%4BW@d2aa*XAU!<*(#7#{;sh?+zq!ajILZP>Xu`wyk|8T4-FUNk`oB4r|G^RG zwX7y^u3zq><%Mwu69!W)5Tgk3!322vxSJv|sJ{=ta>i{!U@~4ZF-XK^^Jv0>eE+I8 z3r4yT&_BEX6aDY*fBhlDMWFoI-k+5Jr#=1;pdP4?YW5qSZ+HzKV!ClYWCI5Cm!{-czJ7w*ymJ zQqp!p{J*Ry*WLLw}$CdUpT&PLdK$-piTQ$@O0W?aQ`b!#2uhk2@1_hbFJ~kDfI8Vk#{n zMo5Pq4?M-(#639bu>F3jq}Lg9K66|jg70d&8xbYiO!+ao75zbSu+zyc*5Q( zbR^{B;J0-P`uGjTrlGx)z7)vm_$=K*aE)h#tM4e=zIg=3!`IFF@<&PH==AZzi1OiZ zJ#uls4R=tNV*s>2`c^MS6n>!Gci5ps>q|uJFROyhj$XfQv{8^MDU^LGQ-4xbv5&nH z`dc`WZ-aTNoqqK(O+=5&U?FtIQ!_@rslIOpdb*s?78UE@+x+f$*mGL(q%*wP%lmlR zOO{+1O%;S8WxFP5btmYkN#8uYhBS1i;InfWy71P%_(Q%A`qAqj+1KR3tc=I!un;@v z)4j5eu$_`;;peoTfm^+&;`iFsHfZ{8#^&*t@K@}`mmfK*D(Ye=pz?HUs>1Qsy<9M? zqvQ(@nX<^e^lvRm{7GYaY_yNs23Es&7g)~5Bzuo5X3z1ux}vqlYL%T2K|+oJ!+0$q z(+3)Fh`vp8@Xw+ctBd+G;kr;AnnGC+({r;@+1r$}V&KP^M-nu)94(_`o|FPbB5Mdr zIv@q-R)0`^1Zzdr@N;T|^T|Ih%z;Za=l)>m%qJ9`^qF0k*Kosk&#G52Oq^>vXqb1n zs9_pqVWZ^e@B3s!4E_yI4Ave<%M=bpcon@*kPc4M(x7drr@3w+4z2~+xmE$qgy-CW z&bl2nRMlfy;~cKhHV^jZu=R>WR!xbql*y!?@uTeU{K}g6dg&LsebdA0Neh)qQ;;>{ z88bw$Qg_seN^d6irREWY@8)0wHPn|^ED%8$m|5y}^uO9pCZkReJ z=1L3Wk%!ue$gyTWQ2706{q%e&v5t*9qeT90aOkc$A3iaBZRy9}wmYr?!{*+AX8OX6 zi{KKOQ7h}HDosy5j2x$2gx>Lnk|nFWR531<8m+OKQy3RBW7po-{ zH}Ci!#<6kIxsv|T0z&X}RCo~2yg$Dd?@cjSlqBKk%##9%qkniQ7z*)(924tY1pMos%5Y|&K0LkC~& zEza5nh(F`(l=Wx@c--)O&6MMvI-P*3hZN84oC~3Hzg9vtP#H#|Yo^;#S8at*NQ*1K zvyxx>zcwEyaqC=asA-O3?E_}>uCrUxunNNZ;X6X?rRuI%;{EMi6C$VIuPbvD$wYQu zgQWWWynplerhELhINR*frCre+g zgYEE|U^dp#m(8sloKQpmv=iD;b}qfOL)u+ZdZ-+k5i!Wf9U zj1E*v3|!T2tvGsxWuvBHdC>`dVfa$jIB+pQT#(v~DVaD&94QK}`E_=Fo0u=DclO=I z+I-^iu^CM!dE>QhiM7uG-3S4H$a5M_8VF-0xsg~S(M-f%TsduzRjgIgzDls04xdW- z6nfoMJ;to7*{>U-UXYNc+eRkpfLJhtTJ!s9x6uyPjjpI%bU(rv8!~WCZZx6pVPEJ3 z$~|V!XIYPYE!}cj7zBB;&qsXy0rS}~9XGSM8NwOMQTZ@kMYY@*Gp^tIV66{dDz`P8 zoJu8366QqHn|>lybUbynWNv7`?+X`_=62*9#LZ9f`y2YF?>|&FQTZf}Kcp8Tn`B=O zRX6LW?B?2_+4?2C@MeAWZ7^>kn=TDcKS$u)P|wflZ`1>0X&7WcObO6 z^!-TY2R6!LMLQZY8&Wi>EwGV!&k7Py;ph&XQR&BavMIL+`boiesmku+O3moB8OX85 zSe008%7h7)f#op-Ly079t6&;WsHiDIzHrK%&)G1$oySC!!UJ>y78B99(dCnE zqNwV+6f095iHjH=)xEf7iRnB$i zXUvMOww}w$cblrKqO?lq6?l1d@jYx>#59MIPHg;PwRV>9ZPAo8Ocifq0-ZKX8S1Gbxf+&ljIO*7 zMP*_w7E~7>;A}M^{#1#*;;yyGHMVQKAJ|nzDI`Xee()&Dn$NPm0HKnH(;an+kP!wb zTzlZE%*C;9>iK<2*8}Eyfb^|KUxD&?E+I{EO=&*yWkV6wV!K?1L15dwZgJl$eOucc zyh<{hxHhm3#@)TVX5dzMg*Q?bY6n~oKLm#c9nk!1Nm0p+9gl^4n{fJzd$>NV){tu@F%Mu{vu7@FOnrezF;; zBIO?xjjq_0EAmv{`f-`XZfiW4#|rM)anXsq#e^US%l;7Dmhcvvq>f6!;goXnX@-2G zj7gjL_;n|fVLeDSf50b!LWAg>P5BJErpb@d&!t~bR_gw&Bsb=L)Q=w=iyVkQ2rHST zT~UvHI&Y#b=yjvesEi@+by$rnZLGs?%pEyhlEjoWqx9gl@d4B8)@6K}=C8kq24|>S zL9C$q`llVUXN%7mLkk4)TkB1pxRa+{OzSJNAiCe?*doKa!%{!UCl{3gX5J-b!G&G@ z-5#?2TfGGmlQRq(d$C>3*$lOY-=m3DKNH>p&fmtVn)7S$PA%7>$>{2#^ea#6hOzEN z_TEhu{>S~`8|fUSXtnO`iSlm`g(im?hFZgOx3{w7fLy>ff3K`UCWwukjF`2m*viN>lr<}R07 z>MkXuA?4{jvDbh;9U>(QfwtIHobNgi67QbtRxj8)*%%j*0 zSbksx-bZ;=%2qxw++<#`GRRdxRa7R;Cf{{fO|vb#3cS~y*OJGG!-Eo1i4Nl@6B=w? zdTc>;;!dd_KD7pm4d|m4&`W~l6E>#9H)7dms=qDc;_I!$zW#jnPA9OR-;1u`Br&Fb zv5JAtFzZo#lqwSAX5_c>k)4Z+#9u$2Xugf%Encv)cx=<;S^V^juCZCP_m_r};_t*2 zlyzWrqmmXp3r8M`o^EcIdH(gTKZCYA%$QGfifuwHd^cn@;NAB9{PP9zTjwts@aSF5 z^)!g1Y0IH|^T?RGCOZdq%t%RYIW3Q}Rjq4S$SJ=tZ{qDmO#7*Q=!?%CE~E=tcLwd8 zI2{6P;uK}CdZaGI!-WQ)dT9v6sugpx#;ZK6rTX5LVIedM5#k1esd=*)t~oG_qYYOC za@xrEO}&D$wX@%(-uCyFJM`<`=UO+6dpIiA+Lqy+RYfrfwzXoDyXSFs&i(F}xDZ~} z{xpj&-0G%urty9K^}H2nTJB$QV0p|<3!tdpFCVbulDKSi3XOSIsJDL0_{f^*U_`QY zm&P!WOEl=?>El*~11D8C_r=Hr%IDoa=#?$ar; zr0OXJwNcq&`&hWvkGnI2dzYaqf|0L7Eg%K7nNYI;h6DA}Y|9^CXD>74J|{b9S)9-79^+SR#; z2c|+*tEMrm-~BtuhoKsz zK8JMezqY3?_T^J((X?phe#q1XMFGnGu%@0hcHKVobbtG(XJnFMK5o7X{BVmwQ%cV# zTgA_R|2N%jZ%X`8$L+0?ozCCRW@=}4?sdde)eeuJ3G3U1Z{w8rs|XiEz&5|d#Taf+ zmn(57L>FsDngl-JZa-e3_H6avR~w3&J};nKANnwa?rD|(+_paAEFTkih_oH3x_sy20se_!vyI1eNKJ*Cwxa)Cvl=qdZZC%Eo?K#(wPNd`s lH}C(xj{ki#54?WCnvJ;=ShjGbp75&yprxh{tAjd3{tL7i?oR*! literal 0 HcmV?d00001 diff --git a/items/icons/bait/64_bait_roe_luminous.png.import b/items/icons/bait/64_bait_roe_luminous.png.import new file mode 100644 index 0000000..b0a78d0 --- /dev/null +++ b/items/icons/bait/64_bait_roe_luminous.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bnqji6j6nigjq" +path="res://.godot/imported/64_bait_roe_luminous.png-cfa7eaf935630a20e2e8111f803c7741.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://items/icons/bait/64_bait_roe_luminous.png" +dest_files=["res://.godot/imported/64_bait_roe_luminous.png-cfa7eaf935630a20e2e8111f803c7741.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/items/icons/bait/64_bait_sardine.png b/items/icons/bait/64_bait_sardine.png new file mode 100644 index 0000000000000000000000000000000000000000..9161ba578ea752d4d98f27a089a436376355cf1d GIT binary patch literal 4671 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 P`v62m2mC?FD&&6vXTZst literal 0 HcmV?d00001 diff --git a/items/icons/bait/64_bait_shrimp.png.import b/items/icons/bait/64_bait_shrimp.png.import new file mode 100644 index 0000000..8c65f4a --- /dev/null +++ b/items/icons/bait/64_bait_shrimp.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://bpq47xg8tv6th" +path="res://.godot/imported/64_bait_shrimp.png-f68293fc8a81e803d5eeb1f268e4f3d3.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://items/icons/bait/64_bait_shrimp.png" +dest_files=["res://.godot/imported/64_bait_shrimp.png-f68293fc8a81e803d5eeb1f268e4f3d3.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/items/icons/bait/64_bait_snail.png b/items/icons/bait/64_bait_snail.png new file mode 100644 index 0000000000000000000000000000000000000000..39e6c16336d80f9dee471f13b8bfabbc883025d5 GIT binary patch literal 5978 zcmaJ_1ymeMv!2C*1h)Voc(C9u!Gb#km!OOLZg7`i!8LdmOMqYjf?Lo8=i-Y5cbDLA zx%a+%&;R~&-kF)Mo~rMw>gvpRpqcT$uR){z*dl#)eGfVr71)Z{_&;xFyr z5;+g_HwOd&c}V}uK?3rS|Ls5UDc8+&))im>Ddc0Ko{CnITi+rF^7q~CZL(6W&kO#Va1b+Fm{NBrL+l@+j}tJ1`W8a^_7B4t zOs`+~&`0bb|BR4NNqriMl_u+G22Qx)T#`0>3&9Sh9S$1m-GI^(2YJernz4k#HPLnU zHL1iT9qni*4^AkzK6Ql!3pF&jh^%70`poN8!{lPKhOS>|+NhS^^L};$sYW0yW7s%9 zXuo(KSt@bcrBJa2XOtx)f$Qf2OYd7AUGE_0%Kdv$sHSv398ch#VrU-$Iw-^Gn-ykNxhr^i8p0rQQH z2M!wAJz$^P<26U~J7j#Mwj~zwSX{(%Vt~r%K`qUoU||b4FhT&_oZS3uoPumzTv}X0 z!knDKocyd0EhnRVVbFgN9Gxv~t-SsvaB6Y#2y^lx?!O2f`i&I`0@FVcptg=+2-FO* zF8@yu9$_B7zc||J8Mz1!{sRZ>X=^3?%Gtr$?X`=U1sLGM%|uc_7)k#zN;_M;bp$&> zRm~j1f0bbz=G?Xl}5h=fqC}e zN5KVZ!HXzN8BeINq#M-3+09Bpn~ zfS!yZ84rsO#KntFPSeKJE}c$&ZpOq`b@664Dwy26%8qoQ9CO0HPu+1R-<%kaq-ZvE zCm#ue3G-O~4@D_Y`k?p$`V-OXJ6Efr0n1|;iQ~cAH=Rkv^BJGq+uQxcyOwhdqvU8e z4fY@R7WgKoNR1IRVACJ%f!$eb7z`MXDgF68gL}?-0n38dErs25E;0rDEo=36Y_V3fE+gIf? z>9xSR@SntzicXVWgDZ|sbkkdL_9x*l_*JuyZy*HmNl6YxbWW~yqCVe@ziKKctCA^p zFd(1u=Zg0gwfX-T%C{!GYTC&>TVyE1<*iG-eT{GWgR=6qe0em|u%aip5JC&o_8NGt zPBcE-U>Q&X_wOo!n7(`(ofA>blEHpQOJ9RVZpd>-hK{$M1>({%H2+DF4m9tN>pUxu z3Qna-8A^56$`x zo`!WfZGb6CLL&@Ej6~Y9mo=ME=nv0WzPZ?76pV<&c@0Au-?d@$QwU+AFL& zQ4t?8$NVv&gs~{{vUA?s1~2)mmOH`?fA!dQrwC#bM8b@JxHeb2jli( zk1ndP)*6XlJWRxYV0{r1m0jv z_r4!cQhSn_oViu+g7UuO1>QI)5zV&g@O<`zk;jF-H>sMnWe`Dt;iD=)y4Cuf%+tHN z<`)iL=k>V}h_{E8V-X$^+G%}vw;?_$=XHUOh)Iq{snFSgLYtt-BSM`l{wz1t7Jv) zTH8ETu|ie`3CWJp1ni~*^M@|`yThtY;l8Y-JM{KS z8e7-BGwfo|bqU`|l4IG|y7=cf5O%7*M={SDi!N~;UzXkPmpfZsU#tA~4Pw{GqwX)s zzKzh>Q?#6N)YK?CjSh@JVW8tB$a)!1?fplHMQ}a0KLE7Yl}eDBaN2hgK=QDT~^zpq%BY93rLe#a4_E0!k84MVE9yk}SOybTZu-slH*807$In`$nt~ zJ_Q#e>6D2pq_{ZRJU7Tn;Y-%}V{=JFRVL0LK*>(b`LGaCLyNhjpeYlReTH2!S~rCS z)BUDmB8hMKfZclo9EvQxIJ|fJDTAh5-Fwp|8#sAP!Nfc#4c0oKiy>yy?g3w&0Lc3V zIqc%;BFkteJ?psI-aY@aRT+5#xPguVku~1+5E?@qO?_#TT!(4Iz1Pls*Q0sUloDws zB%i2AE49FClLL#(7*x^5FC>Okx{@CWtBmK2B)((qyouHMVdLf2RP9qG6K2*V#!?91 z>w_#y8L(f#W7X@-9LY0z34Mjp#TPW)YNXl+ic+ikWwqjCtE!t6kyBLOwW*&`XO}Gq zDQI{FtQEJIZIbkOURSNnaOn-U1jnU(NOA+|ZOnGh`G_fa=D47wiZD*!(iCFQGb}cI z5be!lTB>BLcb36G3!yO!!uRqzP#8c%4fu4Oezwgq7aNL>u2h7rR1|Y3uX`wE zLxI0T6Z;3>g|OUH5V%{$`Rdj2?7sWWNP=2Dbd|F_S2&2&WcG*SH{X_C1#RZb3o<(1 z+!ru7iiq!%BsNha?cS&lm)oiTHYihhE<`7Yw~p+! zf7Mp@P|o_8niKtO)9s|b=tXBKR~ZZZ8!Vlz!eM2-!VjBfX+A@sq1vTnVIzB#r<;8c&OhET8dghDfHvf!k%XkI}(<>uoBr+8E>J>6OpL|2=%P%;__@MK2oTY^qepLeL@j_SNuer)(;#pF(YxbR>a|Bn4wveL4fdMmk8{7Bc^kaY`41Wce?4GJr%xIEx$==rbJU(I^Xa` zu8j)1mrvp|WEwdq5~NS!3oyg`cRk~RZZ!rSAL%lbynYpmb2&MP0SEk9Tkhr4HXJ&l zy~BYcoilx|*IVn~Pvvf$IPSh4?N3&qeHGer+MYr&i{ywDVtgO5mhFNeCym??^S#;z;X;g{BH zioJc+YVJgQq+l90)*JBJ{&=oFGVSrIvt6IO+VZAcOL%4yU@rjda0fO&33yK04t&FY6XR*7N)zq499Ybu*Ws+gO8N2gxMfxD^}`6LzabWOVu_mqq6i%O)GM<1Nub0Z7s!?FVmsbF0#3jS<2by^Lx!gUt%P&DU{v{!7SVEU7Ocuc#`@CD$Q$ciqDRi z6fVBZ>CFdxA7un-I-%BE+^s!{!L$=kW?631*5)>^Pjz45dTz%g(0ZouZAFpQ?)RsL zBxltj3^k(c9c9B)rG&Eg^hL^UOJ{aLqgaM#vsNdU&=AhW28A5z}FRGQV=T;lbCVTsX)FhT{R%d;QAUyQbOpxS4N+(A)dR z4K!DXQIOcM-eM9Iufw0*-r*DQa{c6&ya%ZGa}c71eQLb{*~;ppoFu%Z}$C z6Ih%xyM?+ptDkb1NqVu;e4@y!dF5Y=7d&<_w9?3|Vd4+*C*Qw%E*fKfSXZ+mG8bE1 zB@?$T8Z+Ab(R0Ygzi!nI@d;~R*)nyhi$R5Oyi)APGUPi8ILG~U5L&Rg3z6KQ@upto z`DCRee^lYIXFlV>w?5TyjyUvo{S>ilqAdGdwA$Vw+!Ncskx|-{wDDxXIcGk0(9YCS zm0XU(=MKebYtX{gaxPs!scAgG|L3C@MtR1*Vmb|2`J?R@q{ME=HJLnL3>5cx-VGhx zHB}5o->%3llBX^67{Y-?6Rm>M`}Gn7%WrEh6>g63WolHWaNI5;j%)p7E*pg9Zu7i(39HEyVD5F!L11%q4E3)I0%TJZG1U~9W z3F^>(%e-xlw*$>$xfa+P^<4==LEnqXL|8Hzb4skXU{VFiWstqtOhe4phSut<3nO4F zC9p}6aR=-1=fuH^$?fgAZ27!j^*UowTB#u)@$8?uyXtOP7W@IXWGa9$A>}?4@^aM= zW4N|3S-w#Ay>~N<`wwKq*?omQkFb5j*kY7e=m3+(X|bgus3&P}b*&)5?(4|Nv?}$; z$ptf#4VH-lrQj(SC8ZeL3HO`dN@>=~6O2%0dVLh66m@xX6~*NXL)_|{a4eyDTCdJp zWPrHJrls_1$7jLOcUcM)XAJpPT~=_&fmHO?ahNRY;|6G6so6|f?dz|ztE0MK^5EL3k9aw-}V8~Q<#=JewO!JjbzVdS>?@cknA+B#u qdz*`^Fk^b_=Dk4^6X_c4u&riEcFv}cLpADS?3}OZt=qnHarcdmLGjQnm2IvgH^rs#GY(RYfters@{*Awj1{nXw zc+w@{=zzxAA1WmO(*7+O@<4xcKmd>j{vQq)$Yc2T`jF3Or%0s>W=8`Z1aNTpEM4*ZWYPPoRznCMi(9WiHQ5(-M zW3|pq9W`OOv1}rwb`|4(_RGqKz(UEJn8+(HU;4mi*fdM!z3MOcxXrRy72P5l zWxjhbLIT$-#bou{7Dg;T8)JZ{QhbW^TR8?LvQlJv$Pz7@1*1-h8ElU`{7u1ed!8`OSeEX4?JrM71HCR7;teq6o%|t-Uskcrtk)*8W=E zR^)cUsj*+{5z1j>M9hlkb@O+qVA}dD#_>19aqz1Ni8G0u^l}o%X<6c&9dIb96Bb1m zfGk8-Q3|3gB_m@da|H^4Kp~2k4rj=D-J*#9AYi% z!i9f!z@afHKb!--F8|Laa!@&izc}W`S-Eu1@k0(O5bXlh^!D_|n)*06p#T}qY_J~P z$o-EI;q8RSpuBK~4j9y5ZI}f5ec0h#|3I|pPK;b!{7^VR2BMtyu>B?7Ec}nz*v%W~ zjX~kCXeT`{9181$Md2K9Xm2k-w%|eBB|4Vp-&k}Y%F_}-Pr}RZ5TPI=+aTRE`rjC< z|G-=lu_~iu1P}e_`QV)7>5Zush=X3m;sU&}9%g8q=ik9oXhNoRz~>GY5ni)=Re2^&>?@O_b254Nl&yBk_-TUFzX?%ngxz5W!ZZ2Tixgi zYb+z~efJNbUTT;Q8D3NNRcVfsgRO}*yQN0hb|ec7R9U)+Mb}pZ-ei<)Zf5|Ch}dm` zLSf@&qrwSN{Q8pX}){EEM~}`v>_0dFgE739S}fjRl%7*gZVn1HyrYR zbGv2~Urwa#Q^NXGw?|_Slztq&Yi6YUl!M*tV9DH&v`72>q{^r1#bE#b(`!wDJ16rs zKO}ds%A8Wy{M_&2LBk5Z_IYB%KJ^-urrka*Vj*pD#F{~^lg#>HFeRVJMlfj!rtqh& zMRRm(!`7RMK~F!-p4A}NvSgksb^vNJFZrmxd|-C}lhgF(OmzDIp1I(9^P$sl)Hho= zOL`e1G9S%&jmwq`7`4&&lOs(28dmOE@Z6lbED$m{I54NsyX!ZyvIZE}uCy6fPj;wE z7tI7=A8kCWmfc!&W=gLD4G1N0sP^?LMWO+b>h0!bP*1}eFH~8U8-5Iel&_px{DsXh zI-N#J9#CKxkX9T?!Go05l^TJ8??a3lvi2JH^b6Q_Xg`RF{g7qT*TXS-iEZ-HBX1n? zNOuO9E}Vbe$P@z1tlw@a)@xs>F1vc~ewW4E^#08BC1CMB;!f#cUj-k~uBgMLlre;3 zT!8~DJ2YTNRWKy+%xM!|Y|#lg*NVc(BRm6-0B>0tP?W4+$#V6Ryu1#(7Zc$U;RnVm zQGO&J4+4esul#i(x>ZU8md_Beb*sVUz z11wAkq3*nYPVBIUt;+(9rn~@Luob!mVG*=-_3$gfGIR_+Vdp<#4w%LjRT~rybr~i4 zx`eRi=tE|zbziwz(6{e&KQ^^{0(KTj>Q|jib|;6|A#7Q$iKim<%f8M}T};g7cXGJc z`&^R>WLouA{BUbCZsLs07MrhQ@<(Jra*|mLV9&+*e@|<}Y&|$79gln;`K_x@y$ovb zf765w&L|nzsjE)?F=S$PN;tZGWrJjEU!ItlmsA8#ac*NF===FY)0W)pH#nIlMk2_| zJ6J`bUV=V=udRw{SN%{DBg|NTBxG3Mx95@h)5u5HiYLoiFfX{&hzbJP56(xU1Iggb zzByh2-m@bo&Q+8`p9!UNrI$rVNi%*I2+s;%Hl9wK7Hy5FGS)?d$vgWjpl9a(L7pMK z`hXkT&H$s0k+en9n4Xg1(WD@!K`suxa3y=bN^PS``ri{-6JTu3i=1XI#9hbImQiuZf60>FpbM)3XyH6Zuf0A5=s_C|D``+E5J zeEAlS@812Y%^3JnHn<(97N%418#F;Z2yO8C7$YnwE4tHSr_z_*byPIGAYHWoL_1Ng z8m8d2;yFubZKk|4OiFNKco|%*=iYAFfUGjkoB1f%AEY1R$<#DA7sclnZ+gMJyim1cZ!vDr|6xnD08c?{QK{Er=NkptZz|}El$?Mb z4*>Zo*>Jq$=Z3@ly0w>cou3h3JMq-S$YY!~gYw#-a;~)0*93OXWyB|hE%xIVWg!SM zB-U4n^CYWfRwmPz24pKo@X%WEX0)ba`{X1j2p(Oa5} zys;i>A}Rfqq23`x@$t82UclH|dSz^|H$Jp=`>M}ZP#4G2^qX&cwytB4RcJ6rd};bd z0ysTL;^=x@j7CB!Y~g`1tF&G2T?;PM_RVQ zNjQt(ix-NFb5)j$*}+0XIiY8O@DEe(Zcx5p75mj6D~EDx)X38CFK!T>hCI#^IvXxg z6J%cLb@Xz=&HVFDGO9A$X+As8Z=&hZ&Cc+=#GOnmx@Qt*yB;*Y9FJ(L8@g>2+PB0J zlgQFR1;&=H&rOw5Ms8WVUb?%rJT)fAotgOvwh%QOuXKg*dJf|2i*bvwaev7 z(K}Nf`5_~w9>Y3|MVV@Id3tl0){4t5NEUnP7Jas05{o$&R*arvs}>@6!Ca+d=jP|@ z%GHMDz{aiit`~k0US6A{el|-1Uc(w?Tk4MFAQsAQ$q))&ynOW5>^?aI(wH%%Yc0sO z6ROTLI`V#b$(FQWs{cMLBoMSR`fQFT{p&A{exCNwFQ+(YkC|W1-tp&?l8&}1MR@pS z+O+9bnVmhcI!VHxu4TF{M;HR_b$CQHfns!8!(M}$IA%2SBaaEocT@{q>E<*Fjjm*5 z>gg;*M@L=7?uNVw8a4+5;L-1JzEOKFu4x@PmFs#sXAhcHqM zJ3TqS#UA;|Ti0b>49oP?tgATny-?LG_5AeF=OUobYR+Q(!i4vtMPe4x)7v~VJ*O{k zZOY<<6GcUX-B`&>u#twDsEvoZbN7eu=a^99W1QD*7%Lo~&SH}CU(F8UT_nHNH#KFF zYXCszSiC-9%N2-E2>Eu-RPs1Pm4~QZA^vf-uX2C>zz*@1ajf$AR%=ZqQDEifbF;b8 zE{krfR9vaeHrsZ>9{|7jkE~cJ@n+)pUFSVh)(o0xbyb+m;%4$CYkunfox=lC_Q~oZ z2jhr-@vfA}PV9-SQZ@o1HC}Q5{igH!=g@S%rHPv&A;qsA`lTsV3Gh?KZ*Q$DJe7JS zDZdmj64h2Hj+`dZnjhKeu1^+~Hx{3GkMW=_BSGykKF-dZ>QE&a<-N{^xTJGca=b=7 zw3Ke42IkhVly1UK%)`4!cdpSR(0s?$cJYFm60lb|D3X+Ko8ExX-`q=i7)z$E4U3pq3KG(EWStFXlq?C*n5DA7N=0I&X33 z_ip4W+@qo||F>?>)FfGv?fb8X-98m;4DlA8K4X2(Hlj@5!?Er$Yr|PZ1peem^`g4oIeS%@?~=dHK$nk&tL;~}PoPx_)PgH+AkDhb`{RYxWT=lvM&<1s(77_<#8^Q5V>bH+M0WW@HX1pnI2v~4?IF`av>0%n^yd(0u z{M;2JS`KxQH&`R!$IFH`T?1@Qr8)j;-e7-i{^9|kBec3(S|^F8|2+VDT84-!_^rr) E0r_D9(EtDd literal 0 HcmV?d00001 diff --git a/items/icons/bait/64_bait_squid.png.import b/items/icons/bait/64_bait_squid.png.import new file mode 100644 index 0000000..41bf2b4 --- /dev/null +++ b/items/icons/bait/64_bait_squid.png.import @@ -0,0 +1,40 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://kf3aglrd1en3" +path="res://.godot/imported/64_bait_squid.png-6ec6c9776ffcae93fd2fa312c8a9013d.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://items/icons/bait/64_bait_squid.png" +dest_files=["res://.godot/imported/64_bait_squid.png-6ec6c9776ffcae93fd2fa312c8a9013d.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +compress/uastc_level=0 +compress/rdo_quality_loss=0.0 +compress/hdr_compression=1 +compress/normal_map=0 +compress/channel_pack=0 +mipmaps/generate=false +mipmaps/limit=-1 +roughness/mode=0 +roughness/src_normal="" +process/channel_remap/red=0 +process/channel_remap/green=1 +process/channel_remap/blue=2 +process/channel_remap/alpha=3 +process/fix_alpha_border=true +process/premult_alpha=false +process/normal_map_invert_y=false +process/hdr_as_srgb=false +process/hdr_clamp_exposure=false +process/size_limit=0 +detect_3d/compress_to=1 diff --git a/network/discovery_client.gd b/network/discovery_client.gd index e5a09c7..f2263e8 100644 --- a/network/discovery_client.gd +++ b/network/discovery_client.gd @@ -13,7 +13,12 @@ signal public_join_state_changed(state: int) const BASE_URL_SETTING: String = "network/discovery/base_url" const BASE_URL_ENVIRONMENT: String = "NETFISHING_DISCOVERY_URL" const SETTINGS_PATH: String = "user://network_discovery.cfg" -const DEFAULT_ROOM_NAME: String = "NETfishing Room" +const DEFAULT_ROOM_NAME: String = "Player's Server" +const LEGACY_DEFAULT_ROOM_NAME: String = "NETfishing Room" +const LEGACY_DEDICATED_DEFAULT_ROOM_NAME: String = ( + "NETfishing Dedicated Server" +) +const ROOM_NAME_SUFFIX: String = "'s Server" const MAX_ROOM_NAME_LENGTH: int = 48 const HEARTBEAT_INTERVAL_SECONDS: float = 15.0 const REQUEST_TIMEOUT_SECONDS: float = 8.0 @@ -52,6 +57,7 @@ enum HostRequestKind { var _session: NetworkSession var _base_url: String = "" var _room_name: String = DEFAULT_ROOM_NAME +var _room_name_uses_default: bool = true var _discoverable: bool = false var _host_status_message: String = "" var _host_status_is_error: bool = false @@ -145,6 +151,9 @@ func _ready() -> void: func setup(session: NetworkSession) -> void: _session = session + if _room_name_uses_default: + _room_name = _default_room_name(_session.get_local_display_name()) + _save_settings() _session.set_session_display_name(_room_name) if not _session.state_changed.is_connected(_on_session_state_changed): _session.state_changed.connect(_on_session_state_changed) @@ -216,8 +225,12 @@ func set_room_name(value: String) -> bool: _set_host_status("Room name cannot be empty.", true) return false if cleaned == _room_name: + if _room_name_uses_default: + _room_name_uses_default = false + _save_settings() return true _room_name = cleaned + _room_name_uses_default = false _save_settings() if _session != null: _session.set_session_display_name(_room_name) @@ -278,6 +291,12 @@ func prepare_public_join(room: Dictionary) -> bool: if _session == null or not is_configured(): _set_public_join_state(PublicJoinState.ERROR) return false + if is_own_room(room): + _set_public_join_state(PublicJoinState.ERROR) + public_join_status_changed.emit( + "You are already hosting this room.", true + ) + return false var endpoint: String = room_endpoint(room) var room_id: String = str(room.get("room_id", "")).strip_edges() if endpoint.is_empty() or room_id.is_empty(): @@ -343,6 +362,13 @@ func room_endpoint(room: Dictionary) -> String: return "%s:%d" % [address, port] +func is_own_room(room: Dictionary) -> bool: + return ( + not _lease_room_id.is_empty() + and str(room.get("room_id", "")) == _lease_room_id + ) + + func _configured_base_url() -> String: var value: String = OS.get_environment(BASE_URL_ENVIRONMENT).strip_edges() if value.is_empty(): @@ -957,6 +983,16 @@ func _sanitize_room_name(value: String) -> String: return cleaned.left(MAX_ROOM_NAME_LENGTH) +func _default_room_name(player_name: String) -> String: + var cleaned_name: String = player_name.strip_edges() + if cleaned_name.is_empty(): + return DEFAULT_ROOM_NAME + return "%s%s" % [ + cleaned_name.left(MAX_ROOM_NAME_LENGTH - ROOM_NAME_SUFFIX.length()), + ROOM_NAME_SUFFIX, + ] + + func _load_settings() -> void: var config := ConfigFile.new() if config.load(SETTINGS_PATH) == OK: @@ -965,11 +1001,23 @@ func _load_settings() -> void: ) if not saved_name.is_empty(): _room_name = saved_name + _room_name_uses_default = bool(config.get_value( + "host", + "room_name_uses_default", + saved_name in [ + DEFAULT_ROOM_NAME, + LEGACY_DEFAULT_ROOM_NAME, + LEGACY_DEDICATED_DEFAULT_ROOM_NAME, + ], + )) func _save_settings() -> void: var config := ConfigFile.new() config.set_value("host", "room_name", _room_name) + config.set_value( + "host", "room_name_uses_default", _room_name_uses_default + ) var error: Error = config.save(SETTINGS_PATH) if error != OK: push_warning("Could not save the local NETfishing room name.") diff --git a/network/network_player_list_service.gd b/network/network_player_list_service.gd index 1a8136a..c4307f1 100644 --- a/network/network_player_list_service.gd +++ b/network/network_player_list_service.gd @@ -93,6 +93,10 @@ func is_open_host() -> bool: return _session.is_open_host() +func set_host_open(is_open: bool) -> bool: + return _session.set_host_open(is_open) + + func get_relationships() -> Array[Dictionary]: return _relationships.get_records() diff --git a/network/network_protocol.gd b/network/network_protocol.gd index cfe5a0b..b07d220 100644 --- a/network/network_protocol.gd +++ b/network/network_protocol.gd @@ -3,6 +3,7 @@ extends RefCounted const PROTOCOL_VERSION: int = 3 const GAME_BUILD: String = "prealpha" +const MAX_GAME_VERSION_LENGTH: int = 64 const MAX_DISPLAY_NAME_LENGTH: int = 24 const MAX_PROFILE_ID_LENGTH: int = 96 const MAX_NONCE_LENGTH: int = 96 @@ -37,9 +38,80 @@ enum RejectionCode { SERVER_SHUTTING_DOWN, UNSUPPORTED_CLIENT, BANNED, + CLIENT_OUTDATED, + SERVER_OUTDATED, + VERSION_MISMATCH, } +static func game_version() -> String: + return str(ProjectSettings.get_setting( + "application/config/version", "unknown" + )).strip_edges() + + +static func game_version_rejection( + client_version: String, + server_version: String, +) -> RejectionCode: + if client_version == server_version: + return RejectionCode.NONE + var comparison: int = _compare_game_versions( + client_version, server_version + ) + if comparison < 0: + return RejectionCode.CLIENT_OUTDATED + if comparison > 0: + return RejectionCode.SERVER_OUTDATED + return RejectionCode.VERSION_MISMATCH + + +static func _compare_game_versions(first: String, second: String) -> int: + var first_parts: PackedStringArray = first.split("-", true, 1) + var second_parts: PackedStringArray = second.split("-", true, 1) + var first_core: PackedStringArray = first_parts[0].split(".") + var second_core: PackedStringArray = second_parts[0].split(".") + for index: int in maxi(first_core.size(), second_core.size()): + var first_number: int = ( + int(first_core[index]) + if index < first_core.size() and first_core[index].is_valid_int() + else 0 + ) + var second_number: int = ( + int(second_core[index]) + if index < second_core.size() and second_core[index].is_valid_int() + else 0 + ) + if first_number != second_number: + return -1 if first_number < second_number else 1 + var first_rank: int = _prerelease_rank( + first_parts[1] if first_parts.size() > 1 else "" + ) + var second_rank: int = _prerelease_rank( + second_parts[1] if second_parts.size() > 1 else "" + ) + if first_rank != second_rank: + return -1 if first_rank < second_rank else 1 + return 0 + + +static func _prerelease_rank(label: String) -> int: + var normalized: String = label.to_lower() + if normalized.is_empty(): + return 5 + if normalized.begins_with("dev"): + return 0 + if normalized.begins_with("prealpha"): + return 1 + if normalized.begins_with("alpha"): + return 2 + if normalized.begins_with("beta"): + return 3 + if normalized.begins_with("rc"): + return 4 + return 2 + + static func make_identity_hello( public_key: String, fingerprint: String, @@ -48,6 +120,7 @@ static func make_identity_hello( ) -> Dictionary: return { "protocol_version": PROTOCOL_VERSION, + "game_version": game_version(), "public_key": public_key, "fingerprint": fingerprint, "client_nonce": client_nonce, @@ -62,6 +135,9 @@ static func validate_identity_hello(data: Variant) -> bool: var value: Dictionary = data return ( typeof(value.get("protocol_version")) == TYPE_INT + and typeof(value.get("game_version")) == TYPE_STRING + and not str(value["game_version"]).is_empty() + and str(value["game_version"]).length() <= MAX_GAME_VERSION_LENGTH and typeof(value.get("public_key")) == TYPE_STRING and str(value["public_key"]).to_utf8_buffer().size() <= MAX_PUBLIC_KEY_LENGTH and NetworkIdentityCrypto.valid_fingerprint(value.get("fingerprint")) @@ -85,6 +161,7 @@ static func make_client_hello( ) -> Dictionary: return { "protocol_version": PROTOCOL_VERSION, + "game_version": game_version(), "game_build": GAME_BUILD, "local_profile_id": profile_id, "display_name": display_name, @@ -108,6 +185,7 @@ static func validate_client_hello(data: Variant) -> String: var payload: Dictionary = data for key: String in [ "protocol_version", + "game_version", "game_build", "local_profile_id", "display_name", @@ -121,6 +199,12 @@ static func validate_client_hello(data: Variant) -> String: return "Handshake is missing %s." % key if typeof(payload["protocol_version"]) != TYPE_INT: return "Protocol version is invalid." + if ( + typeof(payload["game_version"]) != TYPE_STRING + or str(payload["game_version"]).is_empty() + or str(payload["game_version"]).length() > MAX_GAME_VERSION_LENGTH + ): + return "Game version is invalid." if typeof(payload["game_build"]) != TYPE_STRING: return "Game build is invalid." if typeof(payload["local_profile_id"]) != TYPE_STRING: @@ -171,6 +255,7 @@ static func validate_client_hello(data: Variant) -> String: static func client_profile_fields(data: Dictionary) -> Array: var appearance: Dictionary = data.get("cosmetic_snapshot", {}) return [ + str(data.get("game_version", "")), str(data.get("client_nonce", "")), str(data.get("identity_fingerprint", "")), str(data.get("local_profile_id", "")), @@ -204,6 +289,7 @@ static func make_server_hello( "accepted": accepted, "rejection_code": int(rejection_code), "protocol_version": PROTOCOL_VERSION, + "game_version": game_version(), "session_id": session_id, "assigned_peer_id": assigned_peer_id, "server_display_name": server_display_name, @@ -251,5 +337,20 @@ static func rejection_text(code: int) -> String: return "This game build is not supported by the server." RejectionCode.BANNED: return "You are not permitted to join this server." + RejectionCode.CLIENT_OUTDATED: + return ( + "Your NETfishing version is out of date. " + + "Update the game to join this server." + ) + RejectionCode.SERVER_OUTDATED: + return ( + "This server is out of date. " + + "The host needs to update NETfishing." + ) + RejectionCode.VERSION_MISMATCH: + return ( + "This game and server use different NETfishing versions. " + + "Update both to the latest release." + ) _: return "The server rejected the connection." diff --git a/network/network_session.gd b/network/network_session.gd index 3ad3892..1d332da 100644 --- a/network/network_session.gd +++ b/network/network_session.gd @@ -315,6 +315,10 @@ func get_session_display_name() -> String: return _session_display_name +func get_local_display_name() -> String: + return _profile.display_name if _profile != null else "" + + static func _can_bind_udp_port(port: int, bind_address: String = "*") -> bool: var probe := PacketPeerUDP.new() var error: Error = probe.bind(port, bind_address) @@ -838,6 +842,16 @@ func submit_identity_hello(data: Dictionary) -> void: var sender_id: int = multiplayer.get_remote_sender_id() if not is_host() or sender_id <= 1 or not _pending_authentication.has(sender_id): return + var client_game_version: String = str(data.get("game_version", "")) + var version_rejection: NetworkProtocol.RejectionCode = ( + NetworkProtocol.game_version_rejection( + client_game_version, + NetworkProtocol.game_version(), + ) + ) + if version_rejection != NetworkProtocol.RejectionCode.NONE: + _reject_peer(sender_id, version_rejection) + return if ( not NetworkProtocol.validate_identity_hello(data) or int(data["protocol_version"]) != NetworkProtocol.PROTOCOL_VERSION @@ -868,8 +882,10 @@ func submit_identity_hello(data: Dictionary) -> void: "client_nonce": str(data["client_nonce"]), "client_fingerprint": fingerprint, "client_public_key": public_pem, + "client_game_version": client_game_version, "server_nonce": server_nonce, "server_fingerprint": _host_identity.fingerprint, + "server_game_version": NetworkProtocol.game_version(), "session_id": _session_id, "generation": _operation_generation, "expires_at_msec": Time.get_ticks_msec() + 60000, @@ -886,7 +902,25 @@ func submit_identity_hello(data: Dictionary) -> void: @rpc("authority", "call_remote", "reliable", 0) func receive_server_identity_proof(data: Dictionary) -> void: - if state != State.AUTHENTICATING or not _valid_server_proof_shape(data): + if state != State.AUTHENTICATING: + return + var server_game_version: String = str(data.get("server_game_version", "")) + if server_game_version.is_empty(): + _fail_identity(NetworkProtocol.rejection_text( + NetworkProtocol.RejectionCode.SERVER_OUTDATED + )) + return + var version_rejection: NetworkProtocol.RejectionCode = ( + NetworkProtocol.game_version_rejection( + NetworkProtocol.game_version(), + server_game_version, + ) + ) + if version_rejection != NetworkProtocol.RejectionCode.NONE: + _fail_identity(NetworkProtocol.rejection_text(version_rejection)) + return + if not _valid_server_proof_shape(data): + _fail_identity("The server sent an invalid identity proof.") return if ( str(data["attempt_id"]) != str(_client_identity_attempt.get("attempt_id", "")) @@ -995,6 +1029,7 @@ func submit_client_identity_proof(data: Dictionary) -> void: _authenticated_identity_cache[sender_id] = { "fingerprint": challenge["client_fingerprint"], "public_key": challenge["client_public_key"], + "game_version": challenge["client_game_version"], } if ( _host_bans != null @@ -1041,6 +1076,16 @@ func submit_client_hello(data: Dictionary) -> void: if not validation_error.is_empty(): _reject_peer(sender_id, NetworkProtocol.RejectionCode.MALFORMED_HANDSHAKE) return + var client_game_version: String = str(data.get("game_version", "")) + var version_rejection: NetworkProtocol.RejectionCode = ( + NetworkProtocol.game_version_rejection( + client_game_version, + NetworkProtocol.game_version(), + ) + ) + if version_rejection != NetworkProtocol.RejectionCode.NONE: + _reject_peer(sender_id, version_rejection) + return var client_capabilities: PackedStringArray = _sanitized_capabilities( data.get("capability_flags", []) ) @@ -1056,6 +1101,7 @@ func submit_client_hello(data: Dictionary) -> void: return if ( data["identity_fingerprint"] != identity["fingerprint"] + or client_game_version != str(identity.get("game_version", "")) or not NetworkIdentityCrypto.verify_fields( NetworkIdentityCrypto.load_public_key(identity["public_key"]), "handshake_client_profile", @@ -1179,6 +1225,7 @@ func receive_server_hello(data: Dictionary) -> void: if ( typeof(data.get("accepted")) != TYPE_BOOL or typeof(data.get("protocol_version")) != TYPE_INT + or typeof(data.get("game_version")) != TYPE_STRING or typeof(data.get("rejection_code")) != TYPE_INT ): _teardown_peer() @@ -1191,6 +1238,16 @@ func receive_server_hello(data: Dictionary) -> void: _teardown_peer() _fail(message) return + var version_rejection: NetworkProtocol.RejectionCode = ( + NetworkProtocol.game_version_rejection( + NetworkProtocol.game_version(), + str(data["game_version"]), + ) + ) + if version_rejection != NetworkProtocol.RejectionCode.NONE: + _teardown_peer() + _fail(NetworkProtocol.rejection_text(version_rejection)) + return if int(data["protocol_version"]) != NetworkProtocol.PROTOCOL_VERSION: _teardown_peer() _fail("The server uses a different network protocol.") @@ -1457,6 +1514,8 @@ func _verify_spawn_identity(entry: Dictionary) -> bool: func _identity_proof_fields(data: Dictionary) -> Array: return [ NetworkProtocol.PROTOCOL_VERSION, + str(data.get("client_game_version", "")), + str(data.get("server_game_version", "")), str(data.get("attempt_id", "")), str(data.get("client_fingerprint", "")), str(data.get("client_nonce", "")), @@ -1497,6 +1556,10 @@ func _valid_server_proof_shape(data: Variant) -> bool: and str(value["server_nonce"]).length() == 64 and NetworkIdentityCrypto.valid_fingerprint(value.get("client_fingerprint")) and NetworkIdentityCrypto.valid_fingerprint(value.get("server_fingerprint")) + and typeof(value.get("client_game_version")) == TYPE_STRING + and str(value["client_game_version"]) == NetworkProtocol.game_version() + and typeof(value.get("server_game_version")) == TYPE_STRING + and str(value["server_game_version"]) == NetworkProtocol.game_version() and typeof(value.get("server_public_key")) == TYPE_STRING and str(value["server_public_key"]).to_utf8_buffer().size() <= NetworkProtocol.MAX_PUBLIC_KEY_LENGTH diff --git a/player/player.gd b/player/player.gd index b9b4133..733be49 100644 --- a/player/player.gd +++ b/player/player.gd @@ -84,6 +84,8 @@ enum PocketVisualTarget { CATCH_SHOWCASE, } const FIGHTING_EYES_ID: String = "alligator_eyes" +const CHARACTER_CALL_MOUTH_ID: String = "open_ah" +const CHARACTER_CALL_MOUTH_DURATION_SECONDS: float = 0.16 const BASE_REEL_SPEED: float = 0.16 # The target Android handheld exposes its physical right trigger through # Godot's left-trigger axis. Keep the role named here so the platform mapping @@ -158,6 +160,20 @@ func set_fighting_visual(active: bool) -> void: _update_character_animation() +func play_character_call_visual() -> void: + _character_call_visual_generation += 1 + var generation: int = _character_call_visual_generation + _character_call_mouth_active = true + _apply_presented_appearance() + await get_tree().create_timer( + CHARACTER_CALL_MOUTH_DURATION_SECONDS + ).timeout + if generation != _character_call_visual_generation: + return + _character_call_mouth_active = false + _apply_presented_appearance() + + func set_fishing_visual(active: bool) -> void: if ( active @@ -223,6 +239,10 @@ func _apply_presented_appearance() -> void: if _fighting_visual_active: presented_appearance = appearance_snapshot.duplicate(true) presented_appearance["eyes"] = FIGHTING_EYES_ID + if _character_call_mouth_active: + if presented_appearance == appearance_snapshot: + presented_appearance = appearance_snapshot.duplicate(true) + presented_appearance["mouth"] = CHARACTER_CALL_MOUTH_ID PlayerVisualPresenter.apply_appearance(_visuals, presented_appearance) @@ -382,6 +402,8 @@ var _pocket_visual_midpoint_callback: Callable var _pocket_visual_midpoint_called: bool = false var _pocket_visual_generation: int = 0 var _fighting_visual_active: bool = false +var _character_call_mouth_active: bool = false +var _character_call_visual_generation: int = 0 var _fishing_visual_active: bool = false var _fishing_visual_phase: FishingVisualPhase = FishingVisualPhase.NONE var _fishing_after_release_pending: bool = false diff --git a/tests/dedicated_server_config_validation.gd b/tests/dedicated_server_config_validation.gd index 888746f..7911f6d 100644 --- a/tests/dedicated_server_config_validation.gd +++ b/tests/dedicated_server_config_validation.gd @@ -46,6 +46,10 @@ func _run() -> void: assert(not invalid.is_valid()) var discovery := DiscoveryClient.new() + assert( + str(discovery.call("_default_room_name", "River")) + == "River's Server" + ) assert( DiscoveryClient.UPNP_RENEW_INTERVAL_SECONDS < float(DiscoveryClient.UPNP_MAPPING_DURATION_SECONDS) @@ -62,7 +66,12 @@ func _run() -> void: discovery.get_public_join_state() == DiscoveryClient.PublicJoinState.IDLE ) - assert(bool(discovery.call("_valid_public_room", { + discovery.set("_lease_room_id", "local-room") + assert(discovery.is_own_room({"room_id": "local-room"})) + assert(not discovery.is_own_room({"room_id": "another-room"})) + discovery.set("_lease_room_id", "") + assert(not discovery.is_own_room({"room_id": "local-room"})) + var listed_room := { "room_id": "empty-dedicated-room", "room_name": "Empty Dedicated Room", "address": "203.0.113.10", @@ -73,7 +82,31 @@ func _run() -> void: "application/config/version", "unknown" )), "protocol_version": NetworkProtocol.PROTOCOL_VERSION, - }))) + } + assert(bool(discovery.call("_valid_public_room", listed_room))) + var incompatible_room: Dictionary = listed_room.duplicate(true) + incompatible_room["game_version"] = "0.0.0-alpha" + assert(not bool(discovery.call("_valid_public_room", incompatible_room))) + assert( + NetworkProtocol.game_version_rejection( + "0.6.4-alpha", "0.6.5-alpha" + ) == NetworkProtocol.RejectionCode.CLIENT_OUTDATED + ) + assert( + NetworkProtocol.game_version_rejection( + "0.6.6-alpha", "0.6.5-alpha" + ) == NetworkProtocol.RejectionCode.SERVER_OUTDATED + ) + assert( + NetworkProtocol.game_version_rejection( + "0.6.5-alpha", "0.6.5-alpha" + ) == NetworkProtocol.RejectionCode.NONE + ) + assert( + NetworkProtocol.game_version_rejection( + "0.6.5-beta", "0.6.5-alpha" + ) == NetworkProtocol.RejectionCode.SERVER_OUTDATED + ) discovery.free() print("DEDICATED_SERVER_CONFIG_VALIDATION_OK") quit() diff --git a/tests/economy_regression_validation.gd b/tests/economy_regression_validation.gd index 9b40e74..f7d0d12 100644 --- a/tests/economy_regression_validation.gd +++ b/tests/economy_regression_validation.gd @@ -552,9 +552,11 @@ func _test_fishing_shop_sale_ui( var shop_panel_style := shop_panel.get_theme_stylebox("panel") as StyleBoxFlat assert(shop_panel_style != null) assert(shop_panel_style.corner_radius_top_left == 24) - var buy_mode := shop.get_node("%BuyModeButton") as Button - var sell_mode := shop.get_node("%SellModeButton") as Button - assert(buy_mode.visible and sell_mode.visible) + assert(not shop.has_node("ShopPanel/Margin/Layout/ModeTabs")) + var shop_tabs: Array = shop.get("_shop_tabs") as Array + assert(shop_tabs.size() == 6) + var sell_mode := shop_tabs[5] as Button + assert(sell_mode != null and sell_mode.text == "Sell Fish") var stock_sections: Array[String] = [] for child: Node in shop.get_node("%SuppliesList").get_children(): if child is Label: @@ -605,8 +607,7 @@ func _test_fishing_shop_sale_ui( assert(player.wallet.get_balance() == balance_before + fish_catch.sale_value) assert(not sale_service.is_local_sale_pending()) assert(reservations.release(reservation_id)) - var back_button := shop.get_node("%BackToShopButton") as Button - await _activate_focused_button(back_button, ui_viewport) + await _activate_focused_button(shop_tabs[0] as Button, ui_viewport) await process_frame assert(shop.visible and (shop.get_node("%ShopPanel") as Control).visible) assert(not (shop.get_node("%ShopCoolerPage") as Control).visible) diff --git a/tests/exported_decal_hotfix_validation.gd b/tests/exported_decal_hotfix_validation.gd index be70325..fe279b0 100644 --- a/tests/exported_decal_hotfix_validation.gd +++ b/tests/exported_decal_hotfix_validation.gd @@ -1,5 +1,11 @@ extends SceneTree +const FEATURE_ASSET_ROOTS: Dictionary = { + "eyes": "res://art/exported/characters/faces/eyes", + "mouth": "res://art/exported/characters/faces/mouth", + "nose": "res://art/exported/characters/faces/noses", +} + func _initialize() -> void: call_deferred("_run") @@ -27,10 +33,10 @@ func _run() -> void: ).is_empty() ) - var expected_counts := {"eyes": 22, "mouth": 17, "nose": 17} - for category_id: String in expected_counts: + for category_id: String in FEATURE_ASSET_ROOTS: var options := CharacterCustomizationCatalog.options_for(category_id) - assert(options.size() == int(expected_counts[category_id]) + 1) + var expected_ids: Dictionary = _feature_ids_on_disk(category_id) + assert(options.size() == expected_ids.size() + 1) var seen_ids: Dictionary = {} for option: Dictionary in options: var option_id := str(option.get("id", "")) @@ -42,6 +48,30 @@ func _run() -> void: category_id, option_id ) != null ) + for expected_id: String in expected_ids: + assert(seen_ids.has(expected_id)) + for mouth_id: String in ["open_ah", "open_oh", "open_smile"]: + assert(CharacterCustomizationCatalog.texture_for( + "mouth", mouth_id + ) != null) print("Exported decal hotfix validation: PASS") quit() + + +func _feature_ids_on_disk(category_id: String) -> Dictionary: + var result: Dictionary = {} + var root_path: String = str(FEATURE_ASSET_ROOTS[category_id]) + var directory := DirAccess.open(root_path) + assert(directory != null) + for file_name: String in directory.get_files(): + if not file_name.to_lower().ends_with(".png"): + continue + var option_id: String = ( + CharacterCustomizationCatalog._feature_id_from_filename( + category_id, file_name + ) + ) + if not option_id.is_empty(): + result[option_id] = true + return result diff --git a/tests/job_system_validation.gd b/tests/job_system_validation.gd index 24cad9d..6061a31 100644 --- a/tests/job_system_validation.gd +++ b/tests/job_system_validation.gd @@ -166,6 +166,9 @@ func _run() -> void: var session_summary := ( join_page.get_node("%SessionSummary") as Label ) + assert(not join_page.has_node( + "Paper/Margin/Layout/Actions/OpenCloseButton" + )) assert("UDP 18138" in session_summary.text) assert(session.set_host_open(true)) await process_frame diff --git a/ui/chat_ui.gd b/ui/chat_ui.gd index 34923ec..f50f6e6 100644 --- a/ui/chat_ui.gd +++ b/ui/chat_ui.gd @@ -7,7 +7,7 @@ const SPEECH_SECONDS: float = 6.0 const DRAFT_SAVE_DELAY: float = 0.4 const IDLE_ALPHA: float = 0.58 const CHAT_SURFACE_COLOR := Color(0.025, 0.13, 0.19, 0.94) -const PANEL_WIDTH: float = 270.0 +const PANEL_WIDTH: float = 290.0 const COMPACT_HEIGHT: float = 250.0 const BOTTOM_MARGIN: float = 12.0 const EXPANDED_TOP_MARGIN: float = 94.0 @@ -31,9 +31,10 @@ const MOBILE_EXPANDED_WIDTH: float = 820.0 const MOBILE_COMPACT_HEIGHT: float = 220.0 const MOBILE_EXPANDED_HEIGHT: float = 390.0 const MOBILE_EDGE_MARGIN: float = 12.0 -const CLOCK_SIZE := Vector2(116.0, 34.0) +const CLOCK_SIZE := Vector2(174.0, 51.0) +const CLOCK_FONT_SIZE: int = 27 const CLOCK_EDGE_MARGIN: float = 10.0 -const WEATHER_ICON_SIZE := Vector2(34.0, 34.0) +const WEATHER_ICON_SIZE := Vector2(51.0, 51.0) const WEATHER_ICON_GAP: float = 6.0 const FISH_FINDER_EFFECT_ICON_SIZE := Vector2(18.0, 18.0) const FISH_FINDER_EFFECT_ICON: Texture2D = preload( @@ -383,7 +384,7 @@ func _build_ui() -> void: _clock_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _clock_label.mouse_filter = Control.MOUSE_FILTER_IGNORE _clock_label.add_theme_font_override("font", UtilityPageStyle.TuffyFont) - _clock_label.add_theme_font_size_override("font_size", 18) + _clock_label.add_theme_font_size_override("font_size", CLOCK_FONT_SIZE) _clock_label.add_theme_color_override( "font_color", UtilityPageStyle.OCEAN_TEXT_PRIMARY ) diff --git a/ui/fishing_shop.gd b/ui/fishing_shop.gd index 8e776bb..9f93cf8 100644 --- a/ui/fishing_shop.gd +++ b/ui/fishing_shop.gd @@ -26,8 +26,13 @@ const ShopInteractionType = preload( ) const UtilityPageStyleType = preload("res://ui/utility_page_style.gd") const OrganizerTabType = preload("res://ui/components/organizer_tab.gd") +const UIMotionType = preload("res://ui/ui_motion.gd") +const FALLBACK_SUPPLY_ICON: Texture2D = preload( + "res://ui/icons/pictograms/x_light.png" +) signal menu_visibility_changed(is_open: bool) +signal menu_exit_started signal sell_fish_requested signal shop_cooler_return_requested signal shop_cooler_confirmation_cancel_requested @@ -46,6 +51,7 @@ enum ShopSection { SNACKS, EQUIPMENT, ART_SUPPLIES, + SELL_FISH, } const SHOP_SECTION_LABELS: Array[String] = [ @@ -54,15 +60,23 @@ const SHOP_SECTION_LABELS: Array[String] = [ "Snacks", "Equipment", "Art Supplies", + "Sell Fish", ] +const SUPPLY_ICON_GRID_COLUMNS: int = 9 +const SUPPLY_ICON_TILE_SIZE := Vector2(72.0, 72.0) +const SUPPLY_ICON_HOST_SIZE := Vector2(72.0, 84.0) +const SUPPLY_ICON_GRID_SEPARATION: int = 8 +const SUPPLY_PRICE_COLOR := Color("c3dfe6") +const SUPPLY_PRICE_FONT_SIZE: int = 15 +const SUPPLY_PRICE_Y: float = 60.0 +const SUPPLY_PRICE_HEIGHT: float = 24.0 +const SUPPLY_PRICE_HORIZONTAL_PADDING: float = 12.0 @onready var _wallet_label: Label = %WalletLabel @onready var _shop_panel: PanelContainer = %ShopPanel @onready var _shop_cooler_page: Control = %ShopCoolerPage @onready var _shop_cooler_mount: Control = %ShopCoolerMount -@onready var _back_to_shop_button: Button = %BackToShopButton -@onready var _buy_mode_button: Button = %BuyModeButton -@onready var _sell_mode_button: Button = %SellModeButton +@onready var _shop_body: HBoxContainer = $ShopPanel/Margin/Layout/Body @onready var _feedback: Label = %Feedback @onready var _shop_tab_bar: HBoxContainer = %ShopTabBar @onready var _upgrades_content: VBoxContainer = %Upgrades @@ -100,6 +114,8 @@ var _snapshot_stored: bool = false var _mouse_snapshot_stored: bool = false var _transaction_in_progress: bool = false var _closing: bool = false +var _close_generation: int = 0 +var _close_tween: Tween var _cooler_page_active: bool = false var _cooler_modal_open: bool = false var _shop_section: ShopSection = ShopSection.UPGRADES @@ -111,46 +127,57 @@ func _ready() -> void: _apply_shop_styles() _build_shop_tabs() %CloseButton.pressed.connect(close_shop) - _buy_mode_button.pressed.connect(_focus_buy_page) - _sell_mode_button.pressed.connect(_request_shop_cooler) - _back_to_shop_button.pressed.connect(shop_cooler_return_requested.emit) _reel_purchase.pressed.connect(_purchase_reel_speed) _barrier_purchase.pressed.connect(_purchase_barrier_power) _cooler_purchase.pressed.connect(_purchase_cooler_capacity) -func _request_shop_cooler() -> void: +func _request_shop_cooler() -> bool: if _transaction_in_progress: _feedback.text = "Finish the current transaction first." - return + return false if not _is_transaction_context_valid(): _feedback.text = "The fishing shop is no longer available." - return + return false sell_fish_requested.emit() + return _cooler_page_active -func _focus_buy_page() -> void: +func _focus_shop_section() -> void: _feedback.text = "" if _shop_section == ShopSection.UPGRADES: _reel_purchase.grab_focus() return - for child: Node in _supplies_list.get_children(): + if _shop_section == ShopSection.SELL_FISH: + _shop_tabs[int(ShopSection.SELL_FISH)].grab_focus() + return + for child: Node in _supplies_list.find_children( + "*", "Button", true, false + ): var stock_button := child as Button if stock_button != null and not stock_button.disabled: stock_button.grab_focus() return - _buy_mode_button.grab_focus() + var section_index: int = int(_shop_section) + if section_index >= 0 and section_index < _shop_tabs.size(): + _shop_tabs[section_index].grab_focus() func _build_shop_tabs() -> void: _shop_tab_bar.custom_minimum_size.y = 44.0 _shop_tab_bar.show() for section_index: int in range(ShopSection.size()): + if section_index == ShopSection.SELL_FISH: + var tab_group_spacer := Control.new() + tab_group_spacer.name = "SellTabSpacer" + tab_group_spacer.size_flags_horizontal = Control.SIZE_EXPAND_FILL + tab_group_spacer.mouse_filter = Control.MOUSE_FILTER_IGNORE + _shop_tab_bar.add_child(tab_group_spacer) var tab: OrganizerTab = OrganizerTabType.new() tab.text = SHOP_SECTION_LABELS[section_index] tab.palette_index = section_index tab.custom_minimum_size = Vector2(132.0, 42.0) - tab.size_flags_horizontal = Control.SIZE_EXPAND_FILL + tab.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN tab.focus_mode = Control.FOCUS_ALL tab.mouse_filter = Control.MOUSE_FILTER_STOP tab.pressed.connect(_select_shop_section.bind(section_index, true)) @@ -161,15 +188,38 @@ func _build_shop_tabs() -> void: func _select_shop_section(section_index: int, focus_content: bool) -> void: + if _closing: + return + if section_index == int(_shop_section): + _update_shop_tab_selection() + if focus_content: + _focus_shop_section() + return + if section_index == ShopSection.SELL_FISH: + if not _request_shop_cooler(): + _update_shop_tab_selection() + return + _shop_section = ShopSection.SELL_FISH + _update_shop_tab_selection() + return + if _cooler_page_active: + shop_cooler_return_requested.emit() _shop_section = section_index as ShopSection var showing_upgrades: bool = _shop_section == ShopSection.UPGRADES _upgrades_content.visible = showing_upgrades _supplies_content.visible = not showing_upgrades - for tab_index: int in range(_shop_tabs.size()): - _shop_tabs[tab_index].set_selected(tab_index == section_index, true) + _update_shop_tab_selection() _refresh_supplies() if focus_content and visible: - _focus_buy_page() + _focus_shop_section() + + +func _update_shop_tab_selection() -> void: + for tab_index: int in range(_shop_tabs.size()): + _shop_tabs[tab_index].set_selected( + tab_index == int(_shop_section), + true, + ) func _apply_shop_styles() -> void: @@ -193,20 +243,11 @@ func _apply_shop_styles() -> void: ) for button: BaseButton in [ %CloseButton, - _buy_mode_button, - _sell_mode_button, _reel_purchase, _barrier_purchase, _cooler_purchase, - _back_to_shop_button, ]: UtilityPageStyleType.apply_ocean_button(button) - _buy_mode_button.add_theme_stylebox_override( - "normal", - UtilityPageStyleType.ocean_button_style( - UtilityPageStyleType.OCEAN_SELECTED - ), - ) _feedback.add_theme_color_override( "font_color", UtilityPageStyleType.OCEAN_TEXT_SECONDARY ) @@ -276,7 +317,10 @@ func open_shop() -> bool: or _buyer.id != MAIN_SHOP_BUYER_ID ): return false + _cancel_close_tween() + _close_generation += 1 _closing = false + modulate.a = 1.0 _transaction_in_progress = ( _network_shop != null and _network_shop.is_local_purchase_pending() @@ -296,7 +340,7 @@ func open_shop() -> bool: _shop_tab_bar.show() _select_shop_section(ShopSection.UPGRADES, false) _refresh_all() - _buy_mode_button.grab_focus() + _reel_purchase.grab_focus() menu_visibility_changed.emit(true) return true @@ -304,11 +348,13 @@ func open_shop() -> bool: func consume_escape() -> bool: if not visible: return false + if _closing: + return true if _cooler_page_active: if _cooler_modal_open: shop_cooler_confirmation_cancel_requested.emit() else: - shop_cooler_return_requested.emit() + _select_shop_section(ShopSection.UPGRADES, true) return true close_shop() return true @@ -319,11 +365,11 @@ func get_shop_cooler_mount() -> Control: func activate_shop_cooler_page() -> void: - _shop_tab_bar.hide() _cooler_page_active = true - _shop_panel.hide() + _shop_panel.show() + _shop_body.hide() + _feedback.hide() _shop_cooler_page.show() - _back_to_shop_button.disabled = false func deactivate_shop_cooler_page() -> void: @@ -331,16 +377,18 @@ func deactivate_shop_cooler_page() -> void: _shop_tab_bar.show() _cooler_page_active = false _cooler_modal_open = false - _back_to_shop_button.disabled = false + for tab: OrganizerTab in _shop_tabs: + tab.disabled = false _shop_cooler_page.hide() _shop_panel.show() - if visible: - _buy_mode_button.grab_focus() + _shop_body.show() + _feedback.show() func set_shop_cooler_modal_open(is_open: bool) -> void: _cooler_modal_open = is_open - _back_to_shop_button.disabled = is_open + for tab: OrganizerTab in _shop_tabs: + tab.disabled = is_open func close_shop( @@ -349,12 +397,47 @@ func close_shop( ) -> void: if not visible: return + if _closing: + if reason in [ + CloseReason.WATER_RECOVERY, + CloseReason.SESSION_END, + CloseReason.TEARDOWN, + ]: + _finish_close(reason, restore_controls, _close_generation) + return _closing = true _transaction_in_progress = false - hide() var current_viewport: Viewport = get_viewport() if current_viewport != null: current_viewport.gui_release_focus() + menu_exit_started.emit() + _close_generation += 1 + var generation: int = _close_generation + if reason in [CloseReason.USER, CloseReason.RANGE_EXIT]: + _close_tween = create_tween() + _close_tween.tween_property( + self, + "modulate:a", + 0.0, + UIMotionType.PLAYER_MENU_EXIT_DURATION, + ).set_trans(Tween.TRANS_QUAD).set_ease(Tween.EASE_IN) + _close_tween.finished.connect( + _finish_close.bind(reason, restore_controls, generation) + ) + return + _finish_close(reason, restore_controls, generation) + + +func _finish_close( + reason: CloseReason, + restore_controls: bool, + generation: int, +) -> void: + if generation != _close_generation or not visible: + return + _cancel_close_tween() + hide() + modulate.a = 1.0 if _fishing_spot != null and is_instance_valid(_fishing_spot): _fishing_spot.set_local_menu_input_suppressed(INPUT_OWNER, false) if ( @@ -372,6 +455,13 @@ func close_shop( deactivate_shop_cooler_page() +func _cancel_close_tween() -> void: + if _close_tween == null: + return + _close_tween.kill() + _close_tween = null + + func close_for_range_exit() -> void: close_shop(CloseReason.RANGE_EXIT) @@ -404,9 +494,9 @@ func _refresh_all() -> void: func _refresh_wallet() -> void: _wallet_label.text = ( - "wallet: $%d" % _wallet.get_balance() + str(_wallet.get_balance()) if _wallet != null - else "wallet: $0" + else "0" ) @@ -475,7 +565,7 @@ func _refresh_supplies() -> void: for child: Node in _supplies_list.get_children(): _supplies_list.remove_child(child) child.queue_free() - if _shop_section == ShopSection.UPGRADES: + if _shop_section in [ShopSection.UPGRADES, ShopSection.SELL_FISH]: return _stock_title.text = SHOP_SECTION_LABELS[int(_shop_section)] var stock_item_ids: Array[StringName] = ( @@ -493,6 +583,9 @@ func _refresh_supplies() -> void: grouped_item_ids.append(item_id) stock_item_ids = grouped_item_ids var current_stock_group: StringName = StringName() + var current_stock_grid: GridContainer + if _shop_section in [ShopSection.SNACKS, ShopSection.EQUIPMENT]: + current_stock_grid = _add_stock_icon_grid() for item_id: StringName in stock_item_ids: var item: ItemDataType = _item_catalog.get_item_by_id(item_id) if item == null or not _item_belongs_in_current_section(item): @@ -503,10 +596,11 @@ func _refresh_supplies() -> void: ) if stock_group != current_stock_group: _add_stock_section(str(stock_group)) + current_stock_grid = _add_stock_icon_grid() current_stock_group = stock_group var button := Button.new() button.custom_minimum_size = Vector2(195, 54) - button.icon = item.icon + button.icon = item.icon if item.icon != null else FALLBACK_SUPPLY_ICON button.expand_icon = true button.alignment = HORIZONTAL_ALIGNMENT_LEFT var owned: int = _bag.get_quantity(item_id) @@ -520,6 +614,9 @@ func _refresh_supplies() -> void: var total_cost: int = FishingShopStockType.get_purchase_cost( item_id, quantity, bait_unlocked ) + var use_icon_tile: bool = ( + current_stock_grid != null + ) button.text = "%s\n$%d • owned %d" % [ item.display_name, FishingShopStockType.get_price(item_id), @@ -527,8 +624,8 @@ func _refresh_supplies() -> void: ] var item_tooltip_text: String = item.description if bait_topoff: - if item.icon != null: - button.custom_minimum_size = Vector2(72, 72) + if use_icon_tile: + button.custom_minimum_size = SUPPLY_ICON_TILE_SIZE button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN button.alignment = HORIZONTAL_ALIGNMENT_CENTER button.text = "" @@ -560,6 +657,17 @@ func _refresh_supplies() -> void: item.description, ] ) + elif use_icon_tile: + button.custom_minimum_size = SUPPLY_ICON_TILE_SIZE + button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + button.alignment = HORIZONTAL_ALIGNMENT_CENTER + button.text = "" + item_tooltip_text = "%s\n$%d • owned %d\n%s" % [ + item.display_name, + FishingShopStockType.get_price(item_id), + owned, + item.description, + ] button.disabled = ( _transaction_in_progress or _closing @@ -577,19 +685,30 @@ func _refresh_supplies() -> void: ) UtilityPageStyleType.apply_ocean_button(button) button.pressed.connect(_purchase_supply.bind(item_id)) - _supplies_list.add_child(button) + if current_stock_grid != null: + _add_supply_icon_tile( + current_stock_grid, + button, + FishingShopStockType.get_price(item_id), + ) + else: + _supplies_list.add_child(button) if _shop_section == ShopSection.ART_SUPPLIES: _add_stock_section("art kit") - _add_art_kit_button() + var art_kit_grid := _add_stock_icon_grid() + _add_art_kit_button(art_kit_grid) _add_stock_section("markers") + var marker_grid := _add_stock_icon_grid() for product_id: StringName in ArtShopStockType.MARKER_PRODUCTS: - _add_art_upgrade_button(product_id) + _add_art_upgrade_button(product_id, marker_grid) _add_stock_section("brushes") + var brush_grid := _add_stock_icon_grid() for product_id: StringName in ArtShopStockType.BRUSH_PRODUCTS: - _add_art_upgrade_button(product_id) + _add_art_upgrade_button(product_id, brush_grid) _add_stock_section("grids") + var canvas_grid := _add_stock_icon_grid() for product_id: StringName in ArtShopStockType.GRID_PRODUCTS: - _add_art_upgrade_button(product_id) + _add_art_upgrade_button(product_id, canvas_grid) func _item_belongs_in_current_section(item: ItemDataType) -> bool: @@ -613,7 +732,21 @@ func _add_stock_section(title: String) -> void: _supplies_list.add_child(label) -func _add_art_kit_button() -> void: +func _add_stock_icon_grid() -> GridContainer: + var grid := GridContainer.new() + grid.columns = SUPPLY_ICON_GRID_COLUMNS + grid.size_flags_horizontal = Control.SIZE_EXPAND_FILL + grid.add_theme_constant_override( + "h_separation", SUPPLY_ICON_GRID_SEPARATION + ) + grid.add_theme_constant_override( + "v_separation", SUPPLY_ICON_GRID_SEPARATION + ) + _supplies_list.add_child(grid) + return grid + + +func _add_art_kit_button(parent: GridContainer) -> void: var item: ItemDataType = _item_catalog.get_item_by_id( ArtShopStockType.ART_KIT_ITEM_ID ) @@ -626,10 +759,19 @@ func _add_art_kit_button() -> void: ArtShopStockType.ART_KIT_PRICE, "owned" if owned else "not owned", ], - item.description, + "%s\n$%d • %s\n%s" % [ + item.display_name, + ArtShopStockType.ART_KIT_PRICE, + "owned" if owned else "not owned", + item.description, + ], + ) + _configure_supply_icon_tile(button, item.icon) + _add_supply_icon_tile( + parent, + button, + ArtShopStockType.ART_KIT_PRICE, ) - button.icon = item.icon - button.expand_icon = true button.disabled = ( owned or _transaction_in_progress @@ -642,7 +784,10 @@ func _add_art_kit_button() -> void: button.pressed.connect(_purchase_art_kit) -func _add_art_upgrade_button(product_id: StringName) -> void: +func _add_art_upgrade_button( + product_id: StringName, + parent: GridContainer, +) -> void: var kit_owned: bool = ( _bag.get_quantity(ArtShopStockType.ART_KIT_ITEM_ID) > 0 ) @@ -653,7 +798,18 @@ func _add_art_upgrade_button(product_id: StringName) -> void: ArtShopStockType.UPGRADE_PRICE, "unlocked" if unlocked else "locked", ], - ArtShopStockType.get_description(product_id), + "%s\n$%d • %s\n%s" % [ + ArtShopStockType.get_display_name(product_id), + ArtShopStockType.UPGRADE_PRICE, + "unlocked" if unlocked else "locked", + ArtShopStockType.get_description(product_id), + ], + ) + _configure_supply_icon_tile(button, FALLBACK_SUPPLY_ICON) + _add_supply_icon_tile( + parent, + button, + ArtShopStockType.UPGRADE_PRICE, ) button.disabled = ( not kit_owned @@ -667,17 +823,92 @@ func _add_art_upgrade_button(product_id: StringName) -> void: button.pressed.connect(_purchase_art_upgrade.bind(product_id)) -func _make_stock_button(button_text: String, tooltip: String) -> Button: +func _make_stock_button( + button_text: String, + tooltip: String, +) -> Button: var button := Button.new() button.custom_minimum_size = Vector2(195, 54) button.alignment = HORIZONTAL_ALIGNMENT_LEFT button.text = button_text button.tooltip_text = tooltip UtilityPageStyleType.apply_ocean_button(button) - _supplies_list.add_child(button) return button +func _configure_supply_icon_tile( + button: Button, + icon: Texture2D, +) -> void: + button.custom_minimum_size = SUPPLY_ICON_TILE_SIZE + button.size_flags_horizontal = Control.SIZE_SHRINK_BEGIN + button.icon = icon if icon != null else FALLBACK_SUPPLY_ICON + button.expand_icon = true + button.alignment = HORIZONTAL_ALIGNMENT_CENTER + button.text = "" + + +func _add_supply_icon_tile( + parent: GridContainer, + button: Button, + price: int, +) -> void: + var tile_host := Control.new() + tile_host.custom_minimum_size = SUPPLY_ICON_HOST_SIZE + tile_host.mouse_filter = Control.MOUSE_FILTER_IGNORE + parent.add_child(tile_host) + button.position = Vector2.ZERO + button.size = SUPPLY_ICON_TILE_SIZE + tile_host.add_child(button) + var price_text := "$%d" % price + var price_text_width: float = UtilityPageStyleType.TuffyFont.get_string_size( + price_text, + HORIZONTAL_ALIGNMENT_LEFT, + -1.0, + SUPPLY_PRICE_FONT_SIZE, + ).x + var price_width: float = minf( + SUPPLY_ICON_TILE_SIZE.x, + ceilf(price_text_width + SUPPLY_PRICE_HORIZONTAL_PADDING), + ) + var price_bubble := PanelContainer.new() + price_bubble.name = "PriceBubble" + price_bubble.position = Vector2( + (SUPPLY_ICON_TILE_SIZE.x - price_width) * 0.5, + SUPPLY_PRICE_Y, + ) + price_bubble.size = Vector2(price_width, SUPPLY_PRICE_HEIGHT) + price_bubble.mouse_filter = Control.MOUSE_FILTER_IGNORE + price_bubble.z_index = 2 + var price_style := UtilityPageStyleType.rounded_style( + UtilityPageStyleType.OCEAN_FIELD, + 12, + ) + price_style.content_margin_left = 6.0 + price_style.content_margin_right = 6.0 + price_style.content_margin_top = 0.0 + price_style.content_margin_bottom = 0.0 + price_bubble.add_theme_stylebox_override("panel", price_style) + tile_host.add_child(price_bubble) + var price_label := Label.new() + price_label.name = "Price" + price_label.mouse_filter = Control.MOUSE_FILTER_IGNORE + price_label.text = price_text + price_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + price_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + price_label.add_theme_font_override( + "font", UtilityPageStyleType.TuffyFont + ) + price_label.add_theme_font_size_override( + "font_size", SUPPLY_PRICE_FONT_SIZE + ) + price_label.add_theme_color_override( + "font_color", SUPPLY_PRICE_COLOR + ) + price_label.add_theme_constant_override("outline_size", 0) + price_bubble.add_child(price_label) + + func _refresh_cooler_capacity() -> void: if _cooler_capacity == null: return diff --git a/ui/fishing_shop.tscn b/ui/fishing_shop.tscn index 58d2961..1fd5314 100644 --- a/ui/fishing_shop.tscn +++ b/ui/fishing_shop.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=7 format=3] +[gd_scene load_steps=8 format=3] [ext_resource type="Script" path="res://ui/fishing_shop.gd" id="1_script"] [ext_resource type="Theme" path="res://ui/game_theme.tres" id="2_theme"] @@ -6,6 +6,7 @@ [ext_resource type="Texture2D" path="res://items/icons/shop/64_power_plus.png" id="4_barrier"] [ext_resource type="Texture2D" path="res://items/icons/placeholder/fish_coin.svg" id="5_coin"] [ext_resource type="Texture2D" path="res://items/icons/equipment/64_cooler_plus.png" id="6_cooler"] +[ext_resource type="Texture2D" path="res://ui/icons/pictograms/x_light.png" id="7_close"] [node name="FishingShop" type="Control"] unique_name_in_owner = true @@ -34,7 +35,7 @@ mouse_filter = 0 [node name="ShopCoolerPage" type="Control" parent="."] unique_name_in_owner = true visible = false -z_index = 90 +z_index = 102 layout_mode = 1 anchors_preset = 15 anchor_right = 1.0 @@ -53,18 +54,8 @@ grow_horizontal = 2 grow_vertical = 2 mouse_filter = 1 -[node name="BackToShopButton" type="Button" parent="ShopCoolerPage"] -unique_name_in_owner = true -z_index = 110 -layout_mode = 0 -offset_left = 1052.0 -offset_top = 54.0 -offset_right = 1218.0 -offset_bottom = 104.0 -text = "back to shop" - [node name="ShopPanel" type="PanelContainer" parent="."] -z_index = 2 +z_index = 101 unique_name_in_owner = true layout_mode = 1 anchors_preset = 8 @@ -101,41 +92,39 @@ text = "fishing shop" theme_override_font_sizes/font_size = 32 theme_override_colors/font_color = Color(0.208, 0.725, 0.78, 1) -[node name="CoinIcon" type="TextureRect" parent="ShopPanel/Margin/Layout/Header"] -custom_minimum_size = Vector2(24, 24) +[node name="BalanceDisplay" type="HBoxContainer" parent="ShopPanel/Margin/Layout/Header"] +custom_minimum_size = Vector2(0, 48) layout_mode = 2 +size_flags_vertical = 4 +theme_override_constants/separation = 6 +alignment = 1 + +[node name="CoinIcon" type="TextureRect" parent="ShopPanel/Margin/Layout/Header/BalanceDisplay"] +custom_minimum_size = Vector2(26, 26) +layout_mode = 2 +size_flags_vertical = 4 texture = ExtResource("5_coin") expand_mode = 1 stretch_mode = 5 texture_filter = 1 -[node name="WalletLabel" type="Label" parent="ShopPanel/Margin/Layout/Header"] +[node name="WalletLabel" type="Label" parent="ShopPanel/Margin/Layout/Header/BalanceDisplay"] unique_name_in_owner = true +custom_minimum_size = Vector2(0, 32) layout_mode = 2 -text = "wallet: $0" +size_flags_vertical = 4 +text = "0" +vertical_alignment = 1 [node name="CloseButton" type="Button" parent="ShopPanel/Margin/Layout/Header"] unique_name_in_owner = true +custom_minimum_size = Vector2(48, 48) layout_mode = 2 -text = "close" -custom_minimum_size = Vector2(104, 48) - -[node name="ModeTabs" type="HBoxContainer" parent="ShopPanel/Margin/Layout"] -layout_mode = 2 -alignment = 1 -theme_override_constants/separation = 10 - -[node name="BuyModeButton" type="Button" parent="ShopPanel/Margin/Layout/ModeTabs"] -unique_name_in_owner = true -custom_minimum_size = Vector2(180, 42) -layout_mode = 2 -text = "buy gear & supplies" - -[node name="SellModeButton" type="Button" parent="ShopPanel/Margin/Layout/ModeTabs"] -unique_name_in_owner = true -custom_minimum_size = Vector2(180, 42) -layout_mode = 2 -text = "sell fish" +tooltip_text = "Close shop" +flat = true +icon = ExtResource("7_close") +expand_icon = true +icon_max_width = 40 [node name="Feedback" type="Label" parent="ShopPanel/Margin/Layout"] unique_name_in_owner = true @@ -146,10 +135,10 @@ theme_override_colors/font_color = Color(1, 0.82, 0.4, 1) [node name="ShopTabBar" type="HBoxContainer" parent="."] unique_name_in_owner = true -z_index = 1 -offset_left = 298.0 +z_index = 100 +offset_left = 180.0 offset_top = 26.0 -offset_right = 982.0 +offset_right = 1100.0 offset_bottom = 70.0 alignment = 1 theme_override_constants/separation = 6 diff --git a/ui/game_theme.tres b/ui/game_theme.tres index 7da4650..e21f90b 100644 --- a/ui/game_theme.tres +++ b/ui/game_theme.tres @@ -144,6 +144,17 @@ corner_radius_top_right = 10 corner_radius_bottom_right = 10 corner_radius_bottom_left = 10 +[sub_resource type="StyleBoxFlat" id="TooltipPanel"] +content_margin_left = 12.0 +content_margin_top = 8.0 +content_margin_right = 12.0 +content_margin_bottom = 8.0 +bg_color = Color(0.031, 0.122, 0.169, 1) +corner_radius_top_left = 10 +corner_radius_top_right = 10 +corner_radius_bottom_right = 10 +corner_radius_bottom_left = 10 + [resource] default_font = ExtResource("1_font") default_font_size = 23 @@ -175,3 +186,6 @@ OptionButton/styles/hover = SubResource("ButtonHover") OptionButton/styles/normal = SubResource("ButtonNormal") OptionButton/styles/pressed = SubResource("ButtonPressed") PanelContainer/styles/panel = SubResource("Panel") +TooltipLabel/colors/font_color = Color(0.765, 0.875, 0.902, 1) +TooltipLabel/font_sizes/font_size = 18 +TooltipPanel/styles/panel = SubResource("TooltipPanel") diff --git a/ui/game_ui.gd b/ui/game_ui.gd index a9db30d..67fb162 100644 --- a/ui/game_ui.gd +++ b/ui/game_ui.gd @@ -402,6 +402,7 @@ func setup( network_shop_service, ) _fishing_shop.menu_visibility_changed.connect(_on_shop_visibility_changed) + _fishing_shop.menu_exit_started.connect(_on_shop_exit_started) _fishing_shop.sell_fish_requested.connect(_on_shop_sell_fish_requested) _fishing_shop.shop_cooler_return_requested.connect( _on_shop_cooler_return_requested @@ -528,8 +529,11 @@ func _on_character_call_received( if _spawn_service == null: return var avatar: PlayerType = _spawn_service.get_avatar(peer_id) + if avatar == null: + return + avatar.play_character_call_visual() var audio_path: String = VoiceProfilesType.call_audio_path(call_id) - if avatar == null or not ResourceLoader.exists(audio_path, "AudioStream"): + if not ResourceLoader.exists(audio_path, "AudioStream"): return var stream: AudioStream = load(audio_path) as AudioStream if stream == null: @@ -2088,11 +2092,18 @@ func _on_player_menu_exit_started() -> void: player_menu_backdrop_visibility_changed.emit(false) +func _on_shop_exit_started() -> void: + if not _shop_open: + return + shop_backdrop_visibility_changed.emit(false) + + func _on_shop_visibility_changed(is_open: bool) -> void: _shop_open = is_open if is_open and _surface_drawing != null: _surface_drawing.deactivate() - shop_backdrop_visibility_changed.emit(is_open) + if is_open: + shop_backdrop_visibility_changed.emit(true) if not is_open and _player_menu.is_shop_cooler_mounted(): _player_menu.unmount_shop_cooler() _refresh_chat_availability() diff --git a/ui/hotbar.gd b/ui/hotbar.gd index ef7c505..fe26cc6 100644 --- a/ui/hotbar.gd +++ b/ui/hotbar.gd @@ -17,7 +17,7 @@ const FishingSpotType = preload("res://fishing/fishing_spot.gd") const DESKTOP_REFERENCE_SIZE := Vector2(1280.0, 720.0) const COMPACT_REFERENCE_SIZE := Vector2(640.0, 480.0) -const HOTBAR_PRESENTATION_SCALE: float = 1.00 +const HOTBAR_PRESENTATION_SCALE: float = 0.80 const HOTBAR_CANONICAL_POSITION := Vector2.ZERO const HOTBAR_MENU_POSITION := Vector2(-136.0, -90.0) const HOTBAR_GAMEPLAY_Z_INDEX: int = 35 @@ -215,9 +215,7 @@ func set_player_menu_context(enabled: bool) -> void: return _player_menu_context = enabled z_index = HOTBAR_MENU_Z_INDEX if enabled else HOTBAR_GAMEPLAY_Z_INDEX - _presentation_scale_root.position = ( - HOTBAR_MENU_POSITION if enabled else HOTBAR_CANONICAL_POSITION - ) + _presentation_scale_root.position = _presentation_position() func set_presentation_visible( @@ -363,11 +361,7 @@ func _apply_layout() -> void: _presentation_scale_root.scale = ( Vector2.ONE * HOTBAR_PRESENTATION_SCALE ) - _presentation_scale_root.position = ( - HOTBAR_MENU_POSITION - if _player_menu_context - else HOTBAR_CANONICAL_POSITION - ) + _presentation_scale_root.position = _presentation_position() var field_size := ( Vector2(580.0, 82.0) if _compact_layout @@ -382,6 +376,21 @@ func _apply_layout() -> void: slot.apply_layout(_compact_layout) +func _presentation_position() -> Vector2: + var scaled_size: Vector2 = ( + DESKTOP_REFERENCE_SIZE * HOTBAR_PRESENTATION_SCALE + ) + var bottom_centered_offset := Vector2( + (DESKTOP_REFERENCE_SIZE.x - scaled_size.x) * 0.5, + DESKTOP_REFERENCE_SIZE.y - scaled_size.y, + ) + return bottom_centered_offset + ( + HOTBAR_MENU_POSITION + if _player_menu_context + else HOTBAR_CANONICAL_POSITION + ) + + func _refresh() -> void: for slot: BubbleHotbarSlotType in _slots: slot.refresh() diff --git a/ui/network/join_game_page.gd b/ui/network/join_game_page.gd index b0b3d6d..1788ea5 100644 --- a/ui/network/join_game_page.gd +++ b/ui/network/join_game_page.gd @@ -42,7 +42,6 @@ const DIRECT_WORKFLOW_HELP: String = ( @onready var _delete_button: Button = %DeleteButton @onready var _cancel_button: Button = %CancelButton @onready var _back_button: Button = %BackButton -@onready var _open_close_button: Button = %OpenCloseButton @onready var _status: Label = %Status @onready var _session_summary: Label = %SessionSummary @onready var _delete_confirmation: BubbleConfirmationPage = ( @@ -75,7 +74,7 @@ func _ready() -> void: _discover_button, _direct_button, _saved_button, _recent_button, _refresh_button, _join_button, _save_button, _edit_button, _favorite_button, _delete_button, - _cancel_button, _back_button, _open_close_button, + _cancel_button, _back_button, ]: UtilityPageStyle.apply_ocean_button(button) UtilityPageStyle.apply_ocean_line_edit(_address) @@ -92,7 +91,6 @@ func _ready() -> void: _delete_button.pressed.connect(_on_delete_pressed) _cancel_button.pressed.connect(_on_cancel_pressed) _back_button.pressed.connect(_on_back_pressed) - _open_close_button.pressed.connect(_on_open_close_pressed) _address.text_submitted.connect(func(_value: String) -> void: if _name_entry_active: _name_edit.grab_focus() @@ -235,6 +233,9 @@ func _request_join() -> void: var endpoint_text: String = _address.text if _mode == Mode.DISCOVER: var room: Dictionary = _selected_discovery_room() + if _discovery != null and _discovery.is_own_room(room): + _set_status("You are already hosting this room.", true) + return if _discovery_room_is_full(room): _set_status("That room is full.", true) return @@ -441,12 +442,16 @@ func _refresh_entries() -> void: for room: Dictionary in _discovery_rooms: var player_count: int = int(room.get("current_players", 0)) var maximum: int = int(room.get("max_players", 0)) + var own_room: bool = ( + _discovery != null and _discovery.is_own_room(room) + ) _server_list.add_item( - "%s — %d / %d players%s" % [ + "%s — %d / %d players%s%s" % [ str(room.get("room_name", "Public room")), player_count, maximum, " — full" if player_count >= maximum else "", + " — your room" if own_room else "", ] ) return @@ -517,6 +522,12 @@ func _refresh() -> void: and selected and _discovery_room_is_full(_selected_discovery_room()) ) + or ( + discovery_mode + and selected + and _discovery != null + and _discovery.is_own_room(_selected_discovery_room()) + ) ) _join_button.text = "join\nnow" if direct else "join" _refresh_button.visible = discovery_mode and not _name_entry_active @@ -541,15 +552,6 @@ func _refresh() -> void: ) _delete_button.text = "remove" if _mode == Mode.RECENT else "delete" _cancel_button.visible = connecting - _open_close_button.visible = ( - _gameplay_context and _network_session.is_host() - ) - if _open_close_button.visible: - _open_close_button.text = ( - "close\ngame" - if _network_session.is_open_host() - else "open\ngame" - ) _session_summary.visible = ( _gameplay_context and _network_session.state != NetworkSession.State.INACTIVE @@ -647,7 +649,7 @@ func _format_entry_details(entry: SavedServerEntry) -> String: func _format_discovery_details(room: Dictionary) -> String: if room.is_empty(): return "Select a public room." - return "\n".join([ + var lines: Array[String] = [ "Room: %s" % str(room.get("room_name", "Public room")), "Players: %d / %d" % [ int(room.get("current_players", 0)), @@ -657,7 +659,10 @@ func _format_discovery_details(room: Dictionary) -> String: _discovery.room_endpoint(room) if _discovery != null else "—" ), "Direct UDP connection • ping is measured after joining", - ]) + ] + if _discovery != null and _discovery.is_own_room(room): + lines.append("This is the room you are currently hosting.") + return "\n".join(lines) func _selected_discovery_room() -> Dictionary: @@ -785,13 +790,6 @@ func _on_back_pressed() -> void: back_requested.emit() -func _on_open_close_pressed() -> void: - if _network_session == null or not _network_session.is_host(): - return - _network_session.set_host_open(not _network_session.is_open_host()) - _refresh() - - func _on_state_changed(_state: NetworkSession.State) -> void: _refresh() diff --git a/ui/network/join_game_page.tscn b/ui/network/join_game_page.tscn index 062a1f6..c4be4fa 100644 --- a/ui/network/join_game_page.tscn +++ b/ui/network/join_game_page.tscn @@ -308,13 +308,6 @@ custom_minimum_size = Vector2(82, 68) layout_mode = 2 text = "delete" -[node name="OpenCloseButton" type="Button" parent="Paper/Margin/Layout/Actions"] -unique_name_in_owner = true -visible = false -custom_minimum_size = Vector2(88, 72) -layout_mode = 2 -text = "open\ngame" - [node name="CancelButton" type="Button" parent="Paper/Margin/Layout/Actions"] unique_name_in_owner = true visible = false diff --git a/ui/players_page.gd b/ui/players_page.gd index 49cc40b..8c93247 100644 --- a/ui/players_page.gd +++ b/ui/players_page.gd @@ -1,6 +1,8 @@ class_name PlayersPage extends Control +const TOGGLE_STATE_COLOR := Color("c3dfe6") + var _service: NetworkPlayerListService var _discovery: DiscoveryClient var _count_label: Label @@ -9,7 +11,10 @@ var _list: VBoxContainer var _status: Label var _host_settings_panel: PanelContainer var _room_name_edit: LineEdit -var _discoverable_toggle: CheckButton +var _online_toggle: Button +var _online_state_label: Label +var _discoverable_toggle: Button +var _discoverable_state_label: Label var _host_discovery_status: Label var _current_tab := 0 @@ -111,21 +116,26 @@ func _build_host_settings(root: VBoxContainer) -> void: _room_name_edit = LineEdit.new() _room_name_edit.custom_minimum_size = Vector2(300.0, 42.0) _room_name_edit.max_length = DiscoveryClient.MAX_ROOM_NAME_LENGTH - _room_name_edit.placeholder_text = DiscoveryClient.DEFAULT_ROOM_NAME + _room_name_edit.placeholder_text = "Player Name's Server" UtilityPageStyle.apply_ocean_line_edit(_room_name_edit) _room_name_edit.text_submitted.connect( func(_value: String) -> void: _commit_room_name() ) _room_name_edit.focus_exited.connect(_commit_room_name) row.add_child(_room_name_edit) - _discoverable_toggle = CheckButton.new() - _discoverable_toggle.text = "list publicly" - _discoverable_toggle.toggled.connect(_on_discoverable_toggled) - UtilityPageStyle.apply_ocean_button(_discoverable_toggle) - row.add_child(_discoverable_toggle) + _online_toggle = _build_host_toggle("online", row) + _online_state_label = _online_toggle.get_node("StateBadge/State") as Label + _online_toggle.pressed.connect(_on_online_pressed) + _discoverable_toggle = _build_host_toggle("discovery", row) + _discoverable_state_label = ( + _discoverable_toggle.get_node("StateBadge/State") as Label + ) + _discoverable_toggle.pressed.connect(_on_discoverable_pressed) _host_discovery_status = Label.new() _host_discovery_status.size_flags_horizontal = Control.SIZE_EXPAND_FILL _host_discovery_status.autowrap_mode = TextServer.AUTOWRAP_WORD_SMART + _host_discovery_status.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + _host_discovery_status.vertical_alignment = VERTICAL_ALIGNMENT_CENTER _host_discovery_status.add_theme_font_size_override("font_size", 14) _host_discovery_status.add_theme_color_override( "font_color", UtilityPageStyle.OCEAN_TEXT_SECONDARY @@ -133,6 +143,56 @@ func _build_host_settings(root: VBoxContainer) -> void: row.add_child(_host_discovery_status) +func _build_host_toggle(label_text: String, row: HBoxContainer) -> Button: + var button := Button.new() + button.custom_minimum_size = Vector2(150.0, 46.0) + button.text = label_text + button.alignment = HORIZONTAL_ALIGNMENT_CENTER + button.clip_contents = false + UtilityPageStyle.apply_ocean_button(button) + row.add_child(button) + var badge := PanelContainer.new() + badge.name = "StateBadge" + badge.mouse_filter = Control.MOUSE_FILTER_IGNORE + badge.z_index = 2 + button.add_child(badge) + badge.set_anchors_preset(Control.PRESET_CENTER_BOTTOM) + badge.offset_left = -39.0 + badge.offset_top = -12.0 + badge.offset_right = 39.0 + badge.offset_bottom = 12.0 + var state_label := Label.new() + state_label.name = "State" + state_label.mouse_filter = Control.MOUSE_FILTER_IGNORE + state_label.horizontal_alignment = HORIZONTAL_ALIGNMENT_CENTER + state_label.vertical_alignment = VERTICAL_ALIGNMENT_CENTER + state_label.add_theme_font_override("font", UtilityPageStyle.TuffyFont) + state_label.add_theme_font_size_override("font_size", 15) + state_label.add_theme_color_override("font_color", TOGGLE_STATE_COLOR) + badge.add_child(state_label) + return button + + +func _set_host_toggle_state( + button: Button, + state_label: Label, + state_text: String, + enabled: bool, +) -> void: + state_label.text = state_text + var badge := state_label.get_parent() as PanelContainer + badge.add_theme_stylebox_override( + "panel", + UtilityPageStyle.rounded_style( + UtilityPageStyle.OCEAN_SELECTED + if enabled + else UtilityPageStyle.OCEAN_FIELD, + 12, + ), + ) + button.queue_redraw() + + func _select_tab(index: int) -> void: if index == 2 and (_service == null or not _service.is_local_host()): return @@ -172,17 +232,38 @@ func _refresh_host_settings() -> void: return if not _room_name_edit.has_focus(): _room_name_edit.text = _discovery.get_room_name() - _discoverable_toggle.set_pressed_no_signal(_discovery.is_discoverable()) + var is_open: bool = _service.is_open_host() + var is_discoverable: bool = _discovery.is_discoverable() + _set_host_toggle_state( + _online_toggle, + _online_state_label, + "OPEN" if is_open else "CLOSED", + is_open, + ) + _set_host_toggle_state( + _discoverable_toggle, + _discoverable_state_label, + "ON" if is_discoverable else "OFF", + is_discoverable, + ) + _online_toggle.disabled = not _service.is_local_host() + _online_toggle.tooltip_text = ( + "Close this game to new connections." + if is_open + else "Open this game so other players can connect." + ) var can_publish: bool = ( _service.is_local_host() and _discovery.is_configured() - and _service.is_open_host() + and is_open ) _discoverable_toggle.disabled = not can_publish _discoverable_toggle.tooltip_text = ( - "Advertise this open game in the public room browser." + "Stop advertising this game in the public room browser." + if is_discoverable + else "Advertise this open game in the public room browser." if can_publish - else "Open the game before listing it publicly." + else "Open the game before enabling discovery." if _discovery.is_configured() else "Room discovery is not configured in this build." ) @@ -201,13 +282,18 @@ func _commit_room_name() -> void: _room_name_edit.text = _discovery.get_room_name() -func _on_discoverable_toggled(enabled: bool) -> void: +func _on_online_pressed() -> void: + if _service == null or not _service.is_local_host(): + return + _service.set_host_open(not _service.is_open_host()) + _refresh_host_settings() + + +func _on_discoverable_pressed() -> void: if _discovery == null: return - if not _discovery.set_discoverable(enabled): - _discoverable_toggle.set_pressed_no_signal( - _discovery.is_discoverable() - ) + _discovery.set_discoverable(not _discovery.is_discoverable()) + _refresh_host_settings() func _on_host_settings_changed( @@ -216,15 +302,25 @@ func _on_host_settings_changed( ) -> void: if _room_name_edit != null and not _room_name_edit.has_focus(): _room_name_edit.text = room_name - if _discoverable_toggle != null: - _discoverable_toggle.set_pressed_no_signal(discoverable) + if _discoverable_state_label != null: + _set_host_toggle_state( + _discoverable_toggle, + _discoverable_state_label, + "ON" if discoverable else "OFF", + discoverable, + ) _refresh_host_settings() func _on_host_status_changed(message: String, is_error: bool) -> void: if _host_discovery_status == null: return - _host_discovery_status.text = message + var tooltip_only: bool = message in [ + "Open the game before listing it publicly.", + "Open the game before enabling discovery.", + ] + _host_discovery_status.text = "" if tooltip_only else message + _host_discovery_status.visible = not _host_discovery_status.text.is_empty() _host_discovery_status.add_theme_color_override( "font_color", UtilityPageStyle.OCEAN_DANGER diff --git a/ui/weather_icon.gd b/ui/weather_icon.gd index dbe41b2..b44bc9b 100644 --- a/ui/weather_icon.gd +++ b/ui/weather_icon.gd @@ -25,7 +25,7 @@ func _ready() -> void: mouse_filter = Control.MOUSE_FILTER_PASS focus_mode = Control.FOCUS_NONE texture_filter = CanvasItem.TEXTURE_FILTER_NEAREST - custom_minimum_size = Vector2(34.0, 34.0) + custom_minimum_size = Vector2(51.0, 51.0) _update_tooltip() queue_redraw()