From 55fd0520724cfbc606c3d5e4601e41c3fdc28255 Mon Sep 17 00:00:00 2001 From: Perry Date: Mon, 16 Mar 2026 20:43:53 +0100 Subject: [PATCH] =?UTF-8?q?Rozsv=C4=9Bcen=C3=AD=20a=20zhas=C3=ADn=C3=A1n?= =?UTF-8?q?=C3=AD=20sv=C4=9Btel,=20sprity=20pro=20m=C3=ADstnosti,=20indik?= =?UTF-8?q?=C3=A1tory=20rozsv=C3=ADcen=C3=BDch=20sv=C4=9Btel,=20po=20konci?= =?UTF-8?q?=20hry=20je=20hr=C3=A1=C4=8D=20vr=C3=A1cen=20do=20hlavn=C3=ADho?= =?UTF-8?q?=20menu?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FNAF_Clone.sln.DotSettings.user | 1 + FNAF_Clone/Client.cs | 11 +-- FNAF_Clone/ClientEnemy.cs | 2 +- FNAF_Clone/ClientEnemyManager.cs | 22 +++-- FNAF_Clone/CommandManager.cs | 12 ++- FNAF_Clone/Content/Content.mgcb | 15 ++++ .../Content/images/SpriteSheet_monitor.png | Bin 5404 -> 5438 bytes .../Content/images/SpriteSheet_rooms.png | Bin 0 -> 17943 bytes .../Content/images/enemies-definition.xml | 22 ++--- .../Content/images/monitor-definition.xml | 55 +++++++------ .../Content/images/office-definition.xml | 14 ++-- .../Content/images/rooms-definition.xml | 32 ++++++++ FNAF_Clone/EventProcessor.cs | 19 ++++- FNAF_Clone/GUI/EnemyUIElement.cs | 33 ++++++++ FNAF_Clone/GUI/Screen.cs | 13 ++- FNAF_Clone/GUI/UIElement.cs | 9 +- FNAF_Clone/GUI/UIManager.cs | 77 ++++++++++++------ FNAF_Clone/Map/ClientMapManager.cs | 58 +++++++++++-- FNAF_Clone/Map/MapTileProjection.cs | 2 + FNAF_Server/CommandProcessor.cs | 13 +++ FNAF_Server/Enemies/RoamingPathfinder.cs | 3 +- FNAF_Server/GameLogic.cs | 22 ++++- FNAF_Server/Map/MapManager.cs | 11 ++- MonoGameLibrary | 2 +- PacketLib/GameEvent.cs | 1 + PacketLib/MapInitPacket.cs | 1 + PacketLib/PlayerCommand.cs | 1 + 27 files changed, 338 insertions(+), 113 deletions(-) create mode 100644 FNAF_Clone/Content/images/SpriteSheet_rooms.png create mode 100755 FNAF_Clone/Content/images/rooms-definition.xml create mode 100644 FNAF_Clone/GUI/EnemyUIElement.cs diff --git a/FNAF_Clone.sln.DotSettings.user b/FNAF_Clone.sln.DotSettings.user index 4f3470d..5e3ead0 100644 --- a/FNAF_Clone.sln.DotSettings.user +++ b/FNAF_Clone.sln.DotSettings.user @@ -12,6 +12,7 @@ ForceIncluded ForceIncluded ForceIncluded + ForceIncluded ForceIncluded ForceIncluded ForceIncluded diff --git a/FNAF_Clone/Client.cs b/FNAF_Clone/Client.cs index 07da325..f37260a 100644 --- a/FNAF_Clone/Client.cs +++ b/FNAF_Clone/Client.cs @@ -98,16 +98,7 @@ public class Client { #nullable enable private static void OnMapInit(MapInitPacket packet) { - (int id1, int id2, ConnectorType type, ClientPlayer? owner)[] connectorsData = new (int, int , ConnectorType, ClientPlayer?)[packet.Connectors.Length / 4]; - for (int i = 0; i < packet.Connectors.Length / 4; i++){ - connectorsData[i] = (packet.Connectors[i * 4], packet.Connectors[i * 4 + 1], (ConnectorType)packet.Connectors[i * 4 + 2], packet.Connectors[i * 4 + 3] == -1 ? null : GetPlayer(packet.Connectors[i * 4 + 3])); - } - ClientMapManager.InitMap(packet.UpsideDown); - TileConnectorProjection[] connectors = connectorsData.Select(c => new TileConnectorProjection(ClientMapManager.Get(c.id1), ClientMapManager.Get(c.id2), c.type){Owner = c.owner}).ToArray(); - ClientMapManager.InitConnectors(connectors); - - UIManager.SpawnMapElements(connectors.Where(c => c.Type == ConnectorType.DOOR_REMOTE).ToArray()); - + ClientMapManager.InitMap(packet.Connectors, packet.YourTiles, packet.OpponentTiles, packet.LitTiles, packet.UpsideDown); } public static void Update() { diff --git a/FNAF_Clone/ClientEnemy.cs b/FNAF_Clone/ClientEnemy.cs index 821cb93..8a68128 100644 --- a/FNAF_Clone/ClientEnemy.cs +++ b/FNAF_Clone/ClientEnemy.cs @@ -6,7 +6,7 @@ using MonoGameLibrary.Graphics; namespace FNAF_Clone; public class ClientEnemy : GlobalEnemy { - public ClientEnemy(int typeId, string name, int id, UIElement sprite, int difficulty, MapTileProjection location, JumpscareUIElement jumpscareSprite) : base(difficulty, id) { + public ClientEnemy(int typeId, string name, int id, EnemyUIElement sprite, int difficulty, MapTileProjection location, JumpscareUIElement jumpscareSprite) : base(difficulty, id) { Name = name; TypeId = typeId; Sprite = sprite; diff --git a/FNAF_Clone/ClientEnemyManager.cs b/FNAF_Clone/ClientEnemyManager.cs index fb40109..6f83f50 100644 --- a/FNAF_Clone/ClientEnemyManager.cs +++ b/FNAF_Clone/ClientEnemyManager.cs @@ -6,12 +6,13 @@ using FNAF_Clone.GUI; using FNAF_Clone.Map; using GlobalClassLib; using Microsoft.Xna.Framework; +using MonoGameLibrary.Graphics; namespace FNAF_Clone; public static class ClientEnemyManager { private static Dictionary enemies = new(); - private static Point cameraCorner = new Point(64, 64); + private static Point cameraCorner = new(64, 64); private static Action defaultAfterJumpscare = UIManager.ShowDeathScreen; public static void AddEnemy(ClientEnemy enemy) { @@ -26,25 +27,27 @@ public static class ClientEnemyManager { (int)type, "Lurk", id, - new UIElement(UIManager.EnemyAtlas[0], cameraCorner), + new EnemyUIElement(UIManager.EnemyAtlas["lurk-lit"], UIManager.EnemyAtlas["lurk-unlit"], cameraCorner), difficulty, location, - new JumpscareUIElement(UIManager.EnemyAtlas[0], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare))); + new JumpscareUIElement(UIManager.EnemyAtlas["lurk-lit"], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare) + )); break; case EnemyType.NEKO: AddEnemy(new ClientEnemy( (int)type, "Neko", id, - new UIElement(UIManager.EnemyAtlas[1], cameraCorner), + new EnemyUIElement(UIManager.EnemyAtlas["neko-lit"], UIManager.EnemyAtlas["neko-unlit"], cameraCorner, 1), difficulty, location, - new JumpscareUIElement(UIManager.EnemyAtlas[1], new(0, -30), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare))); + new JumpscareUIElement(UIManager.EnemyAtlas["neko-lit"], new(0, -30), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare) + )); break; case EnemyType.SPOT: - UIElement element = - new UIElement([UIManager.EnemyAtlas[2], UIManager.EnemyAtlas[3], UIManager.EnemyAtlas[4], UIManager.EnemyAtlas[5]], cameraCorner); - element.SetTexture(2); + EnemyUIElement element = + new EnemyUIElement([UIManager.EnemyAtlas["spot-awake-lit"], UIManager.EnemyAtlas["spot-asleep-lit"]],[UIManager.EnemyAtlas["spot-awake-unlit"], UIManager.EnemyAtlas["spot-asleep-unlit"]], cameraCorner, 2); + element.SetTexture(true, 1); AddEnemy(new ClientEnemy( (int)type, "Spot", @@ -52,7 +55,8 @@ public static class ClientEnemyManager { element, difficulty, location, - new JumpscareUIElement(UIManager.EnemyAtlas[2], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare))); + new JumpscareUIElement(UIManager.EnemyAtlas["spot-awake-lit"], new(0, 0), 3, 2, 2, 0.1f, afterStop:defaultAfterJumpscare) + )); break; } } diff --git a/FNAF_Clone/CommandManager.cs b/FNAF_Clone/CommandManager.cs index cce3369..6487893 100644 --- a/FNAF_Clone/CommandManager.cs +++ b/FNAF_Clone/CommandManager.cs @@ -16,7 +16,8 @@ public class CommandManager { ("Toggle left door", Keys.A, ToggleDoorLeft), ("Toggle centre door", Keys.W, ToggleDoorCentre), ("Toggle right door", Keys.D, ToggleDoorRight), - ("Toggle back door", Keys.S, ToggleDoorBack) + ("Toggle back door", Keys.S, ToggleDoorBack), + ("Toggle light", Keys.F, ToggleLight) ]; private static InputListenerHook allControlsHook{ get; } = new(true); @@ -42,6 +43,8 @@ public class CommandManager { private static Dictionary currentDoorBinds = new(); + private static void ToggleLight() => SendToggleLight(Client.Player.state.camera, !ClientMapManager.Get(Client.Player.state.camera).Lit); + private static void SendToggleDoor(Direction direction) { if (Screen.CurrentScreen.Label == UIManager.ScreenTypes.CAMERAS){ SendToggleRemoteDoor(direction); @@ -80,4 +83,11 @@ public class CommandManager { currentDoorBinds.Add(dir, c); } } + + private static void SendToggleLight(int id, bool state) { + if(!Client.Player.state.monitorUp || ClientMapManager.Get(id).Owner != Client.Player) return; + ClientMapManager.Get(id).Lit = state; + UIManager.UpdateCameras([id]); + Client.SendCommands([PlayerCommand.SET_LIGHT(id, state)]); + } } \ No newline at end of file diff --git a/FNAF_Clone/Content/Content.mgcb b/FNAF_Clone/Content/Content.mgcb index ea1b793..32d5772 100644 --- a/FNAF_Clone/Content/Content.mgcb +++ b/FNAF_Clone/Content/Content.mgcb @@ -22,6 +22,9 @@ #begin images/office-definition.xml /copy:images/office-definition.xml +#begin images/rooms-definition.xml +/copy:images/rooms-definition.xml + #begin images/SpriteSheet_enemies.png /importer:TextureImporter /processor:TextureProcessor @@ -58,6 +61,18 @@ /processorParam:TextureFormat=Color /build:images/SpriteSheet_office.png +#begin images/SpriteSheet_rooms.png +/importer:TextureImporter +/processor:TextureProcessor +/processorParam:ColorKeyColor=255,0,255,255 +/processorParam:ColorKeyEnabled=True +/processorParam:GenerateMipmaps=False +/processorParam:PremultiplyAlpha=True +/processorParam:ResizeToPowerOfTwo=False +/processorParam:MakeSquare=False +/processorParam:TextureFormat=Color +/build:images/SpriteSheet_rooms.png;images/SpriteSheet_map.png + #begin images/SpriteSheet_testBlocks.png /importer:TextureImporter /processor:TextureProcessor diff --git a/FNAF_Clone/Content/images/SpriteSheet_monitor.png b/FNAF_Clone/Content/images/SpriteSheet_monitor.png index 6b32e4e8fcbe5c5da057da9959b5c020f8bc011e..22fe39903d152539860577e4c35bea855e400016 100644 GIT binary patch literal 5438 zcmeHLc~sNawmu1=1d9?y8AJhVn>tiQWl)9~sX$u|h^;6xML+}*P-z4bAVCmRMC-K* zGK=2g1u#eumyZ_y0t=~%4&dJW%`+VQG z_dch0y1CAsvv>{wVD7iyY}*9@k_MkZ^e51gv$Q}Tz9FJ^xjKPcElVZHc*4T{Md%^J=?mK%NSnXttVqb$aVp#{q#xT| zfs^HWd%z9+CtsE*Z(k10OC@BfHcZNF0I(XOs>Yd=c3(!tvRC@RxTO-!9^S+$qjV8}g zM=ry^mJx3dHavxX2j8G90NxmZ1{fEB1j+Mh0PO$M^k*ji#KfOe_%9cQidNCwO=<6? z69s5>8xS9*SufaN%AYAK=4At5IS2c{*3AD$p}~iHr|Mh`?Y2eIrYp$d(Ivco@LZgB zD-!yg%rd1_RnhZjwaUYKdADSD?dTr#11*8=EY|{aKi%4af$p@%k*bT->Vm6|qrAvA zUc|ewQSn;2R~h}_vZs|c(m0fC^as#VLu)d+i%u4lY+QLIUeOubUMdK-mpig^R+2<< z$;)J93dK(D-hKbz_ioyQZ_?syP>kC{aoio>aoxW6BpeuvXZpX&>>L%$PYLjtO8){i zZl>)ZcQT~j-b_lkS}X|m7E{s+GwnN53qzFB*DHuAhis7mZYo11>_i zSy}QaGsiTGDxctw`*nvBK}a$Px>3Vs=XpM&b0?l`PuCtSoGP(%N88QS1GS$DUh$!^ z2upRI76jcsO{gOcM-K2yD|aeWIUU3Y#TVn~2P=b`7UT>Z4q2P!l-H-HXrI2)RdHM9 z4F=;P_>-gsC-ueR`?A~0B)QnmOO>R*h5JOoCo4!V9dK;!F@qx&2^B6L)p~W@g)(js7O94rE+uJtt_!cm0*PXl@kz zl_#{Z8?DdTZvD^}w0>PCpY2uA9bNCQjSTPZsjgB?h@Z5THYLUTywO5~ogOZO({lPE z2t?+L^r`_3z+bo`s0@aqwf156&At7x!Z^Eik`xE&>Ryx{uxzMqHT`;a(McG)&`q?2 zHaflo(QkWsutdYNKqMMC2EK4@-vJQDx&0JM`waQE02e_~=!p3Q^Owx-K3S)zvPKn7 zXB|>A@Y5w`JAQHYSjf7?S#hRNF>$y;`3v3tE7(b`dNx%#J@TE$Lg(oWqx^OHt)X_P z5?*8$Y=1J3##mmtJha=-NRn=(2H`G`h0SJ{93vI}zLL>KtdOY?ilgYOp_ky7Fx#J3 z7pw7FxWX@m*v~V1(q!xaQ3#K8@MNwltEf|wwWEgeg+#rkVm$2dAw_I((FvY9Amg8U zjTusiyMz>gDzGkb)1Em(Wp-gp%kp+z=ZP|M=Zrzv?F)-yd%3M{*TBZ^T!b$>%l2dm z8SZPj&WW$QnRX`~STcBs1p)GQep;bKYH=2l+a1Rlsi8J?ZLt>h<6|PMNeLzekmnCZ zL_0F7>6V7}=kxMs2V@x>)uYDoemkSVH?r&+?42Y$0Ara(zTtUja(kO?6P5@T?}^SY zCGfR8mDS5``bSTaA|CzpmU@ww?`pE&mL*SWfl0lh2E=*<3?e)8X@_)k8g^Tt7qyeM zSgzQ@lT`=7gTvHAD4w#WSf97ipL(rM^k?x92f67r+=jql151E8X!Gvb%E9L0;Zp1^ zepQHqgky%nxK3>}rIan}=|UaxJAALAhENOe*AFG%Wk~NgiL@;Mri2%-tlZ}hkd}6x zg2&-Avy_`-ET#%Jtc;l_$J+!K`zb&2W-PEjL*BC2Bc9n@*O(`4BXRapN&U6)_GQ}&)E;CK7k5g^jD%Is zfy6lz$&T}k6O5H@egAEW6(T?YzcnAItR@XRqgqmkoMHM3xFN@m`?lmh>+227v$ehF ztEwGIx_Cf-5{mnE?)0?zcf#glYlxZ#pVn5vxNpmVkFRij^UOGh`dicg2XlbN^k4xR zlupQej+As!A29uSXXYJbn0H05t*xK!r zSny;l&c*{oh|eB*!XkD31NBcTtB?;$5*}l|8 zuBP;0_2Fs;g*&>&wEA9?e4E&sQfS2wD;55#p!~1)jc&f5qeeLU=g_S2y^d;GjJ+7# z?oiaF**ereTHD{uP_6H_BD(m;<2}_vvD`mS+53WUg5IM@TvEr)SVds zMa!E{wjux|o|Oq+Siwofak(%AVv3fx9&ud_8nVmlHjVw2)w=S!OAyuS*~w-69h)tL z&yQ^lUFKrhO)PjnRP7T-s-*VviaQ>8-}7LTS6-iOMG!10aRtcMO_9&ao1-E&L#zxh zLE*CuKGh155(WIVd(Fb3M&0nbqdOArn-FZ z3k#@hVZD(lqZv5*$9jSfvw@HnMHv!KRr*l}R&q8pC3F=|5WB@po+!3aLkuWYe>8m$ z?>b_MT&wMx|D58#(!FWuadml}Al!N!fufo9#R_LT)*XjsG0nuL9{ZluAEtH~cR^~f zSX!ns&;E$N*H?S5(MnuVk7A)9XeNAeF#9oU;ZKAGYN+|>q5!$xgJ}i3gZI*~hOT4J zAHLU3J)&X@VTh%r{=Rgcob&E?O#j10!SAVV(L&?HDshXx>E_*!U6mONi={7}wOxa@ zd-L?M?S(7Olyo1@B`x`+IBsc1b>75Z#EV{U$)@d3F5Zd;WrvZU05q(_R`pmsEdg#$ z_^>UPw>{doM>^8)LcRJxE}8r*k2>3xk#Kdt70Tl5&hCr0atov|8v@2CD{rba;GydR zokUQiF3s2&F#I6}=+=%OSmhXECdvZU8+2253vFlt4MTi)V~~*C|AzHib}A5beF>Ir z8$egXRyWM6<1}0nwb&SaOw{+bt5D)ClsaGdQVCMF1EIw7OO;8U{_%iNQ?Pjp~zw*P!JrS)?%k~%Rg#4`XTS)DkilXUgqWZN_moros zvP%((%$Lhh*fr*P?}8k2osllh9UN-lRAR+&ZZ(#n&85MzwI?h1dl!k ziR?|4QSlRFBf0K1;Z~dYxCF|C%$zCu>!7v^Z1RdSxrnpy_KAFLLYK1&8;75RKrbJ8 z>H{txU1$lfhz#(?^W}WR&QvuuH5Wz@>&6!#?-_QI;UQ%W;RSHiRF9AK!R0Y_58!Gl z(g#q|K3O_Yfpe^kVw7DgQ*VGQp6+MZ+2xGI)ObLt|3mKm252kT(oPK3J>EfZG z?EQTwzh7Og{V`q(6f60X=%RTcQqjBodzF=IKp>3XZFeC3t@xA^t;~Ri592;&$0l4} zfw}=zzjS^GP*^}cJbBH&JHzc%G){W2&EMkfZwc}z8qak_Xo2fTbyphW=n$Oyf2)K4 sms^4{C#ku*J literal 5404 zcmeHLX;hO}8omhuf(t~kil7C{2&k>ZEx2GvRkV)8rHTw|P?SYPM8U`s5`h5)5vQ~j z2%9<$797FTx ziMYa)(*&`RB5A1EpT7A{=2VjF zW~%1qTfqhVFE690eCBB7eX2|-AeL6Z?GdM&)XKm>cAgD+j{*U___xr*wHX*HAjISM zcnZk`81!S`s3e7yYz07UNCV&m$_x;U5NLqe3y>gYktVc0n4-&*Byp4hd@=&`e9MVc zhVaQJzKBi(@pd3PDVApytyg(lS%(D9vCr2=ccI*5S!!7la})_MBT}m3D4*QxptA{) zzFGxnc%w7P3pN4-*t+ANr}>?k{|J4^!5>Kcfy8g?@()h@-*Q4Dx!k^Aq!R%=A2>k0 ztVR<64IABwQt(-v5!reGu1D$pt6Kj)EB+gwIo!qTtvdBmk^wv;m>ylPhh%s#q0ANS z0Dw?BpwlxlLbp@{vCC9Np2{UddQZAhSn!T--fFuzn$99I?(Qf%b@ z{VnTD^E;)5m{acbh@D8~g|)y(P}xs+K~>Lmq<;IfLOm2Q$eVePpGw zEZ>=-%n`{8*W@72PZ9AZQQa@j<3lV~hvcluV{Dv=>?ZZ4bo7fB#hyI!K5;cl_^Gz7 zeD6}~gKZ(IU5>(<$0IFE?KhBf4eZ+#qP8QI3p&pBluO!^aw5f@wFfNKl!@slZT#uL z^@^}_zACYhlNIisa|MyT2zm3i%sV{XWg#cmXPNburL=@<){yP0!Od6zteX=!Fqo01 z_@vf4xyk~k%}?ZnP#+KNnlV)|bZQ(k-iF(?uXg>uXV`o9#kM8m&%Fn;l3I$mf9SRrY-S(j_Bh#H-*k5 zXv)W)*746SIBtGl@AM1D-4A3U;VOXeZFaWU=u_a^_go_D-^27vu(;uFr(*J1{u%(aCFVFCq^P@!vzFO!P}unp0U|ptm!G;))OL>&d>Nbi zeJ@#2WdhNy^RY`;JiTdt7!LgO$=GD0f3MS84k@^~SYCHwk^)^k3@&x^16b8qH4%>sD z#1TuJn^73xd8H=0p~xz}Z$@Hbf27DR;|soEG9*sh3=r&>d)Q=ptcky4rBaN``w`oT zah@ScqNdf_q;Fu+zOk%PgycxZ`I+uz$ClU^?mOM$ss)z_i7lsCkygPm*d@3I0zx;a$?`V8*x$5E3s)*^fdjA0KjwZj;>_~+> zi&WnxZgQPqoOf(l?{VfW;pS*G1k_L9lau|^&=$3p2 z*IKJ6C&KhrS=PI6T0W^e373{^=c-m%tIv-sA9J<0mZ})6{ljJ$K-)H8??ktZ>JFZ4 zeyC?etLu2})IK;e3ek;DISf-9R*$mkx;YzW2r86{w;~fr)i}%*%?R(_=)3g4t7zRk$p%50bnb z>0x^5D(-yb!k`^Y91S~#KxGHHi{*|Z?Z?SV;;5F7Qo<7yv6C$S_91;P?YuqSZ9n-P z6|vYWMK-0XwKs4cnnL?!RbGZzkXX10A21%~3Zt|-rJSQGrabihj{oWc#Emz7nBnK) zC4u{M5$;YSPNcacX?F7ll5THK7>3ZLtNj;Fm&BO}kc6a&nOflio6{BYZu!ahi!hz7 zn-V??bX>8^T_YoV2irEoW6Dl1B*twY{uW#|ajr z1~a3K9pT~an%kb_N_{0NOq&DK_ccc;G#wV!?F}X7?*qtIW7B7woBi*9ia-yRZ)N&Uq(mw5FjDD>0+0y^{VY0vCNX;6ba zb^VOn71bsL+_;x10T}Z;DoFkKfDkB6`13qcppuv;zr1)>{WrjrYzZKqT1rgEJyPX% z6+{+_Sbe^4I(}_CKIZ>7l$ZcW?Dmd{=916$lqyuxEP%>(s!;Sz{9rjok(Ntqf4Ul~ z>Vp0>m|<@^p4UeUdatlK1UJ3|KUf&5qFcfzD-E;HW~v7unql?26x>|7cp<-So)FkV zU`D62TLxwVGc`k}517+oc`;G&WC;(b*zM%0DTd}ws9-ops^xI#ImBko>~{{BFh|*2 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 Fe*i4x!O{Q# literal 0 HcmV?d00001 diff --git a/FNAF_Clone/Content/images/enemies-definition.xml b/FNAF_Clone/Content/images/enemies-definition.xml index 76e73d4..5110c40 100755 --- a/FNAF_Clone/Content/images/enemies-definition.xml +++ b/FNAF_Clone/Content/images/enemies-definition.xml @@ -1,15 +1,17 @@  - + images/SpriteSheet_enemies - - - - - - - - - + + + + + + + + + + + diff --git a/FNAF_Clone/Content/images/monitor-definition.xml b/FNAF_Clone/Content/images/monitor-definition.xml index c80bec1..0c48d78 100644 --- a/FNAF_Clone/Content/images/monitor-definition.xml +++ b/FNAF_Clone/Content/images/monitor-definition.xml @@ -1,37 +1,40 @@ - + images/SpriteSheet_monitor - - - - - + + + + + - - + + - - - - - - + + + + + + - - - - - - + + + + + + - - - + + + + + + + + + - - - diff --git a/FNAF_Clone/Content/images/office-definition.xml b/FNAF_Clone/Content/images/office-definition.xml index 09f8b3f..326be52 100755 --- a/FNAF_Clone/Content/images/office-definition.xml +++ b/FNAF_Clone/Content/images/office-definition.xml @@ -1,13 +1,13 @@  - + images/SpriteSheet_office - - - + + + - - - + + + diff --git a/FNAF_Clone/Content/images/rooms-definition.xml b/FNAF_Clone/Content/images/rooms-definition.xml new file mode 100755 index 0000000..fea7d41 --- /dev/null +++ b/FNAF_Clone/Content/images/rooms-definition.xml @@ -0,0 +1,32 @@ + + + images/SpriteSheet_map + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/FNAF_Clone/EventProcessor.cs b/FNAF_Clone/EventProcessor.cs index fc89e2c..21c86dc 100644 --- a/FNAF_Clone/EventProcessor.cs +++ b/FNAF_Clone/EventProcessor.cs @@ -95,7 +95,7 @@ public class EventProcessor { case 10: Console.WriteLine($"E: Spot:{e.Args[0]} turned {(e.Args[1] == 1 ? "on" : " off")}"); - ClientEnemyManager.Get(e.Args[0]).Sprite.SetTexture(e.Args[1] == 1 ? 0 : 2); + ClientEnemyManager.Get(e.Args[0]).Sprite.SetTexture(e.Args[1] == 1 ? 0 : 1); break; case 11: @@ -110,7 +110,7 @@ public class EventProcessor { break; case 13: - Console.WriteLine($"E: power tick {e.Args[0]}: {e.Args[1]}"); + // Console.WriteLine($"E: power tick {e.Args[0]}: {e.Args[1]}"); if (e.Args[0] == Client.Player.state.pid){ Client.Player.state.power = e.Args[1]; } @@ -120,7 +120,7 @@ public class EventProcessor { break; - case 14: + case 14: // powerout Console.WriteLine($"E: Player {e.Args[0]} powered out"); ClientMapManager.GetAllConnectors().Where(c => (c.Type == ConnectorType.DOOR_REMOTE || c.Type == ConnectorType.DOOR_OFFICE) && @@ -130,6 +130,9 @@ public class EventProcessor { if(c.Type == ConnectorType.DOOR_REMOTE) UIManager.ChangeRemoteDoorState(c.Id, false); }); + foreach (var tile in ClientMapManager.GetAllTiles()){ + tile.Lit = false; + } if (e.Args[0] == Client.Player.state.pid){ UIManager.ChangeDoorState(Direction.EAST, false); @@ -147,6 +150,16 @@ public class EventProcessor { break; + case 15: // light + bool lightState = e.Args[2] == 1; + Console.WriteLine($"E: Player {e.Args[0]} {(lightState ? "lit": "unlit")} tile {e.Args[1]}"); + if (e.Args[0] == Client.Player.state.pid){ + if (ClientMapManager.Get(e.Args[1]).Lit != lightState) Console.WriteLine("!!! DESYNC: LIGHT STATE"); + break; + } + + ClientMapManager.Get(e.Args[1]).Lit = lightState; + break; } } diff --git a/FNAF_Clone/GUI/EnemyUIElement.cs b/FNAF_Clone/GUI/EnemyUIElement.cs new file mode 100644 index 0000000..bd3471a --- /dev/null +++ b/FNAF_Clone/GUI/EnemyUIElement.cs @@ -0,0 +1,33 @@ +using System.Linq; +using Microsoft.Xna.Framework; +using MonoGameLibrary.Graphics; + +namespace FNAF_Clone.GUI; + +public class EnemyUIElement : UIElement { + private int unlitTexturesId; + private bool currentlyLit = true; + + public EnemyUIElement(TextureRegion litTexture, TextureRegion unlitTexture, Point position, int drawPriority = 0) : base([litTexture, unlitTexture], position, drawPriority) { + unlitTexturesId = 1; + } + + public EnemyUIElement(TextureRegion[] litTextures, TextureRegion[] unlitTextures, Point position, int drawPriority = 0) : base(litTextures.Concat(unlitTextures).ToArray(), position, drawPriority) { + unlitTexturesId = litTextures.Length; + } + + public void SetTexture(bool lit, int id) { + currentlyLit = lit; + base.SetTexture(lit ? id : id + unlitTexturesId); + } + + public override void SetTexture(int id) { + base.SetTexture(currentlyLit ? id : id + unlitTexturesId); + } + + public void SetTexture(bool lit) { + if(lit == currentlyLit) return; + currentlyLit = lit; + SetTexture(lit ? currentTextureId - unlitTexturesId : currentTextureId); + } +} \ No newline at end of file diff --git a/FNAF_Clone/GUI/Screen.cs b/FNAF_Clone/GUI/Screen.cs index 008d39b..1b22436 100644 --- a/FNAF_Clone/GUI/Screen.cs +++ b/FNAF_Clone/GUI/Screen.cs @@ -68,6 +68,8 @@ public class Screen { public string Label{ get; } private Dictionary elements = new(); + private List elementsInDrawOrder = new(); + public bool Active { get; private set; } = false; private InputListenerHook mouseInputHook = new(true); private bool temporary = false; @@ -87,6 +89,15 @@ public class Screen { public UIElement AddElement(string id, UIElement element) { elements.Add(id, element); + + int insertIndex = elementsInDrawOrder.FindLastIndex(e => e.DrawPriority == element.DrawPriority); + if (insertIndex == -1){ + elementsInDrawOrder.Add(element); + } + else{ + elementsInDrawOrder.Insert(insertIndex + 1, element); + } + return element; } @@ -120,7 +131,7 @@ public class Screen { } public void Draw(SpriteBatch spriteBatch) { - foreach (var val in elements.Values){ + foreach (var val in elementsInDrawOrder){ val.Draw(spriteBatch); } } diff --git a/FNAF_Clone/GUI/UIElement.cs b/FNAF_Clone/GUI/UIElement.cs index 1f5b5ed..c0f29be 100644 --- a/FNAF_Clone/GUI/UIElement.cs +++ b/FNAF_Clone/GUI/UIElement.cs @@ -13,6 +13,7 @@ public class UIElement { public bool Active { get; set; } = true; public bool Pressable { get; set; } = false; public bool Visible { get; set; } = true; + public int DrawPriority { get; } = 0; protected (Point, Point) _bounds; public (Point, Point) Bounds{ @@ -45,14 +46,16 @@ public class UIElement { screenSpaceBounds = (Bounds.Item1.MultiplyByScalar(pixelScaleMultiplier), Bounds.Item2.MultiplyByScalar(pixelScaleMultiplier)); } - public UIElement(TextureRegion texture, Point position) { + public UIElement(TextureRegion texture, Point position, int drawPriority = 0) { Textures.Add(texture); Bounds = (position, position + new Point(texture.Width, texture.Height)); + DrawPriority = drawPriority; LoadPixelScaleMultiplier(); } - public UIElement(TextureRegion[] textures, Point position) { + public UIElement(TextureRegion[] textures, Point position, int drawPriority = 0) { this.Textures.AddRange(textures); Bounds = (position, position + new Point(textures[0].Width, textures[0].Height)); + DrawPriority = drawPriority; LoadPixelScaleMultiplier(); } @@ -62,7 +65,7 @@ public class UIElement { LoadPixelScaleMultiplier(); } - public void SetTexture(int textureId) { + public virtual void SetTexture(int textureId) { if (textureId >= Textures.Count){ Console.WriteLine($"WARNING: TEXTURE {textureId} OUT OF BOUNDS"); return; diff --git a/FNAF_Clone/GUI/UIManager.cs b/FNAF_Clone/GUI/UIManager.cs index b54afb4..5ed230b 100644 --- a/FNAF_Clone/GUI/UIManager.cs +++ b/FNAF_Clone/GUI/UIManager.cs @@ -7,6 +7,7 @@ using FNAF_Clone.Map; using GlobalClassLib; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; using MonoGameLibrary; using MonoGameLibrary.Graphics; using MonoGameLibrary.Input; @@ -37,6 +38,7 @@ public class UIManager { public static TextureAtlas OfficeAtlas{ get; private set; } public static TextureAtlas MonitorAtlas{ get; private set; } public static TextureAtlas EnemyAtlas{ get; private set; } + public static TextureAtlas RoomAtlas{ get; private set; } public static SpriteFont PixelMonoFont{ get; private set; } public static int GlobalPixelMultiplier{ get; private set; } @@ -44,6 +46,8 @@ public class UIManager { // private Dictionary<(int, int), UIElement> doorElements = new(); private static Dictionary enemyElements = new(); private static TimerUIElement timerElement; + private static UIElement cameraView; + private static Dictionary lightIndicators = new(); private static InputListenerHook monitorSwitchHook; @@ -52,6 +56,7 @@ public class UIManager { OfficeAtlas = TextureAtlas.FromFile(Core.content, "images/office-definition.xml"); MonitorAtlas = TextureAtlas.FromFile(Core.content, "images/monitor-definition.xml"); EnemyAtlas = TextureAtlas.FromFile(Core.content, "images/enemies-definition.xml"); + RoomAtlas = TextureAtlas.FromFile(Core.content, "images/rooms-definition.xml"); PixelMonoFont = Core.content.Load("ponderosa"); } @@ -62,21 +67,28 @@ public class UIManager { // Screen.SetScreen(ScreenTypes.OFFICE); // Screen.SetOverlayScreen(ScreenTypes.OVERLAY); - officeScreen.AddElement("office_left", new UIElement([OfficeAtlas[3], OfficeAtlas[0]], Point.Zero)); - officeScreen.AddElement("office_centre", new UIElement([OfficeAtlas[4], OfficeAtlas[1]], new Point(200, 0))); - officeScreen.AddElement("office_right", new UIElement([OfficeAtlas[5], OfficeAtlas[2]], new Point(440, 0))); + officeScreen.AddElement("office_left", new UIElement([OfficeAtlas["left-open"], OfficeAtlas["left-closed"]], Point.Zero)); + officeScreen.AddElement("office_centre", new UIElement([OfficeAtlas["centre-open"], OfficeAtlas["centre-closed"]], new Point(200, 0))); + officeScreen.AddElement("office_right", new UIElement([OfficeAtlas["right-open"], OfficeAtlas["right-closed"]], new Point(440, 0))); // officeScreen.AddElement("test", // new UIElement(testAtlas[0], Point.Zero) // {Pressable = true, OnMousePress = () => Console.WriteLine("Pressed!")} // ); - monitorScreen.AddElement("screen", new UIElement(MonitorAtlas[0], Point.Zero)); - monitorScreen.AddElement("view-frame", new UIElement(MonitorAtlas[1], new Point(62, 55))); - monitorScreen.AddElement("map-frame", new UIElement(MonitorAtlas[2], new Point(334, 135))); - monitorScreen.AddElement("map", new UIElement(MonitorAtlas[3], new Point(334, 135))); - + monitorScreen.AddElement("screen", new UIElement(MonitorAtlas["screen"], Point.Zero)); + monitorScreen.AddElement("view-frame", new UIElement(MonitorAtlas["view-frame"], new Point(62, 55))); + monitorScreen.AddElement("map-frame", new UIElement(MonitorAtlas["map-frame"], new Point(334, 135))); + monitorScreen.AddElement("map", new UIElement(MonitorAtlas["map"], new Point(334, 135))); + List rooms = new(); + for (int i = 0; i < ClientMapManager.MAP_SIDE_LENGTH * ClientMapManager.MAP_SIDE_LENGTH; i++){ + rooms.Add(RoomAtlas["room" + i]); + } + cameraView = new UIElement(rooms.ToArray(), new(64, 64)); + monitorScreen.AddElement("camera-view", cameraView); + + // main menu timerElement = new(new(0, 0), PixelMonoFont); overlayScreen.AddElement("timer", timerElement); officeScreen.AddElement("power-p1-office", new PowerIndicator(new(timerElement.Bounds.Item1.X, timerElement.Bounds.Item2.Y + 5), PixelMonoFont, Client.Player, "POWER: ")); @@ -126,22 +138,23 @@ public class UIManager { CommandManager.AllowGameControls(true); UpdateCameras([Client.Player.state.camera]); // in case there is an enemy on the default camera - + cameraView.SetTexture(Client.Player.state.camera); } public static void StartTimer() { timerElement.Start(); } public static void SpawnMapElements(TileConnectorProjection[] doors) { - for (int i = 0; i < 5; i++){ // NOTE: this loop does y in reverse, y labels are inverted to match server for (int j = 0; j < 5; j++){ - int i1 = i; - int j1 = j; + int id = ClientMapManager.CoordsToId(i, 4 - j); + if (Client.Player.state.officeTileId == id || Client.Opponent.state.officeTileId == id) continue; // TODO: remove the other check for office Point point1 = new Point(336 + (32 * i), 144 + (32 * j)); Point point2 = new Point(367 + (32 * i), 175 + (32 * j)); - monitorScreen.AddElement($"room{ClientMapManager.CoordsToId(i, 4 - j)}", new UIElement(point1, point2) - {Pressable = true, OnMousePress = (() => CommandManager.SendChangeCamera(ClientMapManager.Get((i1, 4 - j1)).Id))}); + monitorScreen.AddElement($"room{id}", new UIElement(point1, point2) + {Pressable = true, OnMousePress = (() => CommandManager.SendChangeCamera(id))}); + lightIndicators.Add(id, monitorScreen.AddElement($"light{id}", new UIElement(MonitorAtlas["map-light-indicator"], point1){Visible = false})); + // // if (doorPositions.ContainsKey((i, j))){ // monitorScreen.AddElement("door"+doorPositions[(i, j)], new UIElement([monitorAtlas[5], monitorAtlas[6]], point1)); @@ -149,8 +162,8 @@ public class UIManager { } } - monitorScreen.AddElement("eye-player", new UIElement(MonitorAtlas[24], monitorScreen["room"+Client.Player.state.camera].Bounds.Item1)); - monitorScreen.AddElement("eye-opponent", new UIElement([MonitorAtlas[23], MonitorAtlas[22]], monitorScreen["room"+Client.Opponent.state.camera].Bounds.Item1)); + monitorScreen.AddElement("eye-player", new UIElement(MonitorAtlas["eye-small-player"], monitorScreen["room"+Client.Player.state.camera].Bounds.Item1)); + monitorScreen.AddElement("eye-opponent", new UIElement([MonitorAtlas["eye-small-opponent-closed"], MonitorAtlas["eye-small-opponent-open"]], monitorScreen["room"+Client.Opponent.state.camera].Bounds.Item1)); foreach (var door in doors){ if(door.Type != ConnectorType.DOOR_REMOTE) continue; @@ -161,16 +174,16 @@ public class UIManager { int targetId = door.Tiles.tile1.GridPosition.y > door.Tiles.tile2.GridPosition.y ? door.Tiles.tile1.Id : door.Tiles.tile2.Id; UIElement tile = monitorScreen["room"+targetId]; - monitorScreen.AddElement("door"+Math.Max(door.Tiles.tile1.Id, door.Tiles.tile2.Id)+"-"+Math.Min(door.Tiles.tile1.Id, door.Tiles.tile2.Id), new UIElement([MonitorAtlas[5], MonitorAtlas[6]], tile.Bounds.Item1)); + monitorScreen.AddElement("door"+Math.Max(door.Tiles.tile1.Id, door.Tiles.tile2.Id)+"-"+Math.Min(door.Tiles.tile1.Id, door.Tiles.tile2.Id), new UIElement([MonitorAtlas["door-remote-open"], MonitorAtlas["door-remote-closed"]], tile.Bounds.Item1)); } } - monitorScreen.AddElement("p1-office-door-left", new UIElement([MonitorAtlas[7], MonitorAtlas[8]], new Point(400, 272))); - monitorScreen.AddElement("p1-office-door-centre", new UIElement([MonitorAtlas[9], MonitorAtlas[10]], new Point(400, 272))); - monitorScreen.AddElement("p1-office-door-right", new UIElement([MonitorAtlas[11], MonitorAtlas[12]], new Point(400, 272))); - monitorScreen.AddElement("p2-office-door-right", new UIElement([MonitorAtlas[13], MonitorAtlas[14]], new Point(400, 144))); - monitorScreen.AddElement("p2-office-door-centre", new UIElement([MonitorAtlas[15], MonitorAtlas[16]], new Point(400, 144))); - monitorScreen.AddElement("p2-office-door-left", new UIElement([MonitorAtlas[17], MonitorAtlas[18]], new Point(400, 144))); + monitorScreen.AddElement("p1-office-door-left", new UIElement([MonitorAtlas["door-office-p1-left-open"], MonitorAtlas["door-office-p1-left-closed"]], new Point(400, 272))); + monitorScreen.AddElement("p1-office-door-centre", new UIElement([MonitorAtlas["door-office-p1-centre-open"], MonitorAtlas["door-office-p1-centre-closed"]], new Point(400, 272))); + monitorScreen.AddElement("p1-office-door-right", new UIElement([MonitorAtlas["door-office-p1-right-open"], MonitorAtlas["door-office-p1-right-closed"]], new Point(400, 272))); + monitorScreen.AddElement("p2-office-door-right", new UIElement([MonitorAtlas["door-office-p2-right-open"], MonitorAtlas["door-office-p2-right-closed"]], new Point(400, 144))); + monitorScreen.AddElement("p2-office-door-centre", new UIElement([MonitorAtlas["door-office-p2-centre-open"], MonitorAtlas["door-office-p2-centre-closed"]], new Point(400, 144))); + monitorScreen.AddElement("p2-office-door-left", new UIElement([MonitorAtlas["door-office-p2-left-open"], MonitorAtlas["door-office-p2-left-closed"]], new Point(400, 144))); } @@ -235,17 +248,28 @@ public class UIManager { public static void ChangeCamera(int id) { monitorScreen["eye-player"].SetPosition(monitorScreen["room"+id].Bounds.Item1); + cameraView.SetTexture(id); UpdateCameras([id]); } public static void UpdateCameras(int[] camIds) { + foreach (var id in camIds){ + MapTileProjection tile = ClientMapManager.Get(id); + if(tile.Owner == null) continue; + lightIndicators[id].Visible = tile.Lit; + } + if (camIds.Contains(Client.Player.state.camera)){ + bool lit = ClientMapManager.Get(Client.Player.state.camera).Lit; + cameraView.Visible = lit; enemyElements.Values.Where(e => e.Visible).ToList().ForEach(e => e.Visible = false); - ClientEnemy[] enemies = ClientEnemyManager.GetByLocation(ClientMapManager.Get(Client.Player.state.camera));; + ClientEnemy[] enemies = ClientEnemyManager.GetByLocation(ClientMapManager.Get(Client.Player.state.camera)); foreach (var enemy in enemies){ enemyElements.TryGetValue(enemy.Id, out var element); if (element == null) continue; - element.Visible = true; + EnemyUIElement enemyElement = (EnemyUIElement)element; + enemyElement.Visible = true; + enemyElement.SetTexture(lit); } } } @@ -269,15 +293,16 @@ public class UIManager { Screen.SetScreen(ScreenTypes.WIN); Screen.DisableOverlay(); CommandManager.AllowGameControls(false); + InputManager.AddListener(Keys.Space, DisplayMainMenu, InputTiming.PRESS, new InputListenerHook(true, true)); } public static void ShowDeathScreen() { Screen.SetScreen(ScreenTypes.LOSE); Screen.DisableOverlay(); CommandManager.AllowGameControls(false); + InputManager.AddListener(Keys.Space, DisplayMainMenu, InputTiming.PRESS, new InputListenerHook(true, true)); } - // private static Point GetRoomUIPos((int x, int y) pos) { // return new Point(336 + (32 * pos.x), 144 + (32 * pos.y)); // } diff --git a/FNAF_Clone/Map/ClientMapManager.cs b/FNAF_Clone/Map/ClientMapManager.cs index 7f1df2d..d4f8aac 100644 --- a/FNAF_Clone/Map/ClientMapManager.cs +++ b/FNAF_Clone/Map/ClientMapManager.cs @@ -1,7 +1,11 @@ using System; +using System.Collections; using System.Collections.Generic; +using System.Linq; using System.Runtime.InteropServices.JavaScript; +using FNAF_Clone.GUI; using GlobalClassLib; +using PacketLib; namespace FNAF_Clone.Map; @@ -11,11 +15,18 @@ public class ClientMapManager { public static MapTileProjection Get(int tileId) => Get(IdToCoords(tileId)); private static bool inverted; - public static void InitMap(bool invert = false) { - inverted = invert; + public static void InitMap(int[] connectors, int[] yourTiles, int[] opponentTiles, int[] litTiles, bool upsideDown ) { - IdToCoords = invert ? _IdToCoordsInverse : _IdToCoords; - CoordsToId = invert ? _CoordsToIdInverse : _CoordsToId; + (int id1, int id2, ConnectorType type, ClientPlayer? owner)[] connectorsData = new (int, int , ConnectorType, ClientPlayer?)[connectors.Length / 4]; + for (int i = 0; i < connectors.Length / 4; i++){ + connectorsData[i] = (connectors[i * 4], connectors[i * 4 + 1], (ConnectorType)connectors[i * 4 + 2], connectors[i * 4 + 3] == -1 ? null : Client.GetPlayer(connectors[i * 4 + 3])); + } + // ClientMapManager.InitMap(upsideDown); + + inverted = upsideDown; + + IdToCoords = upsideDown ? _IdToCoordsInverse : _IdToCoords; + CoordsToId = upsideDown ? _CoordsToIdInverse : _CoordsToId; for (int i = 0; i < 5; i++){ for (int j = 0; j < 2; j++){ @@ -27,6 +38,26 @@ public class ClientMapManager { } } + + foreach (var tileId in yourTiles){ + Get(tileId).Owner = Client.Player; + } + + foreach (var tileId in opponentTiles){ + Get(tileId).Owner = Client.Opponent; + } + + foreach (var tileId in litTiles){ + Get(tileId).Lit = true; + } + + + TileConnectorProjection[] connectorProjections = connectorsData.Select(c => new TileConnectorProjection(Get(c.id1), Get(c.id2), c.type){Owner = c.owner}).ToArray(); + InitConnectors(connectorProjections); + + UIManager.SpawnMapElements(connectorProjections.Where(c => c.Type == ConnectorType.DOOR_REMOTE).ToArray()); + + } public static void InitConnectors(TileConnectorProjection[] connectors) { @@ -58,12 +89,21 @@ public class ClientMapManager { } - public const int ID_X_OFFSET = 5; // map grid height + public const int MAP_SIDE_LENGTH = 5; // map grid height public static Func CoordsToId{ get; private set; } public static Func IdToCoords{ get; private set; } - private static Func _IdToCoords = id => (id / ID_X_OFFSET, id % ID_X_OFFSET); - private static Func _IdToCoordsInverse = id => (ID_X_OFFSET - 1 - (id / ID_X_OFFSET), ID_X_OFFSET - 1 - (id % ID_X_OFFSET)); - private static Func _CoordsToId = (x, y) => x * ID_X_OFFSET + y; - private static Func _CoordsToIdInverse = (x, y) => (ID_X_OFFSET - 1 - x) * ID_X_OFFSET + (ID_X_OFFSET - 1 - y); + private static Func _IdToCoords = id => (id / MAP_SIDE_LENGTH, id % MAP_SIDE_LENGTH); + private static Func _IdToCoordsInverse = id => (MAP_SIDE_LENGTH - 1 - (id / MAP_SIDE_LENGTH), MAP_SIDE_LENGTH - 1 - (id % MAP_SIDE_LENGTH)); + private static Func _CoordsToId = (x, y) => x * MAP_SIDE_LENGTH + y; + private static Func _CoordsToIdInverse = (x, y) => (MAP_SIDE_LENGTH - 1 - x) * MAP_SIDE_LENGTH + (MAP_SIDE_LENGTH - 1 - y); + + public static MapTileProjection[] GetAllTiles() { + List tiles = new(); + foreach (var tile in map){ + tiles.Add(tile); + } + + return tiles.ToArray(); + } } \ No newline at end of file diff --git a/FNAF_Clone/Map/MapTileProjection.cs b/FNAF_Clone/Map/MapTileProjection.cs index 1be47a5..551565a 100644 --- a/FNAF_Clone/Map/MapTileProjection.cs +++ b/FNAF_Clone/Map/MapTileProjection.cs @@ -4,6 +4,8 @@ namespace FNAF_Clone.Map; public class MapTileProjection : GlobalMapTile { public ClientPlayer? Owner { get; set; } + public MapTileProjection(int id) : base(id, ClientMapManager.IdToCoords(id)) { + Lit = false; } } \ No newline at end of file diff --git a/FNAF_Server/CommandProcessor.cs b/FNAF_Server/CommandProcessor.cs index 4dd2eef..43f5d17 100644 --- a/FNAF_Server/CommandProcessor.cs +++ b/FNAF_Server/CommandProcessor.cs @@ -51,6 +51,19 @@ public class CommandProcessor { Console.WriteLine($"C: Player {pid} {(door.Blocked ? "closed" : "opened")} door {(playerCommand.Args[0], playerCommand.Args[1])}"); Server.SendUpdateToAll([GameEvent.TOGGLE_DOOR_REMOTE(pid, (playerCommand.Args[0], playerCommand.Args[1]), door.Blocked)]); break; + case 4: + bool lit = playerCommand.Args[1] == 1; + MapTile lightTile = MapManager.Get(playerCommand.Args[0]); + lightTile.Lit = lit; + if (lit){ + GameLogic.PowerConsumers[lightTile] = (GameLogic.LightUsage, pid); + } + else{ + GameLogic.PowerConsumers.Remove(lightTile); + } + + Server.SendUpdateToAll([GameEvent.TOGGLE_LIGHT(pid, playerCommand.Args[0], lit)]); + break; } } } diff --git a/FNAF_Server/Enemies/RoamingPathfinder.cs b/FNAF_Server/Enemies/RoamingPathfinder.cs index 5260d58..3aaf023 100644 --- a/FNAF_Server/Enemies/RoamingPathfinder.cs +++ b/FNAF_Server/Enemies/RoamingPathfinder.cs @@ -18,7 +18,8 @@ public abstract class RoamingPathfinder : Pathfinder{ c => (!c.Blocked || c.Type == ConnectorType.DOOR_OFFICE) && tile != Enemy.Location, t => (!distances.ContainsKey(t) || distances[t] > distances[tile]) && - (!Enemy.BlocksTile || EnemyManager.GetByLocation(t).All(e => !e.BlocksTile || e == Enemy))); + (!Enemy.BlocksTile || EnemyManager.GetByLocation(t).All(e => !e.BlocksTile || e == Enemy)) && + Server.Players.All(p => p.Value.state.officeTileId != t.Id)); neighbours.ForEach(t => distances[t] = distances[tile] + tile.GetConnector(t)!.Value); diff --git a/FNAF_Server/GameLogic.cs b/FNAF_Server/GameLogic.cs index 4026467..88aa4d4 100644 --- a/FNAF_Server/GameLogic.cs +++ b/FNAF_Server/GameLogic.cs @@ -19,6 +19,7 @@ public class GameLogic { public static int OfficeDoorUsage{ get; set; } = 10; public static int RemoteDoorUsage{ get; set; } = 3; + public static int LightUsage{ get; set; } = 2; // public const int POWER_MAX = 1000; // public static int P1Power{ get; set; } = Power.MAX_POWER_VALUE; @@ -46,12 +47,22 @@ public class GameLogic { connectorsConverted[i * 4 + 2] = (int)connectors[i].Type; connectorsConverted[i * 4 + 3] = connectors[i].Owner == null ? -1 : connectors[i].Owner.state.pid; } - Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false}, Server.P1.peer); - Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true}, Server.P2.peer); + + List p1Tiles = new(); + List p2Tiles = new(); + List neutralTiles = new(); + foreach (var tile in MapManager.GetAllTiles()){ + if(tile.Owner == null) neutralTiles.Add(tile.Id); + else if(tile.Owner == Server.P1) p1Tiles.Add(tile.Id); + else if(tile.Owner == Server.P2) p2Tiles.Add(tile.Id); + } + + Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = false, YourTiles = p1Tiles.ToArray(), OpponentTiles = p2Tiles.ToArray(), LitTiles = neutralTiles.ToArray()}, Server.P1.peer); + Server.SendPacket(new MapInitPacket{Connectors = connectorsConverted, UpsideDown = true, YourTiles = p2Tiles.ToArray(), OpponentTiles = p1Tiles.ToArray(), LitTiles = neutralTiles.ToArray()}, Server.P2.peer); - EnemyManager.AddEnemy(new LurkEnemy(0)).Spawn(MapManager.Get(12)); - EnemyManager.AddEnemy(new NekoEnemy(0)).Spawn(MapManager.Get(2)); EnemyManager.AddEnemy(new SpotEnemy(0)).Spawn(MapManager.Get(12)); + EnemyManager.AddEnemy(new LurkEnemy(0)).Spawn(MapManager.Get(12)); + EnemyManager.AddEnemy(new NekoEnemy(10)).Spawn(MapManager.Get(2)); Thread.Sleep(3000); secondCycleTimer.Start(); @@ -109,6 +120,9 @@ public class GameLogic { foreach (var door in MapManager.GetDoors(player)){ door.Blocked = false; } + foreach (var tile in MapManager.GetAllTiles().Where(t => t.Owner == player)){ + tile.Lit = false; + } PowerConsumers.Where(c => c.Value.pid == player.state.pid).ToList().ForEach(c => PowerConsumers.Remove(c.Key)); Server.SendUpdateToAll([GameEvent.POWER_OUT(player.state.pid)]); } diff --git a/FNAF_Server/Map/MapManager.cs b/FNAF_Server/Map/MapManager.cs index afcc85f..e7c0f6e 100644 --- a/FNAF_Server/Map/MapManager.cs +++ b/FNAF_Server/Map/MapManager.cs @@ -73,7 +73,16 @@ public static class MapManager { return connectors.ToArray(); } - + + public static MapTile[] GetAllTiles() { + List tiles = new(); + + foreach (var tile in map){ + tiles.Add(tile); + } + + return tiles.ToArray(); + } public const int ID_X_OFFSET = 5; // map grid height public static int CoordsToId(int x, int y) => x * ID_X_OFFSET + y; diff --git a/MonoGameLibrary b/MonoGameLibrary index 182ebfc..8f24103 160000 --- a/MonoGameLibrary +++ b/MonoGameLibrary @@ -1 +1 @@ -Subproject commit 182ebfc31c37c0759b5a41c1921273f1ba55b759 +Subproject commit 8f241032d24da10a497b5923f532641253c9b84c diff --git a/PacketLib/GameEvent.cs b/PacketLib/GameEvent.cs index 94a9dc7..24586d3 100644 --- a/PacketLib/GameEvent.cs +++ b/PacketLib/GameEvent.cs @@ -23,6 +23,7 @@ public struct GameEvent : INetSerializable{ public static GameEvent GAME_START() => new(){ID = 12}; public static GameEvent POWER_TICK(int pid, int power) => new(){ID = 13, Args = [pid, power]}; public static GameEvent POWER_OUT(int pid) => new(){ID = 14, Args = [pid]}; + public static GameEvent TOGGLE_LIGHT(int pid, int camId, bool state) => new(){ID = 15, Args = [pid, camId, state ? 1 : 0]}; public int ID{ get; set; } public bool Hideable => ID < 0; diff --git a/PacketLib/MapInitPacket.cs b/PacketLib/MapInitPacket.cs index d29350d..16f2cab 100644 --- a/PacketLib/MapInitPacket.cs +++ b/PacketLib/MapInitPacket.cs @@ -4,5 +4,6 @@ public class MapInitPacket { public int[] Connectors { get; set; } // quadruplets (tile1 id, tile2 id, type, owner) public int[] YourTiles { get; set; } public int[] OpponentTiles { get; set; } + public int[] LitTiles{ get; set; } public bool UpsideDown { get; set; } } \ No newline at end of file diff --git a/PacketLib/PlayerCommand.cs b/PacketLib/PlayerCommand.cs index 37b7594..fd6b7f8 100644 --- a/PacketLib/PlayerCommand.cs +++ b/PacketLib/PlayerCommand.cs @@ -8,6 +8,7 @@ public struct PlayerCommand : INetSerializable { public static PlayerCommand SET_MONITOR(bool state) => new(){ID = 1, Args = [state ? 1 : 0]}; public static PlayerCommand SET_DOOR_OFFICE(Direction direction, bool state) => new(){ID = 2, Args = [(int)direction, state ? 1 : 0]}; public static PlayerCommand SET_DOOR_REMOTE((int, int) remoteDoorId, bool state) => new(){ID = 3, Args = [remoteDoorId.Item1, remoteDoorId.Item2, state ? 1 : 0]}; + public static PlayerCommand SET_LIGHT(int camId, bool state) => new(){ID = 4, Args = [camId, state ? 1 : 0]}; public int ID{ get; set; } public bool Hideable => ID < 0; public int[] Args{ get; private set; }