From b8a5e641b65f775c1a2f0e00f8d3bf166ab81ad2 Mon Sep 17 00:00:00 2001 From: HumanoidSandvichDispenser <25856867+HumanoidSandvichDispenser@users.noreply.github.com> Date: Tue, 4 Jun 2024 22:12:01 -0700 Subject: [PATCH] Implement Vendor NPCs (#37) * Add WIP vendors * Add TimerExtensions * chore: Update dialogue manager version * Add Inventory.EquippedItem signal * Move files to UI.Inventory namespace * Modify Hotbar and InventorySlot organization Made InventorySlot a bit more modular. * Modify hotbar icons * Implement request to display shop * Add forsenLevel placeholder icon * Add shop UI * Update UI to use theme and click through * Update item metadata Added placeholder icons for items without an icon. * Add Snus Dealer example vendor NPC * Implement factions with bitflags instead * Fix typo in Doc dialogue * Hide and show shop menu * Add shaders * Add newline to console output * Add modal interface --- Assets/Dialogue/books.dialogue.import | 2 +- Assets/Dialogue/clone-machine.dialogue.import | 2 +- Assets/Dialogue/doc.dialogue | 5 +- Assets/Dialogue/doc.dialogue.import | 2 +- Assets/Sprites/UI/hotbar-active.ase | Bin 0 -> 961 bytes Assets/Sprites/UI/hotbar-active.png | Bin 0 -> 189 bytes Assets/Sprites/UI/hotbar-active.png.import | 34 + Assets/Sprites/UI/hotbar-inactive.ase | Bin 896 -> 924 bytes Assets/Sprites/UI/hotbar-inactive.png | Bin 115 -> 153 bytes Assets/Sprites/UI/menu-rect-no-bg-white.ase | Bin 0 -> 2009 bytes Assets/Sprites/UI/menu-rect-no-bg-white.png | Bin 0 -> 832 bytes .../UI/menu-rect-no-bg-white.png.import | 34 + Assets/Sprites/forsenLevel.ase | Bin 0 -> 1071 bytes Assets/Sprites/forsenLevel.png | Bin 0 -> 579 bytes Assets/Sprites/forsenLevel.png.import | 34 + BoundingBoxes/BoundingBox.cs | 4 +- Characters/Character.cs | 4 +- Characters/Doc.tscn | 1 + Characters/NPC.cs | 32 + Characters/SnusDealer.tscn | 74 + Characters/Vendor.cs | 27 + Debug/DebugConsole.cs | 50 +- Dialogue/snus-dealer.dialogue | 23 + Dialogue/snus-dealer.dialogue.import | 15 + Entities/DynamicDoor.cs | 2 +- Entities/TorchLamp.tscn | 6 +- Events/EventBus.cs | 3 + Extensions/Timer.cs | 12 + Items/IItemCollection.cs | 15 + Items/Inventory.cs | 45 +- Items/Shop.cs | 45 + Items/ShopEntry.cs | 23 + Items/Shops/SnusDealer.tres | 54 + Items/Weapons/Bow.tres | 4 +- Items/Weapons/ProjectileSpawner.cs | 9 + Items/Weapons/Pugio.tres | 4 +- Items/Weapons/Shotgun.tres | 18 + Items/Weapons/Shotgun.tscn | 12 +- Scenes/Level.tscn | 4 +- Scenes/Maps/ForestNew.tscn | 19 +- Shaders/Wipe.gdshader | 6 +- Shaders/WipeXY.gdshader | 10 + State/Global/GlobalState.cs | 9 + State/Global/MapState.cs | 9 + State/Thinker/VendorIdle.cs | 17 + UI/Base.tscn | 72 +- UI/Hotbar.tscn | 25 - UI/IModal.cs | 6 + UI/{ => Inventory}/Hotbar.cs | 5 +- UI/Inventory/Hotbar.tscn | 30 + UI/Inventory/HotbarSlot.cs | 0 UI/Inventory/HotbarSlot.tscn | 16 + UI/Inventory/InventoryGrid.cs | 172 ++ UI/Inventory/InventoryGrid.tscn | 14 + UI/Inventory/InventorySlot.cs | 95 + UI/Inventory/InventorySlot.tscn | 41 + UI/Inventory/ItemTooltip.cs | 44 + UI/Inventory/ItemTooltip.tscn | 127 ++ UI/Inventory/ShopMenu.cs | 100 + UI/Inventory/ShopMenu.tscn | 217 ++ UI/Inventory/ShopSlot.tscn | 14 + UI/InventorySlot.cs | 74 - UI/InventorySlot.tscn | 39 - UI/LocationDisplay.tscn | 1 + UI/SceneTransition.tscn | 2 + UI/Themes/InventorySlotButtonFocus.tres | 14 + UI/Themes/InventorySlotButtonNormal.tres | 15 + UI/Themes/InventorySlotButtonPressed.tres | 15 + UI/Themes/Panel.tres | 14 + UI/Themes/supalidl.tres | 52 +- UI/UIController.cs | 14 + Utils/CharacterStats.cs | 4 +- Utils/IFaction.cs | 9 +- addons/dialogue_manager/DialogueManager.cs | 7 + .../dialogue_manager/components/code_edit.gd | 28 +- .../code_edit_syntax_highlighter.gd | 8 +- addons/dialogue_manager/components/parser.gd | 23 +- addons/dialogue_manager/dialogue_manager.gd | 1904 +++++++++-------- .../dialogue_reponses_menu.gd | 40 +- .../example_balloon/ExampleBalloon.cs | 15 + .../example_balloon/example_balloon.gd | 14 +- addons/dialogue_manager/import_plugin.gd | 2 +- addons/dialogue_manager/l10n/zh.po | 49 +- addons/dialogue_manager/l10n/zh_TW.po | 45 +- addons/dialogue_manager/plugin.cfg | 2 +- addons/dialogue_manager/plugin.gd | 74 + addons/dialogue_manager/settings.gd | 6 +- addons/dialogue_manager/utilities/builtins.gd | 3 +- addons/dialogue_manager/views/main_view.gd | 23 +- addons/panku_console | 2 +- project.godot | 13 +- 91 files changed, 2925 insertions(+), 1233 deletions(-) create mode 100644 Assets/Sprites/UI/hotbar-active.ase create mode 100644 Assets/Sprites/UI/hotbar-active.png create mode 100644 Assets/Sprites/UI/hotbar-active.png.import create mode 100644 Assets/Sprites/UI/menu-rect-no-bg-white.ase create mode 100644 Assets/Sprites/UI/menu-rect-no-bg-white.png create mode 100644 Assets/Sprites/UI/menu-rect-no-bg-white.png.import create mode 100644 Assets/Sprites/forsenLevel.ase create mode 100644 Assets/Sprites/forsenLevel.png create mode 100644 Assets/Sprites/forsenLevel.png.import create mode 100644 Characters/SnusDealer.tscn create mode 100644 Characters/Vendor.cs create mode 100644 Dialogue/snus-dealer.dialogue create mode 100644 Dialogue/snus-dealer.dialogue.import create mode 100644 Extensions/Timer.cs create mode 100644 Items/IItemCollection.cs create mode 100644 Items/Shop.cs create mode 100644 Items/ShopEntry.cs create mode 100644 Items/Shops/SnusDealer.tres create mode 100644 Items/Weapons/Shotgun.tres create mode 100644 Shaders/WipeXY.gdshader create mode 100644 State/Thinker/VendorIdle.cs delete mode 100644 UI/Hotbar.tscn create mode 100644 UI/IModal.cs rename UI/{ => Inventory}/Hotbar.cs (83%) create mode 100644 UI/Inventory/Hotbar.tscn create mode 100644 UI/Inventory/HotbarSlot.cs create mode 100644 UI/Inventory/HotbarSlot.tscn create mode 100644 UI/Inventory/InventoryGrid.cs create mode 100644 UI/Inventory/InventoryGrid.tscn create mode 100644 UI/Inventory/InventorySlot.cs create mode 100644 UI/Inventory/InventorySlot.tscn create mode 100644 UI/Inventory/ItemTooltip.cs create mode 100644 UI/Inventory/ItemTooltip.tscn create mode 100644 UI/Inventory/ShopMenu.cs create mode 100644 UI/Inventory/ShopMenu.tscn create mode 100644 UI/Inventory/ShopSlot.tscn delete mode 100644 UI/InventorySlot.cs delete mode 100644 UI/InventorySlot.tscn create mode 100644 UI/Themes/InventorySlotButtonFocus.tres create mode 100644 UI/Themes/InventorySlotButtonNormal.tres create mode 100644 UI/Themes/InventorySlotButtonPressed.tres create mode 100644 UI/Themes/Panel.tres diff --git a/Assets/Dialogue/books.dialogue.import b/Assets/Dialogue/books.dialogue.import index a2928b8..fd22018 100644 --- a/Assets/Dialogue/books.dialogue.import +++ b/Assets/Dialogue/books.dialogue.import @@ -1,6 +1,6 @@ [remap] -importer="dialogue_manager_compiler_11" +importer="dialogue_manager_compiler_12" type="Resource" uid="uid://dilmuoilweoeh" path="res://.godot/imported/books.dialogue-cc272ebae322ae3ca46820dca11a3437.tres" diff --git a/Assets/Dialogue/clone-machine.dialogue.import b/Assets/Dialogue/clone-machine.dialogue.import index 70159e2..629926e 100644 --- a/Assets/Dialogue/clone-machine.dialogue.import +++ b/Assets/Dialogue/clone-machine.dialogue.import @@ -1,6 +1,6 @@ [remap] -importer="dialogue_manager_compiler_11" +importer="dialogue_manager_compiler_12" type="Resource" uid="uid://c2om4y0fm81yr" path="res://.godot/imported/clone-machine.dialogue-8810934a67eacdad52469e9ef5f970fb.tres" diff --git a/Assets/Dialogue/doc.dialogue b/Assets/Dialogue/doc.dialogue index 2452e7d..5417f16 100644 --- a/Assets/Dialogue/doc.dialogue +++ b/Assets/Dialogue/doc.dialogue @@ -18,11 +18,10 @@ Doc, The Two Time: Nothing but success. Doc, The Two Time: That's what my life's about. Period. Doc, The Two Time: And the arena today, ladies and gentlemen... Doc, The Two Time: is wide open, and the crowds are flooding in. VIP seating. Skybox section. Reserved for the Slick Daddy Club. -Doc, The Two Time: The Slick Daddy Club lofoking so damn good today. I'm feeling so damn good. It's obvious. -Doc, The Two Time: The V of success. +Doc, The Two Time: The Slick Daddy Club looking so damn good today. I'm feeling so damn good. It's obvious. Challenge [b]Doc, The Two Time[/b] to a duel? - Yes do emit("SummonBoss", "Doc") -- No => END +- No, I'm a little chubby cheek wannabe punk kid. => END => END diff --git a/Assets/Dialogue/doc.dialogue.import b/Assets/Dialogue/doc.dialogue.import index 68f302f..491bd4e 100644 --- a/Assets/Dialogue/doc.dialogue.import +++ b/Assets/Dialogue/doc.dialogue.import @@ -1,6 +1,6 @@ [remap] -importer="dialogue_manager_compiler_11" +importer="dialogue_manager_compiler_12" type="Resource" uid="uid://dntkvjjr8mrgf" path="res://.godot/imported/doc.dialogue-8f95f6a09d3ac685b71d7e07c49df1c6.tres" diff --git a/Assets/Sprites/UI/hotbar-active.ase b/Assets/Sprites/UI/hotbar-active.ase new file mode 100644 index 0000000000000000000000000000000000000000..76abceecdcc91a4970d671bee09fd268a944e013 GIT binary patch literal 961 zcmcJNT}YEr9EXo>W@I*+h04smT}T?au@I|4uBIhrDJvKnWLss0WKtBZ0zW8Q6lMg1 zE2%~Z6Y9p1#6oKj*hN=;=|+)t5k`Tn)`o$-JtxUQ_q{KBcE-D%|M@+?g*8G<^rs2s z&z&ZOIKV4)Ri>8zWwnXFLPx(8w;@_F75DPSmp)PwVG#HK0vUVMG9(2Pa z7usQgW(l-r=D_Ma`JL`U6CAW!;qacVu&tpUb{%Vm*E`zah~E##gF!eIjl$`89Of_7 zLbE~6wHL`f9qZ+u?&{UBrJ)RVoT`L3efwZ2D9>iTxO_(##K&e+TeoajuUXlQ7?4&^+Us=0$7qGGj1-n zL5EoyZm*MP_MLnN2X6mRUo%oGs7;neIW$8pbV4DtK^62s2{eFy+#??AD91OlF^y&% zBN)4=#Vb-VicVZ25{oFrAM!AVHk=_0Td2Ylk}!lG+#m)kD8UCZFo6afAb|8lCpV!< zOkA=Ol$1myAK^$wETc@N@aLSnh$}M0cK7MV0ws6@@b|zS8aT-Yuv%4(3{V>t@DZ`XBmss@ZF9=)V`IPrkR;p3QjvZqlW^vEM5# uYf=LetE@$rm9tOT0BtT>TA-v^O*W literal 0 HcmV?d00001 diff --git a/Assets/Sprites/UI/hotbar-active.png b/Assets/Sprites/UI/hotbar-active.png new file mode 100644 index 0000000000000000000000000000000000000000..678ef9f1859f4685d9dfcd02b5a0a5ad0e4c296a GIT binary patch literal 189 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=ffJ7Ec$)kcif|(>L-RP~dR6c(nEZ z{~$4S>%^$r5kWzPJEZ+T$Q24M>RjcqS}8Z|@mv3>3N!BFRZ43Oz88PhIe6{gp*eaF zy=JDKuO> z!4TOO(vv5)_rQw3i@){Xvd{YOv8{f=8#$gl37&uQtjyeQRllS&{)1(}N>YBaZ~D*6 J(EPr60s!N>F8BZd delta 89 zcmbQk-oVb-Fp;sIkzr%wHYQI`1_lOJ1t0+g|Ct~(1A_t(R?K;Ob|EhV0|$%YxB24D jO}Z7Az5U8}l^+V%u$zDW`RAWCV9NgQ6ZX6M4<-Nr3F#jw diff --git a/Assets/Sprites/UI/hotbar-inactive.png b/Assets/Sprites/UI/hotbar-inactive.png index 584cce5ab5584e316941da5319c4b7dd67112883..1df834cdded0c80ff823e7213741ecc26fd96e7a 100644 GIT binary patch delta 123 zcmV->0EGW@nE{X_bzn(EK~z|U?UykM05J#z<27mg|4X;A@~XhKLSV0$DTQQ*AO@qP zUITV!l+-&jzV%a^-nYgFE>coIuJ8i_2q1ufUBHj~FK}hUkDv+NFJPa92q1s}0(OD% dI(>URe*x0;KI`3Q`LX~2002ovPDHLkV1oZ`H8}tP delta 85 zcmbQqSUf?+-^$a)F(jh(?e&F>326Rnp))_miAelPzcGGFi>sZTgRy e0U4}#Tm6rr{0sly>?}t)kc6kJpUXO@geCw}bRB>I diff --git a/Assets/Sprites/UI/menu-rect-no-bg-white.ase b/Assets/Sprites/UI/menu-rect-no-bg-white.ase new file mode 100644 index 0000000000000000000000000000000000000000..8062cf4e23f14fa46921fd1a6d2a1d82d3a1a7fd GIT binary patch literal 2009 zcmcJPc~BE)7>73|n5sagYC#IkhAJM6hD#Jg0Raz`!{`(R1d?(or-(pAE=L9NptTl3 z0Z}VXQbjD$NQF=tRBWZ-Pz3`>5rueFumocgG5xJpW!jF@f80!-?9TV?_kO?U-Hn_{ z5Zz@of{zb}pb-QShVQ$>IEXWhg4E6{2Cd7h=wq}C0M~{NH zBoeT{y&Y65m7v*J254nU)peUrt;utw))XwC3C0J{0k@`jfQJu$30{y;dnjEt9+ZZd zf(@w_pgfQrTKa+91$@w% zw+Zx)%K-(&WuU1(8=PfKWn47F1>{*#hO^dCdp?-d2#Svk=~_{eK|MKC)6g7r25mu4 z&=7P3tw0~p1atuU;U3~)9m?SwvSAvU;TVEp7i!@ZQehN2;SwTY5enfC@?Z|y;0(fG z3##A=l3)mW;09t~1xnxpGGGE4-~a-U9?_8-p^+GIkrhFa5)qLP;gAfmNGcU)6!_*q zT!Kk>2{wmsi(^Nh21w`}eSJ#IXGZTx;w|2^GDC7~6*)D)$mx*mr{2#8T5cFrZn zV&|cJ*DMy3uB*4M=g_M4(|Fn! zwLVOEP=DY=u6`VJ>ZbKgklkxpwVTZlF=p_5Zj^PKTy*6-HAPLo)|5+<{fbBG z7f;R`xcrd6OdV@z6{vlo7QT?*kgwkPXXKUPUmRrp9W!~-XY$~|iKQyeo>U zo@cW?lFm5#By?R3Q}tB@rP#KG{AwF;EjDj|@myQEY{!8W?Kpx2|IdS^}8;j-_d z4-c?c?v>ZXH%JoM8ynN4hdiG6Zkg&QxU9Gt$#{P1`8~Nhcwh|iMR!R8Syj%omos(V z_T5tUjWF_*-KQG@Sy7d&RucnZTUJNviv8(ln$}&c9$gdtAy8k!uYI-f>l3HWF?}dS zUC@b6R!8Qd62&H4=fKjURoa_=lkeI+oV8sW9hTOl3B8kEvQOegt|$(f{M(@QqXUtr zeeZ=!v%}05H}CZF_C3X$y2UA@H|ThJeDbh=$ld!+(`t>wD^&`m)-Ua4#|Vn?lX-{C+hQ8brr^BTNV}A1R7U2Cn|1}M*UT7p|>N*ai_U*u@Pw`1Y$bq&kzJ6}a#D3who1aR2}S literal 0 HcmV?d00001 diff --git a/Assets/Sprites/UI/menu-rect-no-bg-white.png b/Assets/Sprites/UI/menu-rect-no-bg-white.png new file mode 100644 index 0000000000000000000000000000000000000000..d13149c77207425887f52b404295f36d29ef0517 GIT binary patch literal 832 zcmeAS@N?(olHy`uVBq!ia0vp^2_VeD1|%QND7Ro>U}pDpaSW-L^LDOd-eUtE*Qt&T zOpa!Ee=+R*Us=^hH1}%vpA`+kWY%eM@6^Ps?tI(-9BR z^LSak|E`#$&9TCF2U%`5i|?;{clKiL>R7LRhTVMCRr-fN{GZ8pirYwhN9}%=ME^h& zt95IgKS><_X~e^Am^*(9>+0pd|2;Q3+g~{6XlCWaIWdyTkN51{w%%l!>z6v#+ux3P z%)hp3$@8Q}-NKYfwTb5hqyzLHahq<8KDg9BJG83a>wi#yZ{D`~p6jz;@ttQ0b1JObiTlysMjx_KGLv^-h|XZoT8V)0wp!z0U_- zz3})@jc?h@ovYs|hHPAIr(UYEfBvmsbMkyEd&8ygR)3rQv7~bUT|ZM(3t*_Z?%{S7 z(_y_fJJNLH=^1xiql347(9zcrmH)Tnez@z)qlx+JOxG-r4>sNS+HhAxvDeosy$@MS zUhjHkl(u@o-nZJlnS$@xJ9Dd(m-x${nlo?q)12U6YbLIX-NkeG(UaF@>(1YvW0QZD zM>af@f1AqMx0}`PyjuBO?;S&>g4>J6Z!D+Nmpy;IFUTv)RQBnmc0c{;i5_9xchh-4 zeGw_^TwpKze%kd~Tk*DYqDg)$_vB|@UspOqVXwH+JKMSyPxQYYHoiP9ylN^iXE1oW L`njxgN@xNAPEmTA literal 0 HcmV?d00001 diff --git a/Assets/Sprites/UI/menu-rect-no-bg-white.png.import b/Assets/Sprites/UI/menu-rect-no-bg-white.png.import new file mode 100644 index 0000000..cdcd8be --- /dev/null +++ b/Assets/Sprites/UI/menu-rect-no-bg-white.png.import @@ -0,0 +1,34 @@ +[remap] + +importer="texture" +type="CompressedTexture2D" +uid="uid://uhmowtsi3wfh" +path="res://.godot/imported/menu-rect-no-bg-white.png-5ea2d275879af97991070fb370031211.ctex" +metadata={ +"vram_texture": false +} + +[deps] + +source_file="res://Assets/Sprites/UI/menu-rect-no-bg-white.png" +dest_files=["res://.godot/imported/menu-rect-no-bg-white.png-5ea2d275879af97991070fb370031211.ctex"] + +[params] + +compress/mode=0 +compress/high_quality=false +compress/lossy_quality=0.7 +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/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/Assets/Sprites/forsenLevel.ase b/Assets/Sprites/forsenLevel.ase new file mode 100644 index 0000000000000000000000000000000000000000..e424a53ef4ddb741f0fb48b0b71750fe2131b853 GIT binary patch literal 1071 zcmcJOYen$z3of@+dT3JO}UO=D^mfD%ju904D|q;dhG# zx-PbbD!G^&tQ31{w}?G;;Txbn(ii5Yg}}RIr{S{(afXWB%c1F%9Cl`|g9D{v`qwtC zU|%}4jEIQ|=R#rOjbpI1HXfQfvtjGN71;OjHvBUA6fOyNh07z{pjWyIYKqh_wq`#x zww-~}KC!bxUjV&J#BYOJp1|~hm$2sZbGTBz6MA??K<{l)P_0dYd(Rg_U3oQ>JGetH z7m;zt`t4Aw5{3;&#F@)dyI^hQ)NC&!ZN-x#N~0W_p%yx!5Za&$dY}XvKtJvgk9Cye z8`+pfGma6AUDVC zdZLq?&?F`W_ID+r}XbzW=c-*R#Z|i))^(vo7i}H6467R2fbt{+$b7(N^ot`9ZH(wwbh)-bOJioEEoCiATq SNfZ6$`XQ$@%fh;Gx%n4!IdR|s literal 0 HcmV?d00001 diff --git a/Assets/Sprites/forsenLevel.png b/Assets/Sprites/forsenLevel.png new file mode 100644 index 0000000000000000000000000000000000000000..baebf741eaa3b18f7ddc5b3a20d8feaad70b4da0 GIT binary patch literal 579 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM3?#3wJbMaAoeA&>aRt&G%zXU9BGU5mhUOMw zX^A;)4RXwaR@~y=lJfnkdYi1Q_QrbF$3^xQ=FV!Y-?wn#jm?|i-@5hl%NH3g7JX?x zCk^pHOZh}^o%}en#&VZ=Gn000ZkX>Sv@Krxc$MnSsa78j27b6+@#WFNtX#MD?x?9N z)3+V3+jq11`m-5tKJMUkk`W4%)veHX?z2l>;oo~OgTb#tA-`2`(hR4)TPtoJS@C+m zsI zbME}D{hR)3xX!#)o>L~%+|kyXu9IYzl^dhYzw5JzEBn=JQLQ$g1Qzu@`k*P8>LGST z+uyLu`7h%^8@(Ram}v=&UW^j)QOSD#C+-DG9^MfnFzHO+&O7Y)CR{N5>^5V&LQ>eT z?ZQQ7k9IT9Uo9NMf2ZyG%nP5?y{^nUt9`4^>HWg)1eTdMS*n + /// Finds the best character whose faction aligns with this character's. + /// + public virtual Character FindBestNeutral() + { + float bestScore = float.MaxValue; + Character bestChar = null; + // NOTE: this relies on all Characters being under the Entities node + foreach (Node node in GetParent().GetChildren()) + { + if (node is Character character) + { + bool isFriendly = ((IFaction)this).AlignsWith(character); + if (isFriendly || character.Health <= 0) + { + continue; + } + + float score = 0; + score -= Position.DistanceTo(character.Position); + + if (score < bestScore) + { + bestScore = score; + bestChar = character; + } + } + } + return bestChar; + } + public override void _Process(double delta) { ThinkerStateMachine.Process(delta); diff --git a/Characters/SnusDealer.tscn b/Characters/SnusDealer.tscn new file mode 100644 index 0000000..569eb06 --- /dev/null +++ b/Characters/SnusDealer.tscn @@ -0,0 +1,74 @@ +[gd_scene load_steps=11 format=3 uid="uid://rd08pot25h00"] + +[ext_resource type="Script" path="res://Characters/NPC.cs" id="1_04gcf"] +[ext_resource type="Script" path="res://State/Character/CharacterStateMachine.cs" id="2_kynkg"] +[ext_resource type="Texture2D" uid="uid://bej8thq7ruyty" path="res://Assets/Sprites/Characters/forsen2.png" id="2_s5nik"] +[ext_resource type="Script" path="res://State/Character/NPCIdleState.cs" id="3_pcrll"] +[ext_resource type="Script" path="res://State/Thinker/ThinkerStateMachine.cs" id="4_mo4wj"] +[ext_resource type="Script" path="res://State/Thinker/VendorIdle.cs" id="5_oau5d"] +[ext_resource type="PackedScene" uid="uid://dldnp8eunxj3q" path="res://BoundingBoxes/InteractionTrigger.tscn" id="5_sjs24"] +[ext_resource type="Script" path="res://Utils/InteractionTriggerDialogue.cs" id="5_yknpw"] +[ext_resource type="Resource" uid="uid://c4n7vhoxybu70" path="res://Dialogue/snus-dealer.dialogue" id="6_isvnq"] +[ext_resource type="Script" path="res://Items/Inventory.cs" id="7_vip6b"] + +[node name="SnusDealer" type="CharacterBody2D" node_paths=PackedStringArray("ThinkerStateMachine", "Sprite", "Inventory", "StateMachine")] +script = ExtResource("1_04gcf") +ThinkerStateMachine = NodePath("Thinker") +Sprite = NodePath("Sprites/Sprite") +Inventory = NodePath("Inventory") +StateMachine = NodePath("StateMachine") + +[node name="StateMachine" type="Node" parent="." node_paths=PackedStringArray("InitialState", "Character")] +script = ExtResource("2_kynkg") +InitialState = NodePath("Idle") +Character = NodePath("..") + +[node name="Idle" type="Node" parent="StateMachine" node_paths=PackedStringArray("Character")] +script = ExtResource("3_pcrll") +Character = NodePath("../..") + +[node name="Thinker" type="Node" parent="." node_paths=PackedStringArray("InitialState")] +script = ExtResource("4_mo4wj") +InitialState = NodePath("Idle") + +[node name="Idle" type="Node" parent="Thinker" node_paths=PackedStringArray("NPC")] +script = ExtResource("5_oau5d") +NPC = NodePath("../..") + +[node name="Animations" type="Node" parent="."] + +[node name="Movement" type="AnimationPlayer" parent="Animations"] + +[node name="Hurt" type="AnimationPlayer" parent="Animations"] + +[node name="Stun" type="AnimationPlayer" parent="Animations"] + +[node name="Attack" type="AnimationPlayer" parent="Animations"] + +[node name="Stats" type="Node" parent="."] + +[node name="Sprites" type="Node2D" parent="."] + +[node name="Sprite" type="Sprite2D" parent="Sprites"] +texture = ExtResource("2_s5nik") +centered = false +offset = Vector2(-12, -20) +hframes = 46 + +[node name="Inventory" type="Node2D" parent="." node_paths=PackedStringArray("Hotbar")] +script = ExtResource("7_vip6b") +Hotbar = [] + +[node name="Interaction" type="Node2D" parent="." node_paths=PackedStringArray("InteractionTrigger")] +position = Vector2(0, -4) +script = ExtResource("5_yknpw") +InteractionTrigger = NodePath("InteractionTrigger") +DialogueResource = ExtResource("6_isvnq") +DialogueTitle = "start" + +[node name="InteractionTrigger" parent="Interaction" instance=ExtResource("5_sjs24")] + +[node name="Label" parent="Interaction/InteractionTrigger/Popup" index="0"] +text = "Listen" + +[editable path="Interaction/InteractionTrigger"] diff --git a/Characters/Vendor.cs b/Characters/Vendor.cs new file mode 100644 index 0000000..8487415 --- /dev/null +++ b/Characters/Vendor.cs @@ -0,0 +1,27 @@ +//using Godot; +//using SupaLidlGame.BoundingBoxes; +//using SupaLidlGame.Extensions; +// +//namespace SupaLidlGame.Characters; +// +//public partial class Vendor : NPC +//{ +// [Export] +// public InteractionTrigger InteractionTrigger { get; set; } +// +// [Export(PropertyHint.File, "*.dialogue")] +// public Resource DialogueResource { get; set; } +// +// [Export] +// public string DialogueTitle { get; set; } +// +// public override void _Ready() +// { +// InteractionTrigger.Interaction += OnInteraction; +// } +// +// private void OnInteraction() +// { +// this.GetWorld().DialogueBalloon.Start(DialogueResource, DialogueTitle); +// } +//} diff --git a/Debug/DebugConsole.cs b/Debug/DebugConsole.cs index 580f6b0..2a47fc8 100644 --- a/Debug/DebugConsole.cs +++ b/Debug/DebugConsole.cs @@ -34,6 +34,8 @@ public sealed partial class DebugConsole : Control private Node ctx => Context; + public const double DEBUG_REFRESH_INTERVAL = 0.25; + public override void _Ready() { _entry = GetNode("%Entry"); @@ -61,6 +63,39 @@ public sealed partial class DebugConsole : Control } } }; + + GD.Print("DebugConsole init"); + // TODO: put this in a separate class + // watch godot.log + bool isFileLoggingEnabled = ProjectSettings + .GetSetting("debug/file_logging/enable_file_logging.pc") + .AsBool(); + + if (isFileLoggingEnabled) + { + GD.Print("File logging is enabled."); + string logPath = ProjectSettings + .GetSetting("debug/file_logging/log_path") + .AsString(); + var fs = FileAccess.Open(logPath, FileAccess.ModeFlags.Read); + + var timer = new Timer(); + AddChild(timer); + timer.Timeout += () => + { + // push + while (fs.GetPosition() < fs.GetLength()) + { + string line = fs.GetLine(); + _output.Text += line + "\n"; + } + }; + timer.Start(DEBUG_REFRESH_INTERVAL); + } + else + { + GD.PushWarning("File logging is not enabled."); + } } public IEnumerable SplitPath(NodePath path) @@ -209,13 +244,21 @@ public sealed partial class DebugConsole : Control Godot.Expression exp = new(); - string[] reserved = { "from", "set_context", "context", "set_prop", "to_node_path" }; + string[] reserved = { + "from", + "set_context", + "context", + "set_prop", + "to_node_path", + "load", + }; Godot.Collections.Array reservedMap = new(); reservedMap.Add(new Callable(this, MethodName.From)); reservedMap.Add(new Callable(this, MethodName.SetContext)); reservedMap.Add(Context); reservedMap.Add(new Callable(this, MethodName.SetProp)); reservedMap.Add(new Callable(this, MethodName.ToNodePath)); + reservedMap.Add(new Callable(this, MethodName.Load)); var err = exp.Parse(str, reserved); if (err != Error.Ok) @@ -246,4 +289,9 @@ public sealed partial class DebugConsole : Control { Context = node; } + + private Resource Load(string path) + { + return ResourceLoader.Load(path); + } } diff --git a/Dialogue/snus-dealer.dialogue b/Dialogue/snus-dealer.dialogue new file mode 100644 index 0000000..8572f45 --- /dev/null +++ b/Dialogue/snus-dealer.dialogue @@ -0,0 +1,23 @@ +~ start + +Snus Dealer: Hey kid, wanna buy some snus? +- Alright. => shop +- No, I don't think so. => END +- Snus? => dont_snus + +=> END + +~ dont_snus + +Snus Dealer: You know what they say. +Snus Dealer: If you don't snus... +Snus Dealer: you lose. +- Pepepains + +=> start + +~ shop + +do emit("EnterShop", "res://Items/Shops/SnusDealer.tres") + +=> END diff --git a/Dialogue/snus-dealer.dialogue.import b/Dialogue/snus-dealer.dialogue.import new file mode 100644 index 0000000..bc5cd9b --- /dev/null +++ b/Dialogue/snus-dealer.dialogue.import @@ -0,0 +1,15 @@ +[remap] + +importer="dialogue_manager_compiler_12" +type="Resource" +uid="uid://c4n7vhoxybu70" +path="res://.godot/imported/snus-dealer.dialogue-69dbddee28632f18888364bae03f393d.tres" + +[deps] + +source_file="res://Dialogue/snus-dealer.dialogue" +dest_files=["res://.godot/imported/snus-dealer.dialogue-69dbddee28632f18888364bae03f393d.tres"] + +[params] + +defaults=true diff --git a/Entities/DynamicDoor.cs b/Entities/DynamicDoor.cs index 7670eb8..38661e7 100644 --- a/Entities/DynamicDoor.cs +++ b/Entities/DynamicDoor.cs @@ -58,7 +58,7 @@ public partial class DynamicDoor : StaticBody2D foreach (var navmesh in Rebake) { // rebake navmesh so NPCs can correctly travel conditionally - GD.Print("rebaking"); + GD.Print("Dynamic door updated; rebaking navmeshes..."); navmesh.BakeNavigationPolygon(); } } diff --git a/Entities/TorchLamp.tscn b/Entities/TorchLamp.tscn index 11df0f7..d858e18 100644 --- a/Entities/TorchLamp.tscn +++ b/Entities/TorchLamp.tscn @@ -102,12 +102,12 @@ y_sort_enabled = true texture_filter = 1 sprite_frames = SubResource("SpriteFrames_gf7ku") autoplay = "default" -frame = 6 -frame_progress = 0.743234 +frame = 9 +frame_progress = 0.966501 offset = Vector2(0, -12) [node name="PointLight2D" type="PointLight2D" parent="."] -color = Color(1, 0.9525, 0.85, 1) +color = Color(1, 0.811765, 0.537255, 1) energy = 1.2 blend_mode = 2 shadow_filter_smooth = 3.0 diff --git a/Events/EventBus.cs b/Events/EventBus.cs index 9aa4885..e158812 100644 --- a/Events/EventBus.cs +++ b/Events/EventBus.cs @@ -51,6 +51,9 @@ public partial class EventBus : Node [Signal] public delegate void ExitTransitionEventHandler(); + [Signal] + public delegate void EnterShopEventHandler(string shopResourcePath); + public override void _Ready() { ProcessMode = ProcessModeEnum.Always; diff --git a/Extensions/Timer.cs b/Extensions/Timer.cs new file mode 100644 index 0000000..bac4566 --- /dev/null +++ b/Extensions/Timer.cs @@ -0,0 +1,12 @@ +using Godot; + +namespace SupaLidlGame.Extensions; + +public static class TimerExtensions +{ + public static void Restart(this Timer timer, double timeSec = -1) + { + timer.Stop(); + timer.Start(timeSec); + } +} diff --git a/Items/IItemCollection.cs b/Items/IItemCollection.cs new file mode 100644 index 0000000..c715fb9 --- /dev/null +++ b/Items/IItemCollection.cs @@ -0,0 +1,15 @@ +namespace SupaLidlGame.Items; + +public interface IItemCollection +{ + public System.Collections.Generic.IEnumerable GetItems(); + + public int Capacity { get; } +} + +public interface IItemCollection : IItemCollection +{ + public bool Add(T item); + + public bool Remove(T item); +} diff --git a/Items/Inventory.cs b/Items/Inventory.cs index 772d6e4..80f286c 100644 --- a/Items/Inventory.cs +++ b/Items/Inventory.cs @@ -4,7 +4,7 @@ using Godot.Collections; namespace SupaLidlGame.Items; -public partial class Inventory : Node2D +public partial class Inventory : Node2D, IItemCollection { public Character Character { get; private set; } @@ -20,7 +20,15 @@ public partial class Inventory : Node2D [Signal] public delegate void UsedItemEventHandler(Item item); - public const int MaxCapacity = 3; + [Signal] + public delegate void EquippedItemEventHandler(Item newItem, Item prevItem); + + [Signal] + public delegate void ItemAddedEventHandler(ItemMetadata newItemMetadata); + + public int Capacity { get; set; } = 30; + + public const int HotbarCapacity = 3; private Item _selectedItem; @@ -87,7 +95,8 @@ public partial class Inventory : Node2D return false; } - _selectedItem?.Unequip(Character); + Item prevItem = _selectedItem; + prevItem?.Unequip(Character); _selectedIndex = index; if (index >= 0) @@ -100,6 +109,8 @@ public partial class Inventory : Node2D _selectedItem = null; } + EmitSignal(SignalName.EquippedItem, prevItem, _selectedItem); + GD.Print($"Inventory: {index} is new selected index."); return true; @@ -135,8 +146,9 @@ public partial class Inventory : Node2D return null; } - public Item AddItemToHotbar(ItemMetadata metadata) + public Item AddToHotbar(ItemMetadata metadata) { + //AddItemMetadata(metadata); var item = metadata.Instance.Instantiate(); AddItem(item); AddChild(item); @@ -146,7 +158,7 @@ public partial class Inventory : Node2D public Item AddItem(Item item) { - if (Hotbar.Count >= MaxCapacity) + if (Hotbar.Count >= HotbarCapacity) { return null; } @@ -157,9 +169,32 @@ public partial class Inventory : Node2D { Hotbar.Add(item); } + return item; } + public System.Collections.Generic.IEnumerable GetItems() + { + return Items; + } + + public bool Add(ItemMetadata item) + { + if (Items.Count >= Capacity) + { + return false; + } + + Items.Add(item); + EmitSignal(SignalName.ItemAdded, item); + return true; + } + + public bool Remove(ItemMetadata item) + { + return Items.Remove(item); + } + public Item DropItem(Item item) { item.CharacterOwner = null; diff --git a/Items/Shop.cs b/Items/Shop.cs new file mode 100644 index 0000000..4ba249a --- /dev/null +++ b/Items/Shop.cs @@ -0,0 +1,45 @@ +using Godot; +using Godot.Collections; +using GodotUtilities.Collections; +using System.Linq; +using SupaLidlGame.Utils; + +namespace SupaLidlGame.Items; + +[GlobalClass] +public partial class Shop : Resource, IItemCollection +{ + [Export] + protected Godot.Collections.Array Entries { get; private set; } + + public System.Collections.Generic.IEnumerable GetItems() + { + var mapState = World.Instance.GlobalState.MapState; + return Entries + .Where( + (entry) => + { + var condition = entry.MapStateCondition; + if (string.IsNullOrEmpty(condition)) + { + return true; + } + return mapState.GetBoolean(condition) ?? false; + } + ) + .Select((entry) => entry.Item); + } + + public bool Add(ShopEntry entry) + { + Entries.Add(entry); + return true; + } + + public bool Remove(ShopEntry entry) + { + return Entries.Remove(entry); + } + + public int Capacity => Entries.Count; +} diff --git a/Items/ShopEntry.cs b/Items/ShopEntry.cs new file mode 100644 index 0000000..b5617ad --- /dev/null +++ b/Items/ShopEntry.cs @@ -0,0 +1,23 @@ +using Godot; + +namespace SupaLidlGame.Items; + +[GlobalClass] +public partial class ShopEntry : Resource +{ + public ShopEntry() : base() + { + + } + + public ShopEntry(ItemMetadata item) : base() + { + Item = item; + } + + [Export] + public ItemMetadata Item { get; set; } + + [Export] + public string MapStateCondition { get; set; } +} diff --git a/Items/Shops/SnusDealer.tres b/Items/Shops/SnusDealer.tres new file mode 100644 index 0000000..ed8d52d --- /dev/null +++ b/Items/Shops/SnusDealer.tres @@ -0,0 +1,54 @@ +[gd_resource type="Resource" script_class="Shop" load_steps=16 format=3 uid="uid://djqd88vdkoi6d"] + +[ext_resource type="Script" path="res://Items/Shop.cs" id="1_betbc"] +[ext_resource type="Resource" uid="uid://cjsh0dcgbfn77" path="res://Items/Weapons/Bow.tres" id="1_ntroj"] +[ext_resource type="Script" path="res://Items/ShopEntry.cs" id="2_xgvwu"] +[ext_resource type="Resource" uid="uid://iqe6rgnb3jur" path="res://Items/Weapons/Pugio.tres" id="3_nfeft"] +[ext_resource type="Resource" uid="uid://dkm216ug0vj2h" path="res://Items/Weapons/Shotgun.tres" id="4_aw0ju"] + +[sub_resource type="Resource" id="Resource_jdx0p"] +script = ExtResource("2_xgvwu") +Item = ExtResource("1_ntroj") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_min4b"] +script = ExtResource("2_xgvwu") +Item = ExtResource("3_nfeft") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_t0x08"] +script = ExtResource("2_xgvwu") +Item = ExtResource("4_aw0ju") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_exmab"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_jlgb3"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_8rd47"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_pqgh5"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_8mift"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_4e8it"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[sub_resource type="Resource" id="Resource_jp7ms"] +script = ExtResource("2_xgvwu") +MapStateCondition = "" + +[resource] +script = ExtResource("1_betbc") +Entries = Array[Object]([SubResource("Resource_jdx0p"), SubResource("Resource_min4b"), SubResource("Resource_t0x08"), SubResource("Resource_exmab"), SubResource("Resource_jlgb3"), SubResource("Resource_8rd47"), SubResource("Resource_pqgh5"), SubResource("Resource_8mift"), SubResource("Resource_4e8it"), SubResource("Resource_jp7ms")]) diff --git a/Items/Weapons/Bow.tres b/Items/Weapons/Bow.tres index 7e4edc9..5f2a8b1 100644 --- a/Items/Weapons/Bow.tres +++ b/Items/Weapons/Bow.tres @@ -1,5 +1,6 @@ -[gd_resource type="Resource" script_class="ItemMetadata" load_steps=4 format=3 uid="uid://cjsh0dcgbfn77"] +[gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://cjsh0dcgbfn77"] +[ext_resource type="Texture2D" uid="uid://vjdyrv8wp7gl" path="res://Assets/Sprites/forsenLevel.png" id="1_5blro"] [ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_haiji"] [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_hjbs0"] @@ -10,6 +11,7 @@ Path = "res://Items/Weapons/Bow.tscn" [resource] script = ExtResource("2_hjbs0") Instance = SubResource("Resource_mjj1w") +Icon = ExtResource("1_5blro") Name = "Bow" Description = "A bow and arrow." BuyPrice = 0 diff --git a/Items/Weapons/ProjectileSpawner.cs b/Items/Weapons/ProjectileSpawner.cs index 6362899..33d5ad2 100644 --- a/Items/Weapons/ProjectileSpawner.cs +++ b/Items/Weapons/ProjectileSpawner.cs @@ -24,6 +24,15 @@ public partial class ProjectileSpawner : Ranged [Export] public float ProjectileAngleDeviation { get; set; } + public string ProjectilePath + { + get => Projectile?.ResourcePath; + set + { + Projectile = GD.Load(value); + } + } + protected virtual void SpawnProjectile(Scenes.Map map, Vector2 direction, float velocityModifier = 1) { diff --git a/Items/Weapons/Pugio.tres b/Items/Weapons/Pugio.tres index eee0522..a141748 100644 --- a/Items/Weapons/Pugio.tres +++ b/Items/Weapons/Pugio.tres @@ -1,5 +1,6 @@ -[gd_resource type="Resource" script_class="ItemMetadata" load_steps=4 format=3 uid="uid://iqe6rgnb3jur"] +[gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://iqe6rgnb3jur"] +[ext_resource type="Texture2D" uid="uid://vjdyrv8wp7gl" path="res://Assets/Sprites/forsenLevel.png" id="1_3ptey"] [ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_o026a"] [ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="2_j4tmu"] @@ -10,6 +11,7 @@ Path = "res://Items/Weapons/Pugio.tscn" [resource] script = ExtResource("2_j4tmu") Instance = SubResource("Resource_abrg1") +Icon = ExtResource("1_3ptey") Name = "Pugio" Description = "A sidearm dagger." BuyPrice = 0 diff --git a/Items/Weapons/Shotgun.tres b/Items/Weapons/Shotgun.tres new file mode 100644 index 0000000..651fe91 --- /dev/null +++ b/Items/Weapons/Shotgun.tres @@ -0,0 +1,18 @@ +[gd_resource type="Resource" script_class="ItemMetadata" load_steps=5 format=3 uid="uid://dkm216ug0vj2h"] + +[ext_resource type="Script" path="res://Utils/ScenePath.cs" id="1_owc5r"] +[ext_resource type="Texture2D" uid="uid://vjdyrv8wp7gl" path="res://Assets/Sprites/forsenLevel.png" id="1_v76vk"] +[ext_resource type="Script" path="res://Items/ItemMetadata.cs" id="1_vtlr1"] + +[sub_resource type="Resource" id="Resource_6sxq7"] +script = ExtResource("1_owc5r") +Path = "res://Items/Weapons/Shotgun.tscn" + +[resource] +script = ExtResource("1_vtlr1") +Instance = SubResource("Resource_6sxq7") +Icon = ExtResource("1_v76vk") +Name = "Shotgun" +Description = "" +BuyPrice = 1887 +SellPrice = 0 diff --git a/Items/Weapons/Shotgun.tscn b/Items/Weapons/Shotgun.tscn index 1f84f6d..a7beaf0 100644 --- a/Items/Weapons/Shotgun.tscn +++ b/Items/Weapons/Shotgun.tscn @@ -1,9 +1,10 @@ -[gd_scene load_steps=26 format=3 uid="uid://d1d4vg7we5rjr"] +[gd_scene load_steps=27 format=3 uid="uid://d1d4vg7we5rjr"] [ext_resource type="Script" path="res://Items/Weapons/ProjectileSpawner.cs" id="1_4xyyt"] [ext_resource type="Script" path="res://State/Weapon/WeaponStateMachine.cs" id="2_ag6rd"] [ext_resource type="PackedScene" uid="uid://da1do2r2pbayb" path="res://Entities/ShotgunPellet.tscn" id="2_p3wx2"] [ext_resource type="Script" path="res://State/Weapon/RangedIdleState.cs" id="3_dd6bh"] +[ext_resource type="Resource" uid="uid://dkm216ug0vj2h" path="res://Items/Weapons/Shotgun.tres" id="3_ju036"] [ext_resource type="Script" path="res://State/Weapon/RangedFireState.cs" id="4_bwqd6"] [ext_resource type="Texture2D" uid="uid://b1omx2kdb2x1n" path="res://Assets/Sprites/Items/shotgun.png" id="5_g8d45"] [ext_resource type="Texture2D" uid="uid://c1a7lvb4uuwfy" path="res://Assets/Sprites/Particles/circle-16.png" id="6_va8ee"] @@ -216,11 +217,9 @@ curve = SubResource("Curve_mqgo6") [sub_resource type="ParticleProcessMaterial" id="ParticleProcessMaterial_uexo4"] particle_flag_disable_z = true flatness = 1.0 -gravity = Vector3(0, -4, 0) initial_velocity_min = -8.0 initial_velocity_max = 24.0 -orbit_velocity_min = 0.0 -orbit_velocity_max = 0.0 +gravity = Vector3(0, -4, 0) scale_min = 0.75 scale_curve = SubResource("CurveTexture_3omj5") color_ramp = SubResource("GradientTexture1D_83h4s") @@ -236,13 +235,11 @@ gradient = SubResource("Gradient_jks7f") particle_flag_disable_z = true direction = Vector3(-1, -2, 0) spread = 10.0 -gravity = Vector3(0, 64, 0) initial_velocity_min = 16.0 initial_velocity_max = 32.0 angular_velocity_min = 30.0 angular_velocity_max = 60.0 -orbit_velocity_min = 0.0 -orbit_velocity_max = 0.0 +gravity = Vector3(0, 64, 0) scale_min = 2.0 scale_max = 2.0 color_ramp = SubResource("GradientTexture1D_eeskk") @@ -259,6 +256,7 @@ Damage = 12.0 UseTime = 1.5 InitialVelocity = 220.0 PlayerLevelGain = 0.5 +Metadata = ExtResource("3_ju036") [node name="State" type="Node" parent="." node_paths=PackedStringArray("InitialState")] script = ExtResource("2_ag6rd") diff --git a/Scenes/Level.tscn b/Scenes/Level.tscn index 09ca743..72cdf09 100644 --- a/Scenes/Level.tscn +++ b/Scenes/Level.tscn @@ -3,11 +3,10 @@ [ext_resource type="Script" path="res://Utils/World.cs" id="1_1k6ew"] [ext_resource type="PackedScene" uid="uid://c271rdjhd1gfo" path="res://UI/Base.tscn" id="2_mm0qt"] -[node name="World" type="Node2D" node_paths=PackedStringArray("MusicPlayer", "DialogueBalloon")] +[node name="World" type="Node2D" node_paths=PackedStringArray("MusicPlayer")] process_mode = 3 script = ExtResource("1_1k6ew") MusicPlayer = NodePath("MusicPlayer") -DialogueBalloon = NodePath("CanvasLayer/SubViewportContainer/UIViewport/DialogBalloon") [node name="CanvasLayer" parent="." instance=ExtResource("2_mm0qt")] @@ -21,6 +20,7 @@ anchor_right = 1.0 anchor_bottom = 1.0 grow_horizontal = 2 grow_vertical = 2 +mouse_filter = 2 [node name="MusicPlayer" type="AudioStreamPlayer" parent="."] bus = &"Music" diff --git a/Scenes/Maps/ForestNew.tscn b/Scenes/Maps/ForestNew.tscn index 369dba3..5bfe142 100644 --- a/Scenes/Maps/ForestNew.tscn +++ b/Scenes/Maps/ForestNew.tscn @@ -1,4 +1,4 @@ -[gd_scene load_steps=22 format=3 uid="uid://cumi1cbg6xfdd"] +[gd_scene load_steps=23 format=3 uid="uid://cumi1cbg6xfdd"] [ext_resource type="PackedScene" uid="uid://clwv2owvk6abe" path="res://Scenes/BaseMap.tscn" id="1_oy3cp"] [ext_resource type="Texture2D" uid="uid://c70cn53osy56w" path="res://Assets/Sprites/dev-tileset.png" id="2_2gdry"] @@ -11,6 +11,7 @@ [ext_resource type="Texture2D" uid="uid://chwwo6vmf8iri" path="res://Assets/Sprites/Props/tree-autumn-leaves-2.png" id="8_4laic"] [ext_resource type="Texture2D" uid="uid://dhf2f5a1ty502" path="res://Assets/Sprites/Props/tree-autumn-leaves-1.png" id="8_hyhpu"] [ext_resource type="Texture2D" uid="uid://crvbsxrda5gcj" path="res://Assets/Sprites/Props/tree-autumn-leaves-3.png" id="9_exvkw"] +[ext_resource type="PackedScene" uid="uid://rd08pot25h00" path="res://Characters/SnusDealer.tscn" id="12_eyny2"] [sub_resource type="TileSetAtlasSource" id="TileSetAtlasSource_s1h2f"] texture = ExtResource("2_2gdry") @@ -458,8 +459,8 @@ cell_size = 16.0 agent_radius = 8.0 [sub_resource type="NavigationPolygon" id="NavigationPolygon_8xwo5"] -vertices = PackedVector2Array(280, -232, 280, -120, 276, -120, 216, -232, 216, -40, 276, -72, 296, -72, 296, -88, 392, -88, 392, -136, 472, -136, 472, -40, 440, -40, 440, 8, 488, 8, 488, 24, 440, 24, 440, 72, 424, 72, 424, 24, 424, 8, 376, 24, 376, 8, 424, -40, 296, -40, 296, -56, 276, -56, 276, -40) -polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3), PackedInt32Array(4, 3, 2, 5), PackedInt32Array(6, 7, 8), PackedInt32Array(8, 9, 10, 11, 12), PackedInt32Array(13, 14, 15, 16), PackedInt32Array(16, 17, 18, 19), PackedInt32Array(13, 16, 19, 20), PackedInt32Array(19, 21, 22, 20), PackedInt32Array(12, 13, 20, 23), PackedInt32Array(8, 12, 23), PackedInt32Array(8, 23, 24, 25), PackedInt32Array(6, 8, 25), PackedInt32Array(5, 6, 25, 26), PackedInt32Array(4, 5, 26), PackedInt32Array(4, 26, 27)]) +vertices = PackedVector2Array(392, -88, 392, -136, 472, -136, 472, -40, 440, -40, 440, 8, 488, 8, 488, 24, 440, 24, 440, 72, 424, 72, 424, 24, 424, 8, 376, 24, 376, 8, 424, -40, 216, -40, 280, -88, 216, -232, 280, -232) +polygons = Array[PackedInt32Array]([PackedInt32Array(0, 1, 2, 3, 4), PackedInt32Array(5, 6, 7, 8), PackedInt32Array(8, 9, 10, 11), PackedInt32Array(5, 8, 11, 12), PackedInt32Array(11, 13, 14, 12), PackedInt32Array(4, 5, 12, 15), PackedInt32Array(0, 4, 15), PackedInt32Array(0, 15, 16, 17), PackedInt32Array(17, 16, 18, 19)]) outlines = Array[PackedVector2Array]([PackedVector2Array(208, -240, 208, -32, 416, -32, 416.001, -15.9759, 368.001, -15.9787, 368, 32, 416, 32, 416, 80, 448, 80, 448, 32, 496, 32, 496.001, -15.9713, 448.001, -15.9741, 448, -32, 480, -32, 480.008, -143.972, 384.008, -143.978, 384.006, -95.9778, 288, -96, 288, -240)]) source_geometry_mode = 1 source_geometry_group_name = &"navigation" @@ -479,6 +480,9 @@ Markers = NodePath("Markers") AreaName = "Forest" MapName = "Forest" +[node name="CanvasModulate" parent="." index="7"] +position = Vector2(0, 1) + [node name="StaticBody2D" type="StaticBody2D" parent="Props" index="0"] y_sort_enabled = true position = Vector2(0, -96) @@ -843,7 +847,14 @@ position = Vector2(1272, 0) [node name="Campfire" parent="Entities" index="0" instance=ExtResource("3_ve4i2")] position = Vector2(-24, -8) -[node name="Areas" parent="." index="3"] +[node name="SnusDealer" parent="Entities" index="1" node_paths=PackedStringArray("ThinkerStateMachine", "Sprite", "Inventory", "StateMachine") instance=ExtResource("12_eyny2")] +position = Vector2(79, -6) +ThinkerStateMachine = NodePath("Thinker") +Sprite = NodePath("Sprites/Sprite") +Inventory = NodePath("Inventory") +StateMachine = NodePath("StateMachine") + +[node name="Areas" parent="." index="10"] visible = false [node name="Main" type="NavigationRegion2D" parent="Areas" index="0"] diff --git a/Shaders/Wipe.gdshader b/Shaders/Wipe.gdshader index 7cebd6d..64f052c 100644 --- a/Shaders/Wipe.gdshader +++ b/Shaders/Wipe.gdshader @@ -3,9 +3,5 @@ shader_type canvas_item; uniform float amount : hint_range(0.5, 1.0, 0.01) = 1; void fragment() { - // Place fragment code here. - //COLOR = texture(TEXTURE, UV); - if (UV.x > amount || UV.x < 1.0 - amount) { - COLOR.a = 0.0; - } + COLOR.a *= step(UV.x, amount) * step(1.0 - amount, UV.x); } diff --git a/Shaders/WipeXY.gdshader b/Shaders/WipeXY.gdshader new file mode 100644 index 0000000..cbe13a9 --- /dev/null +++ b/Shaders/WipeXY.gdshader @@ -0,0 +1,10 @@ +shader_type canvas_item; + +uniform float x_amount : hint_range(0.5, 1.0, 0.01) = 1; +uniform float y_amount : hint_range(0.5, 1.0, 0.01) = 0.5; + +void fragment() { + COLOR.a *= step(UV.x, x_amount) * step(1.0 - x_amount, UV.x); + //COLOR.a *= step(1.0 - y_amount, UV.y) * step(UV.y, y_amount); + COLOR.a *= 1.0 - (step(1.0 - y_amount, UV.y) * step(UV.y, y_amount)); +} diff --git a/State/Global/GlobalState.cs b/State/Global/GlobalState.cs index a4fe117..8c56b17 100644 --- a/State/Global/GlobalState.cs +++ b/State/Global/GlobalState.cs @@ -14,6 +14,8 @@ public partial class GlobalState : Node [Export] public Stats Stats { get; set; } + public static GlobalState Instance { get; private set; } + [Export] public GameSettings Settings { get; set; } @@ -30,6 +32,13 @@ public partial class GlobalState : Node public override void _Ready() { + if (Instance != null) + { + throw new MultipleSingletonsException(); + } + + Instance = this; + ProcessMode = ProcessModeEnum.Always; LoadSettings(); } diff --git a/State/Global/MapState.cs b/State/Global/MapState.cs index 4c3824b..6b8bf71 100644 --- a/State/Global/MapState.cs +++ b/State/Global/MapState.cs @@ -45,4 +45,13 @@ public partial class MapState : Resource } } } + + public bool? GetBoolean(string key) + { + if (_state[key].VariantType == Variant.Type.Bool) + { + return (bool)_state[key]; + } + return null; + } } diff --git a/State/Thinker/VendorIdle.cs b/State/Thinker/VendorIdle.cs new file mode 100644 index 0000000..d0154f6 --- /dev/null +++ b/State/Thinker/VendorIdle.cs @@ -0,0 +1,17 @@ +using Godot; +using GodotUtilities; + +namespace SupaLidlGame.State.Thinker; + +public partial class VendorIdle : ThinkerState +{ + public override ThinkerState Think() + { + var bestNeutral = NPC.FindBestNeutral(); + if (bestNeutral is not null) + { + NPC.Target = bestNeutral.Position - NPC.Position; + } + return null; + } +} diff --git a/UI/Base.tscn b/UI/Base.tscn index c9699d4..bbc7b12 100644 --- a/UI/Base.tscn +++ b/UI/Base.tscn @@ -1,13 +1,13 @@ [gd_scene load_steps=14 format=3 uid="uid://c271rdjhd1gfo"] -[ext_resource type="PackedScene" uid="uid://73jm5qjy52vq" path="res://Dialogue/balloon.tscn" id="1_atjb1"] [ext_resource type="Script" path="res://UI/UIController.cs" id="2_b4b6l"] [ext_resource type="PackedScene" uid="uid://bxo553hblp6nf" path="res://UI/HealthBar.tscn" id="3_j1j6h"] [ext_resource type="PackedScene" uid="uid://01d24ij5av1y" path="res://UI/BossBar.tscn" id="4_igi28"] [ext_resource type="PackedScene" uid="uid://cr7tkxctmyags" path="res://UI/LevelBar.tscn" id="4_rcekd"] [ext_resource type="PackedScene" uid="uid://c77754nvmckn" path="res://UI/LocationDisplay.tscn" id="5_cr6vo"] -[ext_resource type="PackedScene" uid="uid://sfs8dpfitpdu" path="res://UI/Hotbar.tscn" id="5_mmp18"] +[ext_resource type="PackedScene" uid="uid://sfs8dpfitpdu" path="res://UI/Inventory/Hotbar.tscn" id="5_mmp18"] [ext_resource type="PackedScene" uid="uid://d3q1yu3n7cqfj" path="res://UI/SceneTransition.tscn" id="6_j0nhv"] +[ext_resource type="PackedScene" uid="uid://cyggkyqosjk36" path="res://UI/Inventory/ShopMenu.tscn" id="8_ep3ae"] [ext_resource type="PackedScene" uid="uid://2afbrf8asy2a" path="res://UI/PostProcessing/Vignette.tscn" id="9_p1ubd"] [ext_resource type="PackedScene" uid="uid://b1wsryv4bn0cn" path="res://UI/PostProcessing/StunEffect.tscn" id="10_646ma"] [ext_resource type="Shader" path="res://Shaders/Grayscale.gdshader" id="11_w4gn1"] @@ -20,6 +20,26 @@ shader_parameter/grayscale_intensity = 0.0 [node name="BaseUI" type="CanvasLayer"] process_mode = 3 +[node name="PostProcessing" type="CanvasLayer" parent="."] + +[node name="Vignette" parent="PostProcessing" instance=ExtResource("9_p1ubd")] + +[node name="StunEffect" parent="PostProcessing" instance=ExtResource("10_646ma")] + +[node name="Sprite2D" type="TextureRect" parent="PostProcessing"] +visible = false +material = SubResource("ShaderMaterial_kbd61") +anchors_preset = 3 +anchor_left = 1.0 +anchor_top = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -128.0 +offset_top = -128.0 +grow_horizontal = 0 +grow_vertical = 0 +texture = ExtResource("12_tyv35") + [node name="SubViewportContainer" type="SubViewportContainer" parent="."] anchors_preset = 15 anchor_right = 1.0 @@ -36,9 +56,6 @@ handle_input_locally = false size = Vector2i(640, 360) render_target_update_mode = 4 -[node name="DialogBalloon" parent="SubViewportContainer/UIViewport" instance=ExtResource("1_atjb1")] -layer = 4 - [node name="MainUILayer" type="CanvasLayer" parent="SubViewportContainer/UIViewport"] layer = 3 @@ -52,6 +69,7 @@ grow_horizontal = 2 grow_vertical = 2 size_flags_horizontal = 3 size_flags_vertical = 3 +mouse_filter = 2 script = ExtResource("2_b4b6l") [node name="Top" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] @@ -78,7 +96,6 @@ layout_mode = 2 layout_mode = 2 [node name="Margin2" type="MarginContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main/Top/Left/VBoxContainer"] -visible = false layout_mode = 2 theme_override_constants/margin_left = 16 theme_override_constants/margin_top = 16 @@ -92,6 +109,23 @@ theme_override_constants/margin_right = 16 layout_mode = 2 _slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] +[node name="BoxContainer" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] +layout_mode = 1 +anchors_preset = 11 +anchor_left = 1.0 +anchor_right = 1.0 +anchor_bottom = 1.0 +offset_left = -236.0 +grow_horizontal = 0 +grow_vertical = 2 +alignment = 2 + +[node name="ShopMenu" parent="SubViewportContainer/UIViewport/MainUILayer/Main/BoxContainer" node_paths=PackedStringArray("_inventoryGrid") instance=ExtResource("8_ep3ae")] +unique_name_in_owner = true +visible = false +layout_mode = 2 +_inventoryGrid = NodePath("PanelContainer/VBoxContainer/ScrollContainer/InventoryGrid") + [node name="Bottom" type="HBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] layout_mode = 1 anchors_preset = 12 @@ -107,27 +141,13 @@ alignment = 1 visible = false layout_mode = 2 +[node name="VBoxContainer" type="VBoxContainer" parent="SubViewportContainer/UIViewport/MainUILayer/Main"] +visible = false +layout_mode = 0 +offset_right = 40.0 +offset_bottom = 40.0 + [node name="LocationDisplay" parent="SubViewportContainer/UIViewport/MainUILayer" instance=ExtResource("5_cr6vo")] [node name="SceneTransition" parent="SubViewportContainer/UIViewport/MainUILayer" instance=ExtResource("6_j0nhv")] z_index = 1 - -[node name="PostProcessing" type="CanvasLayer" parent="."] - -[node name="Vignette" parent="PostProcessing" instance=ExtResource("9_p1ubd")] - -[node name="StunEffect" parent="PostProcessing" instance=ExtResource("10_646ma")] - -[node name="Sprite2D" type="TextureRect" parent="PostProcessing"] -visible = false -material = SubResource("ShaderMaterial_kbd61") -anchors_preset = 3 -anchor_left = 1.0 -anchor_top = 1.0 -anchor_right = 1.0 -anchor_bottom = 1.0 -offset_left = -128.0 -offset_top = -128.0 -grow_horizontal = 0 -grow_vertical = 0 -texture = ExtResource("12_tyv35") diff --git a/UI/Hotbar.tscn b/UI/Hotbar.tscn deleted file mode 100644 index 371c5c6..0000000 --- a/UI/Hotbar.tscn +++ /dev/null @@ -1,25 +0,0 @@ -[gd_scene load_steps=3 format=3 uid="uid://sfs8dpfitpdu"] - -[ext_resource type="Script" path="res://UI/Hotbar.cs" id="1_2sak2"] -[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/InventorySlot.tscn" id="1_ct3cn"] - -[node name="Hotbar" type="GridContainer" node_paths=PackedStringArray("_slots")] -anchors_preset = 1 -anchor_left = 1.0 -anchor_right = 1.0 -offset_left = -112.0 -offset_bottom = 32.0 -grow_horizontal = 0 -theme_override_constants/h_separation = 8 -columns = 3 -script = ExtResource("1_2sak2") -_slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] - -[node name="InventorySlot" parent="." instance=ExtResource("1_ct3cn")] -layout_mode = 2 - -[node name="InventorySlot2" parent="." instance=ExtResource("1_ct3cn")] -layout_mode = 2 - -[node name="InventorySlot3" parent="." instance=ExtResource("1_ct3cn")] -layout_mode = 2 diff --git a/UI/IModal.cs b/UI/IModal.cs new file mode 100644 index 0000000..9b7d172 --- /dev/null +++ b/UI/IModal.cs @@ -0,0 +1,6 @@ +namespace SupaLidlGame.UI; + +public interface IModal +{ + public void HideModal(); +} diff --git a/UI/Hotbar.cs b/UI/Inventory/Hotbar.cs similarity index 83% rename from UI/Hotbar.cs rename to UI/Inventory/Hotbar.cs index 5b311da..afbf0c4 100644 --- a/UI/Hotbar.cs +++ b/UI/Inventory/Hotbar.cs @@ -1,7 +1,6 @@ using Godot; -using SupaLidlGame.Items; -namespace SupaLidlGame.UI; +namespace SupaLidlGame.UI.Inventory; public partial class Hotbar : GridContainer { @@ -13,7 +12,7 @@ public partial class Hotbar : GridContainer Events.EventBus.Instance.PlayerInventoryUpdate += OnInventoryUpdate; } - public void OnInventoryUpdate(Inventory inventory) + public void OnInventoryUpdate(Items.Inventory inventory) { GD.Print($"UPDATE: {inventory.SelectedIndex} is selected index."); for (int i = 0; i < 3; i++) diff --git a/UI/Inventory/Hotbar.tscn b/UI/Inventory/Hotbar.tscn new file mode 100644 index 0000000..2ef8600 --- /dev/null +++ b/UI/Inventory/Hotbar.tscn @@ -0,0 +1,30 @@ +[gd_scene load_steps=4 format=3 uid="uid://sfs8dpfitpdu"] + +[ext_resource type="Script" path="res://UI/Inventory/Hotbar.cs" id="1_2sak2"] +[ext_resource type="PackedScene" uid="uid://dmvu2hjyrwc1y" path="res://UI/Inventory/HotbarSlot.tscn" id="2_3axfe"] + +[sub_resource type="StyleBoxEmpty" id="StyleBoxEmpty_6jbma"] + +[node name="Hotbar" type="GridContainer" node_paths=PackedStringArray("_slots")] +anchors_preset = 1 +anchor_left = 1.0 +anchor_right = 1.0 +offset_left = -112.0 +offset_bottom = 32.0 +grow_horizontal = 0 +theme_override_constants/h_separation = 8 +columns = 3 +script = ExtResource("1_2sak2") +_slots = [NodePath("InventorySlot"), NodePath("InventorySlot2"), NodePath("InventorySlot3")] + +[node name="InventorySlot" parent="." instance=ExtResource("2_3axfe")] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_6jbma") + +[node name="InventorySlot2" parent="." instance=ExtResource("2_3axfe")] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_6jbma") + +[node name="InventorySlot3" parent="." instance=ExtResource("2_3axfe")] +layout_mode = 2 +theme_override_styles/panel = SubResource("StyleBoxEmpty_6jbma") diff --git a/UI/Inventory/HotbarSlot.cs b/UI/Inventory/HotbarSlot.cs new file mode 100644 index 0000000..e69de29 diff --git a/UI/Inventory/HotbarSlot.tscn b/UI/Inventory/HotbarSlot.tscn new file mode 100644 index 0000000..ba63588 --- /dev/null +++ b/UI/Inventory/HotbarSlot.tscn @@ -0,0 +1,16 @@ +[gd_scene load_steps=4 format=3 uid="uid://dmvu2hjyrwc1y"] + +[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/Inventory/InventorySlot.tscn" id="1_fb62b"] +[ext_resource type="Texture2D" uid="uid://dp7osg05ip5oo" path="res://Assets/Sprites/sword.png" id="2_aqbyo"] +[ext_resource type="Texture2D" uid="uid://dc1gcsbhkchvg" path="res://Assets/Sprites/UI/hotbar-active.png" id="2_bcv71"] + +[node name="InventorySlot" instance=ExtResource("1_fb62b")] + +[node name="TextureRect" parent="." index="0"] +texture = ExtResource("2_aqbyo") +expand_mode = 3 + +[node name="SelectedFrame" type="NinePatchRect" parent="." index="2"] +visible = false +layout_mode = 2 +texture = ExtResource("2_bcv71") diff --git a/UI/Inventory/InventoryGrid.cs b/UI/Inventory/InventoryGrid.cs new file mode 100644 index 0000000..124b536 --- /dev/null +++ b/UI/Inventory/InventoryGrid.cs @@ -0,0 +1,172 @@ +using Godot; +using GodotUtilities; +using SupaLidlGame.Items; + +namespace SupaLidlGame.UI.Inventory; + +public partial class InventoryGrid : GridContainer +{ + private SupaLidlGame.Items.IItemCollection _source; + + [Export] + private PackedScene _slotScene; + + public ButtonGroup ButtonGroup { get; private set; } + + public SupaLidlGame.Items.IItemCollection Source + { + get => _source; + set + { + GD.Print("Set InventoryGrid source"); + _source = value; + Redraw(); + } + } + + [Signal] + public delegate void SlotFocusedEventHandler(InventorySlot slot); + + [Signal] + public delegate void SlotUnfocusedEventHandler(InventorySlot slot); + + [Signal] + public delegate void SlotSelectedEventHandler(InventorySlot slot); + + public InventoryGrid() + { + ButtonGroup = new(); + } + + public void Redraw() + { + GD.Print("Redrawing inventory grid..."); + + if (_source is null) + { + this.QueueFreeChildren(); + } + + var children = GetChildren(); + + for (int i = children.Count; i < _source.Capacity; i++) + { + AddInventorySlot(); + } + + for (int i = children.Count - 1; i >= _source.Capacity; i--) + { + children[i].QueueFree(); + } + + children = GetChildren(); + + // iterate through items and update the grid + using (var items = _source.GetItems().GetEnumerator()) + { + GD.Print("Updating items..."); + int i; + for (i = 0; items.MoveNext(); i++) + { + InventorySlot slot = children[i] as InventorySlot; + + ItemMetadata item = items.Current; + slot.Item = item; + } + + // make remaining slots display empty + for (; i < _source.Capacity; i++) + { + InventorySlot slot = children[i] as InventorySlot; + + slot.Item = null; + } + } + + for (int i = 0; i < children.Count; i++) + { + var child = children[i] as Control; + if (i > 0) + { + child.FocusPrevious = child.GetPathTo(children[i - 1]); + } + if (i < children.Count - 1) + { + child.FocusNext = child.GetPathTo(children[i + 1]); + } + } + + if (children.Count > 0) + { + var button = children[0] as Button; + button.ButtonPressed = true; + button.GrabFocus(); + } + } + + private InventorySlot AddInventorySlot() + { + var slot = _slotScene.Instantiate(); + AddChild(slot); + slot.ButtonGroup = ButtonGroup; + + void focusedHandler() + { + EmitSignal(SignalName.SlotFocused, slot); + } + + void unfocusedHandler() + { + EmitSignal(SignalName.SlotUnfocused, slot); + } + + void toggledHandler() + { + EmitSignal(SignalName.SlotSelected, slot); + } + + slot.Connect( + InventorySlot.SignalName.FocusEntered, + Callable.From(focusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.FocusExited, + Callable.From(unfocusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.MouseEntered, + Callable.From(focusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.MouseExited, + Callable.From(unfocusedHandler) + ); + + slot.Connect( + InventorySlot.SignalName.Pressed, + Callable.From(toggledHandler) + ); + + return slot; + } + + private void RemoveInventorySlot(InventorySlot slot) + { + RemoveChild(slot); + } + + public bool GrabSlotFocus() + { + var children = GetChildren(); + if (children.Count > 0) + { + var button = children[0] as Button; + button.GrabFocus(); + return true; + } + return false; + } +} diff --git a/UI/Inventory/InventoryGrid.tscn b/UI/Inventory/InventoryGrid.tscn new file mode 100644 index 0000000..291e659 --- /dev/null +++ b/UI/Inventory/InventoryGrid.tscn @@ -0,0 +1,14 @@ +[gd_scene load_steps=3 format=3 uid="uid://chmokkxsy5vas"] + +[ext_resource type="Script" path="res://UI/Inventory/InventoryGrid.cs" id="1_7128g"] +[ext_resource type="PackedScene" uid="uid://ctad0dkoyw8ad" path="res://UI/Inventory/InventorySlot.tscn" id="2_b6vp8"] + +[node name="InventoryGrid" type="GridContainer"] +anchors_preset = 15 +anchor_right = 1.0 +anchor_bottom = 1.0 +grow_horizontal = 2 +grow_vertical = 2 +columns = 5 +script = ExtResource("1_7128g") +_slotScene = ExtResource("2_b6vp8") diff --git a/UI/Inventory/InventorySlot.cs b/UI/Inventory/InventorySlot.cs new file mode 100644 index 0000000..8d9bda6 --- /dev/null +++ b/UI/Inventory/InventorySlot.cs @@ -0,0 +1,95 @@ +using Godot; +using GodotUtilities; +using GodotUtilities.SourceGenerators; + +namespace SupaLidlGame.UI.Inventory; + +public partial class InventorySlot : Button +{ + private bool _isSelected = false; + + public bool IsSelected + { + get => _isSelected; + set + { + _isSelected = value; + if (_selectedFrame is not null) + { + //_selectedFrame.Visible = _isSelected; + //_frame.Visible = !_isSelected; + } + } + } + + [Export] + public bool UseFocusAsSelected { get; set; } = true; + + private TextureRect _textureRect; + + private NinePatchRect _frame; + + private NinePatchRect _selectedFrame; + + private static Texture2D _placeholderTexture; + + private Items.ItemMetadata _item; + + public Items.ItemMetadata Item + { + get => _item; + set + { + _item = value; + + if (_item is null) + { + //_textureRect.Texture = null; + Icon = null; + } + else + { + //_textureRect.Texture = _item.Icon; + Icon = _item.Icon; + } + } + } + + static InventorySlot() + { + _placeholderTexture = ResourceLoader.Load( + "res://Assets/Sprites/UI/hotbar-inactive.png"); + } + + public override void _Ready() + { + _textureRect = GetNode("TextureRect"); + _frame = GetNode("Frame"); + _selectedFrame = GetNode("SelectedFrame"); + + if (Item is null) + { + // do this to reset the icon + Item = null; + } + + if (UseFocusAsSelected) + { + void focusEntered() + { + IsSelected = true; + } + + void focusExited() + { + IsSelected = false; + } + + Connect(SignalName.FocusEntered, Callable.From(focusEntered)); + Connect(SignalName.FocusExited, Callable.From(focusExited)); + + Connect(SignalName.MouseEntered, Callable.From(focusEntered)); + Connect(SignalName.MouseExited, Callable.From(focusExited)); + } + } +} diff --git a/UI/Inventory/InventorySlot.tscn b/UI/Inventory/InventorySlot.tscn new file mode 100644 index 0000000..275b23e --- /dev/null +++ b/UI/Inventory/InventorySlot.tscn @@ -0,0 +1,41 @@ +[gd_scene load_steps=8 format=3 uid="uid://ctad0dkoyw8ad"] + +[ext_resource type="Script" path="res://UI/Inventory/InventorySlot.cs" id="1_fju5i"] +[ext_resource type="Theme" uid="uid://cksjbu3vrup5" path="res://UI/Themes/supalidl.tres" id="1_wnu7u"] +[ext_resource type="StyleBox" uid="uid://nvb4etac7ee2" path="res://UI/Themes/InventorySlotButtonFocus.tres" id="2_3wx0v"] +[ext_resource type="Texture2D" uid="uid://dc1gcsbhkchvg" path="res://Assets/Sprites/UI/hotbar-active.png" id="2_m56j3"] +[ext_resource type="StyleBox" uid="uid://pqtn0115bqtp" path="res://UI/Themes/InventorySlotButtonPressed.tres" id="3_46bp6"] +[ext_resource type="StyleBox" uid="uid://cfqp0ycwvwx7c" path="res://UI/Themes/InventorySlotButtonNormal.tres" id="4_cc2jo"] + +[sub_resource type="PlaceholderTexture2D" id="PlaceholderTexture2D_ajutj"] +size = Vector2(24, 24) + +[node name="InventorySlot" type="Button"] +custom_minimum_size = Vector2(32, 32) +theme = ExtResource("1_wnu7u") +theme_type_variation = &"InventorySlotButton" +theme_override_styles/focus = ExtResource("2_3wx0v") +theme_override_styles/pressed = ExtResource("3_46bp6") +theme_override_styles/normal = ExtResource("4_cc2jo") +toggle_mode = true +action_mode = 0 +icon_alignment = 1 +script = ExtResource("1_fju5i") + +[node name="TextureRect" type="TextureRect" parent="."] +visible = false +layout_mode = 2 +offset_right = 32.0 +offset_bottom = 32.0 +mouse_filter = 2 +texture = SubResource("PlaceholderTexture2D_ajutj") +expand_mode = 2 +stretch_mode = 3 + +[node name="Frame" type="NinePatchRect" parent="."] +visible = false +self_modulate = Color(1, 1, 1, 0.5) +layout_mode = 2 +offset_right = 32.0 +offset_bottom = 32.0 +texture = ExtResource("2_m56j3") diff --git a/UI/Inventory/ItemTooltip.cs b/UI/Inventory/ItemTooltip.cs new file mode 100644 index 0000000..dcf6f08 --- /dev/null +++ b/UI/Inventory/ItemTooltip.cs @@ -0,0 +1,44 @@ +using Godot; +using SupaLidlGame.Items; + +namespace SupaLidlGame.UI.Inventory; + +public partial class ItemTooltip : Control +{ + private ItemMetadata _item; + + public ItemMetadata Item + { + get => _item; + set + { + _item = value; + if (IsNodeReady()) + { + Render(); + } + } + } + + public override void _Ready() + { + Render(); + } + + public void Render() + { + if (_item is null) + { + GetNode