From 0c330c3aa1bc35776037a0687f03667718ca3d4c Mon Sep 17 00:00:00 2001 From: OneLoneCoder Date: Sun, 1 Jul 2018 14:27:29 +0100 Subject: [PATCH] Added OpenGL CGE - Thanks KrossX Also Made CGEGL into CGEGLOOP too! --- Core/BattleRoyaleRules.lua | 54 + Core/BattleRoyale_Engine.cpp | 11 +- Releases/olcBattleRoyale_CGE_Big.exe | Bin 75264 -> 75776 bytes Releases/olcBattleRoyale_CGE_GL_Big.exe | Bin 0 -> 110080 bytes Releases/olcBattleRoyale_CGE_RasterFont.exe | Bin 0 -> 75776 bytes Releases/olcBattleRoyale_CGE_Small.exe | Bin 75264 -> 75776 bytes VS2018_BUILD_CGE/BattleRoyale_Console.h | 14 +- VS2018_BUILD_CGE/olcBattleRoyale.sln | 12 + VS2018_BUILD_CGE/olcBattleRoyale.vcxproj | 169 +- .../olcBattleRoyale.vcxproj.filters | 9 + VS2018_BUILD_CGE/olcConsoleGameEngineGL.h | 2043 +++++++++++++++++ .../olcConsoleGameEngineGLOOP.cpp | 1626 +++++++++++++ VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.h | 597 +++++ VS2018_BUILD_CGE/olcConsoleGameEngineOOP.cpp | 7 +- 14 files changed, 4534 insertions(+), 8 deletions(-) create mode 100644 Core/BattleRoyaleRules.lua create mode 100644 Releases/olcBattleRoyale_CGE_GL_Big.exe create mode 100644 Releases/olcBattleRoyale_CGE_RasterFont.exe create mode 100644 VS2018_BUILD_CGE/olcConsoleGameEngineGL.h create mode 100644 VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.cpp create mode 100644 VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.h diff --git a/Core/BattleRoyaleRules.lua b/Core/BattleRoyaleRules.lua new file mode 100644 index 0000000..6005d6e --- /dev/null +++ b/Core/BattleRoyaleRules.lua @@ -0,0 +1,54 @@ +-- This is a battle plan file. Here you specify the rules and constraints +-- of the battle. During battle you will not have control of these parameters +-- as they will be set by tournament host + +-- DO NOT CHANGE FROM HERE ======================================= + +-- Enable to stop on robot malfunction and show error message +DebugMode = true + +-- Maximum number of robots allowed to enter +MaxRobots = 8 + +-- Starting Health of robot +MaxRobotHealth = 10 + +-- Starting energy of robot (in seconds) +MaxRobotEnergy = 30.0 + +-- Speed of step movement +RobotMoveSpeed = 30.0 + +-- Duration of step movement +RobotMoveTime = 0.4 + +-- Speed of turn movement (rad/s) +RobotTurnSpeed = math.pi / 2.0 + +-- Duration of turn movement +RobotTurnTime = 0.5 + +-- Maximum Game Time +MaxGameTime = 600.0 + +-- Gun Cooldown Time +GunCooldown = 0.2 + +-- Bullet Damage +BulletDamage = 1 + +-- Bullet Speed +BulletSpeed = 100.0 + +-- Self Destruct Bullet count +SelfDestructBullets = 10 + +-- DO NOT CHANGE ABOVE HERE ======================================= + + +-- Robots can be loaded into teams +Teams = {} +Teams["OLC"] = {"javidbot.lua", "javidbot.lua", "javidbot.lua", "brankbot.lua"} +Teams["BadDudes"] = {"nullbot.lua", "nullbot.lua", "nullbot.lua", "nullbot.lua"} + + diff --git a/Core/BattleRoyale_Engine.cpp b/Core/BattleRoyale_Engine.cpp index ca79aa4..28d89bf 100644 --- a/Core/BattleRoyale_Engine.cpp +++ b/Core/BattleRoyale_Engine.cpp @@ -80,8 +80,8 @@ void OneLoneCoder_BattleRoyale::Update(float fElapsedTime) // Update Bullets for (auto &bullet : vecBullets) { - bullet.x += bullet.vx * 100.0f * fElapsedTime; - bullet.y += bullet.vy * 100.0f * fElapsedTime; + bullet.x += bullet.vx * BattleRoyale_Parameters::fBulletSpeed * fElapsedTime; + bullet.y += bullet.vy * BattleRoyale_Parameters::fBulletSpeed * fElapsedTime; // Has bullet left arena? if (bullet.x<0.0f || bullet.x > 200.0f || bullet.y <0.0f || bullet.y > 200.0f) @@ -102,7 +102,7 @@ void OneLoneCoder_BattleRoyale::Update(float fElapsedTime) robot->muxUpdatingSensors.lock(); if (robot->status.health > 0) { - robot->status.health--; + robot->status.health -= BattleRoyale_Parameters::nBulletDamage; if (robot->status.health <= 0) { robot->status.health = 0; @@ -131,6 +131,8 @@ void OneLoneCoder_BattleRoyale::Update(float fElapsedTime) if (nAliveRobots == 1) bBattleOver = true; + // Count unique teams that still have members that are alive + // Update Robots for (auto &robot : vecRobots) { @@ -282,6 +284,9 @@ float OneLoneCoder_BattleRoyale::GetBattleDuration() std::string OneLoneCoder_BattleRoyale::AddRobot(std::string sBotFile, int nTeamID) { + if (vecRobots.size() == BattleRoyale_Parameters::nMaxRobots) + return "TOO MANY ROBOTS"; + // Create new robot cRobot* robot = new cRobot(); robot->status.id = vecRobots.size(); diff --git a/Releases/olcBattleRoyale_CGE_Big.exe b/Releases/olcBattleRoyale_CGE_Big.exe index 5831d2dbfd092639c138d584cd4d4aa20be941bf..671efda7421ece04c7619ad4d02c616fbae1ea7b 100644 GIT binary patch delta 28267 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®RgiYh}rQ%h>0u{aOyR}}}} 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

OmP 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ⅈ>xJwjcm z-lP6h{jIuHeM{XL&iNtDYE8Ljqh`BikLCd8lTdB6HUTrr80|!Dt`_5;Y}bT$g|F7CoVHneUMp%jol+N|i_}HyVs)e76*KjA{Z!*jW1evVeBol_ zQe&ZUm9ZFJvCLRu++eIUZZa-5FEy_+pEEa`Tg_KdfnfBCc#8>rVw7d3WwB+crNm+@ zvs73rExRpM@aqkhM$2hSlcm|xX1QevwuV{NR=w4PY1JP@FfEp;FZ-P#bHXfzR)Me}PFmaaXDs_9X zxTx2Ox~sZdx?p{zUaz;HMtS(I`I$=$OiW1B zCtBbZGZV8DXC^ulHzaPt)UhjZPvYB&hZ2t`Hel$TOFW-=De-FJtwg0M$fPz|OfxZ) zSC}@L-ZmXF9XBys_X_T;I_i<1kJi<8TeE0Q-PZ^DSB1HeeMdxknsU98@!u2wf;utaM!v^aU| G|Nj90M9Av^ diff --git a/Releases/olcBattleRoyale_CGE_GL_Big.exe b/Releases/olcBattleRoyale_CGE_GL_Big.exe new file mode 100644 index 0000000000000000000000000000000000000000..c78127cb21f19b85377e87e3c1feabdec8f1c1ee GIT binary patch literal 110080 zcmeFaePC3@)jz(Q-6RVv+>Hbb8X=a|#s&omD7dHz$!3EP*btI{@@8vkn$n6Pn_wjn zyjha#aDm4MTig0nT5aXAK977`t*P3Y4Y(U#grF3IqK!)1i-|TM4+M0u;l)v= z=Z3v@b*p*aYgaF-`tI7SH8uBsr{?bOXMOALAN=6n+N^JWJFCX`gRJlVAggdeS=RUO zz31CEj2t=Ksb{@SduPl6<#$Dqf9lJxBQS1y7Jaf6L*mJ-_v| z;r)Q}+oJpV^LItZ@Vx$`7d@}>=e(l*{P}-8^j!GeZ>l#u|CuCeu7~vgrrMX51C| z)@@2Mk1&t2oAOY>AK)MUzBvO=2LfJt&lBM!lPLvn^cQ)W`T!(i;?H8r+r%SMI!er+ z(FXxdleU;mizv&sBvUs%jengc>AdtEKQ zeRLZNCA_GvI^K~aStirU8*1*kyY_CAX-g@fRQH8=7UCKAmxt6hh-jwlRD9cwf0Wmc z2E}W;p=NE(xA2MA6?Hb@f74F~#ml>)=G&|9MMPeA(7<#V;ypGf-fTes|G)nQ1;W>y zU6^m$zCC=+LH*gQKc|%m_(}cUslRLbd!PR7)Sub<_ip`tlK!5hKQr~`QT@A3e;?4_ zP5OI={2-XP_u6=5#IH`O>gF-3C`LEM*v(_uMT~ORE1&HDM7pz0+n;MT zok4*8by+0!!Gm$%e?QcB`F^XZHYI$`EMQHkO2cDLpd^8;c;6EL&hzWr)Z2kJ|AlpT zzA1OF(&XHWHx`hxPgPWYBBiZ%);6w7HYtV9EXjZQD0HHAD8&AW0AEq)a-Ossec zTQ^CW=XAI(NKHr3ukiJn)*t4Zq{`P3yy|5NPRJwh0vmojNWSDC{_VxT*YFS7ESB)k zW0fW!L6!)od93x4H2FaNaA|U9eX=xpPn|XBNjJ0N47Mg6g-vsvmYPV~u~E$9aOG!8 zO__LWynmTV@?R#9@K`^QmgZZ}NRE+>gaZg$Kh;f7-bbYsY0oU%;S_H4&LWE z?c)354!k8lM>$LmNDn?w1WHNHMj77lnAP5Vw6WFSRy!PTJH#7s%6ItNHhiyG$#<|( z^8F6e#ucG_F0<7|&SI9bFjVODlDuMhnIP^wR_JuHycq~OSh2O#iyCHW2d>8V&Odw} z4omo+l~X)RzF?Lf{4*k{~W|b<1+6Q*TvfJHEv8d`BLfQMc*d?+^H_eaP6=Cx-_8? zD1r9)JX&&?r8LlTL%w5`bj;&GbUA=%zHX$*W^1P8oT*q?iEdJwZ&Izd$$RZ=y)#Sk zOzDxT&Z6Ii$81Lh2*xTiSUX#go)ZYyI*SASNnUMSCMZ&wp1Y6LXP{^0oFPt-gO<@G5CSAtENJ3$6L`I)_P`k7ypwHJ|%5ULK{pVD^EjlFiJM!RpgN z`(mZmnd$12nyw#VGHqumJndEqFJIi;DWS$KL;=&qLWc16xbUNK;T@6iO(1oVjrj4s z$(+~b+XSmEN!|-KLsv-QY`IRlGobB96vNjdFgCb$Jgn881yGhQuys zpu-HJLp$QI1y1J4LPKVwwe;>8jjK~-mMb7N6(yTYlEXp8fmV*C&h=9E`v}~CPTqy* zdgnSRd%qOiUk<=$Qt*9rh^6mwgwmz7Elz4eL;zqZ`ay92)A&qptDR1SK9sIoyf?zN4*&C=4tdJ2>xfvN&FLHgfFKMBMBEzTdz|k}gN| zrJvy+sG!qxwbLso4QPaHOU?sN zO-u^#@;^s8@V|^ie2k;@u9OYxELloSwFJWr(xDG6e@Ev;=|NvYx;=GOz;>xKPmmY& zUh*aCz(3qY!+91x)$;kOU}K&!Lq}gH2_gOji+fw`Txr+_)UN+bWb zqSg=z1ek`Z#s`m8gjaHU-J?A=5(&W#3~ecx#i}={|3=aihldKCE0L#(@hsX$R^K$rGSR z(AqsnpU#q4{mPkZRw8UiwhrG2()@yj&MVCulj&h&wT-2 zJhVnNKa4u0E1PKCf&O3UoR-7G#mc5e0(sI|2Q*qvvG&@lSqe0N){ob0W}dk&vo=)@ zSfM4K;TfW* zcMQ#R<%W~dFq=*jT-CkX8$PD`*N4Ml9$3T!d@{_QpN1ECuibx6YHCC|Oq~S)XR@3_ z=TEUiF-E%;b7s5L{4>N-t&M^1q+7|lRdbE%o#^V6Hp`T{{aj#zZ#}CUaGhT3%Q+-p zu-5!QzA&-=yWR?I#btc@AM#U?h-?b^XGB9(D#1jhH7^Lm%CqE)j+)z8iG4N;Y`Vxx zyH>54=sHt3jvd=I!8e-C8E|#1wdVxnerrv#+&{4nRLBDr7M5y#X}mZVVkKwqQ9vt` zX~~M^tfVjZHSOQ=4NcOXX+oVMe`S^Ig8a%oC^PlGP+@1-(A~tuxbztr7 zImhKstu-^`Pbb#rvg6(|Z7i@4HB%Y%hdv)_CK*A0p=K(L{@CFacbyuBfFKAeS%JZ8 z{yO=Bqh=l}x+j0r1y0bL)Y6HrBhu#op)~t`ji)sNeg^Jcmcm#{?F|qlJsgyicR2V7c8+s|*C^0X^Pa!I0?FL>#O+9k2 zui-gQkL0`vk&yFZk#iw(&ZC@Xu%bzkoWI6%uA51@`f^2%lSGc!P>xwV#}6bO*I6RR zJdxuh)ABu0`joK~a<2_0C)h8_+4roVCKDcMBHpYPbr$k+?`6L6_$*m zbT&T&sg#lq(iUcd1OKF1Xg)qeX*e2cZl$d87ixZ41kmDp(FJ}YhYHVsUhQOOwC6`r z?X8NtRV}va0;N)!qLduez5^CvzE;+7RPh~EHe`m1vN|jBOo&8`uaG}Zkw0P=tX81VdOYIjWT)L%n+5E{G<# zp@y6iA$J?c&)w;AcS!!%XiQEN1-%djEhD)oU?1ECP9Omq5eMA9*lRDVfLLG)tTPwb zYO|Qz21}omW`;c{kL)@54%uVPSh4&rk|tIYOU>y}+ykw@dHU&ZP+w1CZv}cLSl-(7 z>C;a?JzQ$Km2g~OQ`eevN+PjZ>67B?r`Y=grzTkTvm-}7%xQOZ)Q?c74Oa`yu)xgj z^Plsj=Dx0!5A@90^f}Ls%OBNR7wEPKN&Gb~KBUd>q1vTdo1Hg0q_g~fH_|Dy2YN<% zwf_`gV97dF>s_k79hE8#`_7_br3(|P#w1p0)qJpWvI+8D2c%ggkmr@l;i4=_S3zzi zeCn5}*1RY#x+t3?MA>X1%3MN}O(Rh@nTxWy92*-e8DQ>yMM4g$HK2H`s1^^{qNEkw zRI5+LSeOK+)jqSCVr@l@M)GZxe_!qQScQ({k@FuCRfV1;kn|=ZNkdoNEHP|NXQczK zBWrKS*-yjP*J;@L8r$zJ)qXt!@|JgQlC?#o4nQcOcvHOi5d2~*W5+AmTpN49>N;FI zCc-eOa)H&cBElt>oc=)Ax`{YjlA4gaDW^lNGzTtNTM2}UDCPY}KF$eZFtD;SJ*k${ zt~1i?Gcg`0-UMz&;^sGi1Pcbbt%)oU0Jv29;&Xu~_<)xmIPZZFz()dK2$}txB6f?R*7(qTP|VZwge@yU@Nloq>zi zO_-1YpOV1)2=c+~Yw8^nUHj`?><9*f>8zNvgByf)@O2stzQzIzz1p#1|7?4fqA4ob zd>h+9tvMR4$xKSBTx7LElMp>Vh1xQSx8?0p6X|L>9n^}qLs=CaxlCPQMnk5uJ<76y zo)M~bxNDCzdk@I(Z}VAm-|o4c=z6=Qbfj7-`$sRfteC)Rj+ z{}k<)h)&5MbX**r=>nY_1Uh>Ak>W8H=={_Dk+aD|ZIEYE?(2sCB7 zUV)rZVrq4&jC8b!+y>ruscAZO(c9FyW*$W;=|BIER{BCQF9!ZG!E%wk$z^sw`s)Ss z*Na&DnSH?@j>+sW&#Ac&&8b>ni8gaYX1~Lm*^VV%jDSX&{XZ!jtjp}5srCmWa>XpF zfeBVoLeCv9v`^yQORAob*D)>rpOn`>;3F=0lw^Vt&#yohw6|AD2|`L=YB4H^kLKkxT*q*s z5K^-F>7-xY%u)>MQY~#N6&b6gx6IlL=mWsWN4Q(y9*0n zd*n8=^l;$(@ev)qWTj-FXGT=C6o?v zSZOd$#PJ~Y2ZuHcX&}(iYW{t`k!Yn(#fD|gW$iXhi0#oZS|EJQqfnAX+t6>74GOkc zF62OJA53O?GtoY0pv1yLkq$n>LS{|~tA}!b)&?J2X7j%-ZKXL~*k@;Ns#s^O(0&UQ z2iBWxwuj>FwH^7C9kX=h9Dx-lQ+|H{F>^w4_duDeye+lJI$ptU8!j*KrfPZmGh=Tb~OvEK^e+Y zn3jqiFlfQx>ne6gTVFy9b@qT|k5~0rD>3J$rBk2=>z7?gJw=j~>h_bI^Xw4SnFiwH zWa&EcssHT_x$LyQKFbOpADD}}pp226eF>@ds}W1w6jC;!_U-v$2Zpo3k$0EBl? zz88|#U&jh>9)Knedl@?nwD5+s4rJt2iw5|z?Ca1Ck%j!R6{=Z(66-*X8}v~Vfo~f>rt-OI4Q)nvAW?>5CYz(YhbZPh z4lPj6R`-YKlY1AVxw+66ZEjwi;i8f-8tUkR#5+CIHPtDv?g{b zkqf}y(xDG}lo3pNFbcu9fy0mtBVf+qu+PTM1U{Z%ITK%wE6BMc1Ke@9;EqgG$k9VA zL)FRD?hrhZfmU*`Lt>yhjFRJXA#ArTL`2RtFX&wJES_q8I@a%PQWKfT|B#LfadRew z9>J&pa$nPUaR)TByvDHp3!kDpft{5$=()3!Rgwv!fxgTCaE<2-!~M zA{wz$QxrLYnLbTB&6(tXFJG@GW^+r96mSC=xzIT!=P>1$g`ok10*x0%4rH1| zoX2U0mN^`=FXg_D$)4b4`QvmRfjB`9pao;G1aI!(>g*m=+AarrEr&s~!qfrKjL1-T zjqo4fvxFpgawxMM3@d4leDG>IS=9eZ(TFRgw9Aw`IIGIRbd*E{!tB-m9nPmTNXNt# zjfX%TyrU&S=Cg7K8?F@F*d11-*uJdYZo+pAE^w&GiYp%$dU#U6g94N9Jcryyo)mu* z*^<12uw!p2B`3kbEReGY<3UBB-vY^QSC;jv(_}S&fF1L)1KKudBj7Z3gH>rb>w0(X zcy^2x4UnLLA==^U*f`P^*f>nBL3G!EC$SO#EC9$>KUwn*!5*iZWd8vC1D> zU7yyx&QmhvfP9wq`>uDn2WnUI(#A4L83h4D<*~8KC>X3!B?Sbe`k(Sp|JpNvL`dSh zma=~rIF3dEBb2dh!GQb^o{Z8U@5@hRQO|X-5vV{L&uR22__&6d`=qkYx@3KEcU=-aMOYR5|s3!9HDM^Q&EppJ|zq_h#rTn93O z2$6fz3BQh-7umA2u7J<$dP8cWr3-;jYAiJu1Mm@32*o78ftH>za4mA6Bl5gC#_j3I z{!}uxc3UqefNvh56eN^Vz+#fa29~AOk0OUs;6UM0+9-uk_4M+*d-c3|u{>icnR0y= zeH>FOb_XI4N8VCe5=p+<4iJe)v?=Iah`1RM!K8y2h4V)`+1na0oUjm?9H6CSOIyi- zHt#3$`>B?8^ky~vRcbCwp;)F(DeqO&@!QYR6!%%8<+47Eovgfn!w6S| z0>=hSDbFgIQ$RInd3rv`M8rPK{(?+$mi1yCZ#3bDG{|feKAS0>1%&~c%F5C4d5!}0 ztLS4YYZy>wq7%ZOPk>XP5}6ejyNz8zK$M=Mj8|^K`#7qA$Faeq%mf<4=`G)}Vde^D z)NoabO&{HeVY~j7~{c@tyJH9hxGG31(vs%^>M}e0{aApK3CUz7%c`+ zYgd;~I@fOY4M&Ord{%41u0st@;jdO=SBt_&(6TJEnay>$?r>}vu4KNz`ewQZ*1yNF z3T8r*D5{))>@oR*Ny)Oa>_E;TIBud7vWxNK9_On}cpS#_Rb$}&B-P#+c+UcXi$wFL zk|?OVhWZAD9rmeGnsqAmjY7Qgo*|-vucgWJZgkWYNe)CPypbZ`$a{hHMux}=K&Y~m zW}m7cD_tz$Lmhf+%22tYR~^pT57HtoXh?WaH*!c^7{Lfl!w_jI)YW+3g8zjBQeg7D zTO4(>p(U`wTL_I?1RA%H#sG-kX}MxV`Fb36WBAaXN<+Jz-!O921n0hmQ51$*H&KHN zvpkN;MC#~sk@*NI%|d9Po4ET08ayV_N|Wa}>hej_p%lk3@Hvdj;5eQ$EP;3(fpmK9 z3L;ogbepe1SABJAc`a^Ux0`tts=Rh z<{K$HB4sS271Tar{NiO(saFvPL<>dpQ<)?e^%!#Q;B^d>7&dW z86V^3(`s=T^Olmc7$T2+sx%C^K5e+g@_KHYYJZq&uoWXz;no?d{T1oQZE~ME&(g^* zs@8{5qs`!_t*me}+Iy?R^~U;r)aTytjo)SVU4bDLi8uEm9n=Ip;s8Zt%j`271{!XP z8xIQ{YGwEc&vi1gtGj|yT{M<3vwUFv>u6|vgt-DGqmhxHQ3o)n196gN=-?&S<8CO5 zE}>&RZr#x9aUnNZPH7>F9@?{=7{8)>5Fnu#WJUop@*E8dkWa2VsxKoa<aSNkyM~k=*0C4=ONr(wvsPgj2EA+^h8zax>k*Da9SMtbH^~fK-#;XKm z^_;Kfku&wkYk1^jJ@Oxok7vR##v?wcN4zB(QOEZ_0FGZZP0w`~k6f-t9vO|S=UNDW$n`lMagrX<8jYwo zSz)QfYe9G62|*_XSA=sY*OzxOA}0y{&ASP`^(yAA7WjaFMM0P zS|4SEg0n>4yx>RBs|l74ScSHVf3Z_pSBgl4zBUh$-$zRrgV^w*C~zTJoG(a=0I3?nf2w5QIhj z#-RH#npV4?!JOK?J>-6dXb^JmCJG3*A(WHi)xL*LiBK{t-$9N!9_DVG%{ug@x5?@c zC%P_5o2zLq|0djkB>xHu<_*9ID=$JYrvs)f`N9OLxd?AqhnxQ~@9;#D83%Y}EF>Jc zP^8=;MU1)IW4W7Sxu3&AU@UhHZ^*q5G8EZS?oaP0hN2etZ^Pt+Kp}uo7cPyGMr;i}^PSk-w27Z>I7BvSyY0QJZyCUa8g?EAN(Ac|ZS*%6ZAm z$})vB(_CAukbhn_P869x6C`={&CHhvkZmBzlUTe-#mDD1VIa zC%QUt*RmzTA7i2L$CxYpF$&2a<9669;b$Ot|`C4&&EHTVQ?$ao&q z8%by)jSc^RJCWu~>v3uaQAw@x@Az4WA2#12mnoEPtpDYV+=chne|OG#`1~o$5x5lSz9N`0-8$!C=P;@1DdMZ?6ir=c z<}t}k^>HhGwH%`qi9S16|2^MhgWrzpyfphf95$@EVe;7Fxiv563`MSXrW`nJ=pK}j zrG8oxDb;MJ1Y50}DF3}%@$fafvl@r7eoP@rYN^)Ocpd@O-aIrZpmbtdm~SopB6_ef zL=VQPP^k@I!Cr&~$N9q3>2s;}@4$8&3P(4K1ghpcdcOe0`R$|TdCRWrNk@h+Ug&c$11O_)pF}R*%5c(UhW2lJF zltL*&)c2a?V5VB6xkA2OLnEP=Up*XF@nWmxPeUU!{8}@~H8nP7mwZ-ti^!TcjpMa@g*-17cH;mzo zm0B%tz%t5evS$9!xjt>975hL(&|*qooi(SgCn@(Fn>~XaQI;K5$~Drcv2uXDY;$IW zv4M63K9pz?GUQt=a8>GM0oKqfZ?v0UA;IIDgg16X-Z)FV*JjdtxyBaE;@@2dYKQAh zZ(*I_4pul0o=DOTcb7gMqR`>5K%X?7dL2JBE+R_$HEj?za(`a!H0ayFIfq%0_@Tma zd2HUe87*!t?9;+y9z!e1{b8TT0QK#0&hI$_L+Na5HHH*h*mr`>%a{=!vj!1eZkuni z|8-x^_95~+fvBU8Vvv&(u&+H&H{~2!G*|6q5JAW5sh)N-OHb zfa5ufB8$31p0o5V?}eOBT8yJS!7og9Og z==&%7;`>8-)b`T{%`@gW#7?Wrt)Ue1IEF9WZX{>7?!Yg3RkYQnc*AcZ#4PY_2BK~) z=+=p!afsUCb^izS29xCOfB;G<Vh{HLr+FSyY!IK+=h_C{V$c~4un;{%Yj@=yx2d(9j&XA z@Jc6Dh)p2TS9x!GsCh9p6#hcZe7^`nEEM9d=>mF>{(VLKkb(BVc#-Xi8Z#l^WBi=J z&b|m^YJYzTs&s#)CcTV#tf{K;?mX-Px(qK>ePH%V?PdYr{!M~kMC2+{Il(6YE;nR3 zuy?2$R!O(rTd92upXI$Xgwb;^Fs%+(YFnuQ7(z6{oQ$5m)HvMe*+o0P6!%esy|Cmc z8AQ6Y-s54UKZf+MFm%!GoxRqWT0_|d-CbC>z}Bk`ZjB^_LoX&$2-!k?AXWU1AL+p* zfbiLP-BmOb_YGwWB?rw!TWF=W{za-Dw}q0+5SdvjwPykRlIlh5ERlMp^Ll-bg#Vj* z{RdD;tk)($M7_rGdJVCg>2AGFNje?bA>FsI>I8i8f+;4gz6) zv5iup6I}npns`jTiENar#y7~TcG@T<_;qnSzh+1IwKq}zUeE=pcJm*AAMEZc@B}~T zEE{>ZbFQt>HsLEi5!Y5|g*<9HXI$9L=&d0Wj0;OW7}o|Dt*7A=h<+y+x0%`&2DU!~ zcxeOLedehBOgGox`X*6M7~mT?(|skYwF0*t2;ft$pApBv|79%SD=I6r(Nvs33I$YX-{YUN!ed&o%>~k|4G-qdQcDh^WqyLK zIZAn_b}hPl>`%YH1rN?HDaeZ``nqMn3n^+EF^pjs4O1fDNc{ z|ERf4x2Iu)*fKswmeBTT7}qA#u5))sT{Ib%a?^@3=p)KmoV*)5Q^nqHclSH^KY2R< z+JD1XgD5|z5ld`wEyvdTrEeWy1|`h#ZTzIIvf+jD@`uxh~*|K{@THV zw@MGbgv_Kn_iF24aRGuQo!GY3-IPx$V@0EWgwfS<79 z-m9yTU5dLaUJ;Bt04eA`fh}VWcL%yIb7OQJ0Cp%d&Ap4j)Hv>ED1@R~RRX5Dchmb( zUg&c{>}bgs zl)(^1H5@|+=*WRdO38_se-!pVAI8pSJle2l8fQF!1WU00dGS7w3&oXmXg5G>K=P7~ z1WMWgjZ*R;HtWf4f|%gYOA)hMad%&mm?s4>nfoA!K|IDvP7uiq#O(Y6VovCQfGTN| z14K{aKAa9VBt7vN?owe7r(J|Ju6}9DM_5USs5hM1IhlteFT~nR>QtoX#^^n`4}SNs27r-%1?-4bwwX zgbR>X>N}7RXshMzz(iVd-&?E1u$n3$WQkx4LbcZClr2JMo@W;<*@&@O4=%4*tv@XkDwyfV*Jw<6DU z5dZQKz8C*K0Nj38o@qM%EyurR{QJ}LJkuxmcLm}tz(32~d8UV#=9w-mLt6b$=&K#E z!Cmfk#K!hSeYL?>ZLZXYKg$)Sq)OJMt@>{swp6k{?IDDZFVl!^HB}~}fxa?zab5JE z^eVq=!z*k8IyI*YbAwY5>lnyJMh1`yu}tbq)EK+Xn^PZ3ZECBXfVCf0CR&5YG_e;G z`VVke1Jd=pjZ}zED@5@!3M8h_xoruA)wQ1Ac5=$bPd9b_y>DoG80;rL&;xqve+hb2 zuzsxqVd&hX(LPI6h#ZB$dFQGG;|dHCR?v#i+p(iw2tBJ4`$~`peV{2j;>14uME^kZ z`-BgT`Uhh0gb&}rhX^$zs0j8W4*}4Ikl+&U)D*BUyhbEH<^Ip${TLM%kN3rA1m3%h zSiGly*lGOGp?`SN_~BW6(A)D=0r}x#FO0t!&8s5@Gt?!9J+^H(G83C5wmpO3`BT-V zm?J-?MY+hEar*}pZ}tks$$gvFTKlD@7xBXO{A3}b_5KE}ikTSZ7f68NJQZ`1Po%%K zoq_WfTFDM*)hy`#v`+peA|r~&nixj|xGGPN^Eg*_gk>v zAWBelP#jA@)&{qxy%jHyq6hhU?1ee5BJ&h2GRQ<%$BF@Uhs(%D&C7?GO)NW|a-j78Z+Q6e>8t4B$spP!<5b>evOLijooak-n~EY{;p!i)5K&oVUe zOoFe};pqat9Pq0NzFLP5{HJI(kU9^Tf#Y=s7(uaW^;pLc3sw}gfCVIO#7Q$!(^jZH zu*9Fb7f(7I#D4&v1Oq3~yE+JZ!w_DfJ*te=+P$V(OL#YyPl%$(4+P5;9VZJ^RN`ruaV+e{E7m8=GfefPL6cCSGXq&e*&1DlEl}8C z-%4X{AG=6!n)#u}cYjX93vt%I0K-!uA6OqWyeitncVKXtLbg%w&+7ls++ z@!d88VV_)jP9OG9WzZHT+79IKO4ng4TX;+d6)?{^1r+DU5Iw#>5q<1;y%9?y%W%5L zC458%ts3Hxohh_7n#0!(w;@A}RMpNzFOdpz6VYnK$2fNpef-HPj@mni@D+jKe!fHd zrs(oSFzmb+c6?T6!x14vcK+DWAvi}>KR6^|P!z2u^ntus`S2@<>H~>~FP+wh7Pi5m ztN5_BD-D+t&9K5_6~G*?A;rliGSd%@c=%>knT120*k0q>4UYazgOahqL#_;Yj*usv zh!OG#R|eSM*@DdQ>)|n-Xk@jdSDS*Km{?`L-0+)@xHi9}{o_cCBxRd=cMgj?lPd9u zn*uwKl^?K~H{8kA*w|5+BWF7}Vzc>@68z+XNpXI16>PtDY!BAsZ#)_f!&SDJ`^3V< zg*(G8g&hIR8F7Y{JHn@4g8*j-tH)G5hyFQexk`J8^7`u=)PI4b>R)^g@&%iF?>$O6 z*;SPQn)MN`v=Uyb{{+p_c*On+tZMidAx4Np>HDgXzZ(${-+z)nDbyDYosEY2qoILl zh&DV?CLzBy8nQ)0_GrkVhpK6z57IE?Pmg}f&_h}nRa5u})t?O@))zwlOa#z^NJ0Cp zM98V95Q`D$e?k9Z0)_mm^)Dg+8iZiRGX?!i2?qHirTJ?S1Oh3{zZ(u~a{s8BT)E#C z@;`$&xFwsaGu7uGLs%-{kE$*-0eTi~t_5v#+g2$>nLQIYILOIpQdNo{ex7JrJxZH* zGg_e+HvIvt65%6njHj^uS`YUA3XSVuhZNAzzQalyStp%J0f!8L;76w-Rm?x*YW2Q} zuG4i7utVqCC-`c~kFb61_jBN?XRTQ!Uz}KfH%@}E2e}9e@m!G{R9KJ9c*xyI5B3NV zgIw?OnM&07D&BX$dX*QWs)Cy?NoZ{j=^ditKi>)MtIg0SY0`5!G82=Y1npd$Kd?Cq zj13*TsgZEsnvJ&v8odK>j7B$c{}O3)h$8b-wf;yUady{l5#;_X(t}SSNG{{eKc~-h zgvJ|~yo7jRfRGdqZFZt8`U`EQQt6Lv%|M6-2N&CHMKI!3qMx}{#=S~PgjY$C@SVAx z#)n&Y8TX&w&kRRX1XK+}OB&(Y^ZH;+m zkgTvNCWI6$%zI{W6g@Ls9krPi{QS%ts~x4#8th=zbyOw#fW9REg&0rAhZqrZnxtX7_ zBW)(-pP#cM`O%L|{Hz^G{wad|ydCMmZ3yDb9jSRUeWr7ZK5nOyvhXU3c2X$h$)*R+ zUqmllieV)FWSIGbj$)a`+xRBY#*;-GXHy$bTz6QJ^cC;FAPx42N zKmSGV_&C4E=#=$k=f6;)7(7I^zUVwAtgkxeMD-21*Lf-bR5;J{vJoNo2~L}Q?(?Ft zbxyV>%O74pZleTEX*5F8#$@^Pv2`~2Dc;d%h_L!}6Qqo&&m9 zP4(ES$1onm+-l_hvA#)gtI0;LxjfhF4Zb>wxu$Z|dWP%y?!(C)m-4Ua9qeD@UCaGz z;Jrd;2H8vzP2b^p@Hs{2C7BNQr+@X<*%x%z5x z3JCTXRp96VoFd znn2>`3V7U)LiHhas_`FaLuc!qqCrm#A#C~%TID2Ostg~4<$ywEWHQTQhr_wyn>8Fg zhXebhXD#^XtxiI%!p9QcZ9H9g%%g-oR*LsJ<%_nOg^}$|9OH+h7*7!xZz7B(e?7kH zy9G#H;FwDf(m5D>ASRU!HJFF-8Z}_7)>Fu zBZ2fzy^PPGpz<$ zY{Ci+9pI~OdlI@6EemQpV5PPP?9KAwmOhqNDm%B^u=x3Id6l-01W4ahi zZq%~zc0}sKq9T1G6+PtM4(KbI=Q{8Mjx}y3^OBhzteS>tnzo4wJFI>9pX8P`3AyIB zDefoLHD<;47(1jkeC$7W|CIB`xkQ!!k#zn&2w-e|wX~tt15;Kh65Ef{le*>5=dIU+ z#H*Clo~yTiS?t`lkh>MJ5#7B@b$^UwL`g2<{EYmZ(2;(*Kehg9bsB8^trb|JjT{}L z&y5PF?_$2kdM>XF`m0bqOv-nKpQn&loAwQ&$}WB2@~SG;pVJex=Hv81)>*Zf>U|t4 z8f(}|n(kq(cNbnto+Qc3%C{>`+opgJB!QMQPC~vr3R&UwtPsZisGyjxZ(ZW2IEkHD zPvN`16YDVqL8S{g()ZsGohJPyW~1IP0r7 zvFo31;K`E%BXQ0sc{DYj5eW!xD&1h1tL9n zDrRV+aj{pDdhVJ_HZD#d9!%MOl%`YmBMJ)2zK@_z**6j7ls$w1C~JNGpKsh^Ro6tcqHD>-3HeRy=%45yS`0&*GgyhV=if?^dRGuZ$V4NT*Et`yoEj}mmZ(H0V4 zTeYP9!wEVx?1WhS^Aj{EYzIYmn+@g_(r8+LhlpG!7UONx9ntyUre0cTyX1KL$0?9x z>;)4f5o+>dE1v#yFmXl=Ytn)a|LZuqrKCw(z=1+O=Cr@`0p%90f%z%UC^CbFRXXt>FJm{X)Qgv1Zjsy!k7>|W5s7;J~M1uq{3 zxjNSGZ+yjs%9?7gCa*h?S#7rBoo)bl8jGNy$iyq4`Sy&X@$?5!60ksXC2(KGnxEti z;8q(OwwfMl{x#lO+}ork+IiRbN(+zbKY+2V^$x7j)lfn_eR@bIK;L=~ftJxk0hJ6BKoH8_-b!d+~1NDWDJmJ-V zjk|~r(rzXrQN*1ldVVH*gcMt{-gz0%F}5Rkx~>k}3IAh!zp8xgZ6h4H8S3Za1&+4~Sz`n`G90^jh)`^|H)i?6LG`N}rA14nI2 z{xW<;eIf1XDg*6}tKi{T1=gaWS}PX$E8HeY_3Z=+wmk-Fv%Z{dkJ6KqkuI~CpQ-qo zl%kdTiS2*rKogz6fK{+|?qQV%CtZG&`=Ad+*d;+<)cJ*ot@~poAB2Jhe-Yg?XmK0& z0$Byut5tHF4ffk?mEXMpi`feFnGLV$e!&vl0r5Hk*y0F5hlO zkjpo6NrQZ&d%|d*^avFi@)OPi2`Wk;K_x*u;+#SV4KDS4B+*MH`KgzB5JA1v0D`68D|2s;<$aCLg_|iu;zHj>p6}XqJkOf@kYD|3LF7vtSa z4rGdc=2E$+@c#n(R!@1XpkOm~c{7!FdqR0%7n95@L|LOnS-2a8PApGGS<(A1u>bwP zIrh5r@QqNnk@Zy@`#4|q#dJ%&r>J1h=3T^=FVGZ2^l?}nrhFnzDzaBgRV~-x+hH%g z+W0TsgEI-|0UX$b5`qrb^Q%E`Sic05Hxuh5h!NacK-(g)PCk;Zxl?~k^M+O1nT>9z ztj$4Y`Buz4DOWQJ(mr?)1#$@_qDB%@g5@S(3f&*+n<>4JpN%j1HvBBXB0l-GInvnS zs)vEE7pvJ=bA=ep%R?`)Iu~m1$dHo1K|3|xk9#d&xG!7bc=PJ{t{*C=iouyYicEUG z8|Eq5l+uvZNhOHBh%D$zGRN3b>XHn;l5J`;Rh1*))s`ZyUM((c^hRbTx$Pcw$ybhes$j5gYQ#%{nH2%&k+0x()KhKLz1>X>#S1iO>T+&p;c$qLC{Drw&m~YE$ zfrP6QXDRVM%ZBx&Ftv(Mo|CEi{wxQ~N=eK8x1Gm{O)a9ny1kF)rV-;HGJ}0Ez;H=K zOJiV4%P58ezFII~#30ghMNq6` zibBHArz1gilD0XM28We+0ZItnqlfsZ>wYiZFy!;QGkB!f!Z&BFNDl#eR&!ihB!a+4=?|&P4FO(b9bP7RB9^3!m!|)G@bFFLnjM|LlV3 zvac(Dq@KF99Tz^2vmm>p|2^%h4A;ObsUnGP$gV)Xva5Uy!I7Y2eEgngp@9LVg z)<27?MeF6?$K_26uoM-FRtM?192H8PyB9hd2CK!%ZCqcQ)aifyU#su}Lxt`Cs|xp6 zn0pIaEUvkzPU?M`dUK^4>V+xJS8J$60`JIt;;Xk8T8cFj|3Hq4_QJSJTiEf)!yeY< zd}AwbuCeNxD-xTkkwyo-O70en4KOa2G_jH`XqFB%%SgRhj)-O{M}zW6{&PwR(TNz5 zW_F;Of{z^yQ}yx9hsj&RRbapo@$FIG0`6<%(>nJSuwEnPbVmi!OkE8o#DJb388_+O z#b*wZ|6o7K%1K;JYkl>1zx+20GvdsR5z%8xTE5%XcN!wDqZJq9wVXc%4CzpgY<$`h8K1U9$EPh{GCo}@2}Y+cm@i<2 zl4yh?T^%FTvyY?Wg9m*dLAWLR_uW5(kZc@sKx~ZQ*cipJ@k?;LR1($wQOLa$9T~RG zC%N#1T^gv_fm+FS;B`62s|BP7UMaw979ZhBe;5+C|A*r{r{7U1cN{%`7eYe3?b&wxJQq5#_b`qUN8jwGyn<~uJN$NC5 zRVNvz^h*UevZOFT?diD^6{nAS6{5)Rfd7hdPD<4f$kX^6MZOR(gOTSGXrMO&_)~OM zeTML#GzfotJpN|F-<*KIneab2B>rZ?-^@QI;&0ZDUiRh6j_@9XzVqf!6ZD;kSJ?>m zNE>jDsywrqpXv}(dH#io>&-hOO3v_zlJhe1B9eyuREp>Yls{K;P*$Rn^DmTj;PFUV z!-TEL*pQ+u%0E&T@zz(W*NSLa=u+flXmlJDWl{c-vWT0%QdytC(+dU@FjcDPzQ*1q z%A))uWhJVp{{jukt4x%Y!X6Q(XwD!e=_91q2C%>jqDP{bFMT<&TQc==GHECi(uUbQtht@|W!qlH zOEpd-XXb^tBZ2&H^X$5jF$t%M569^@O1?F`jS^{}JVrAqlCEeOa?x*Q$OM~t*Z(vR zIyx<>vKOjgFI7uUmST%YtG8kmj$?pk#Zzm|@G!$wl?QOy6>DCN1T;S|ue~Ae zwo1m0@jcJ7rWa_1vgg+b&}vrCe;`0BA3Zzr6rNUKq*UNq%f?p}QMr#`OJ&@GAjCG@ zrM#K5wT&k*Fbu2GAk0cx2l#lTHY<%C0$Wz|Lc(?hQ9jz78psQzF#`OrRFotZ*TnK z`K@Sh%x|Z}&2I^a`7O^)pWnV`{})95$sI$A{85HkE_%aP79oY;G9f}nqP2|V6|#yl z=dubG(XOx5=idEEq!e^(8?uXW6E6a%ljz<0E6ex?L=jadz@62@W8K;W8e)cgKftW~ zWhw=yB=7nvzR2T52}7=Zz5FP$=hqVchoz<#qKMm>`#KCdxRAk(bIfogMNR;F z>03!a;O++h+w0~r$#uM;$bW9#Y*)Rt;Z_!uo(W!{mBdo`r=! zMs`KH-NqVD%7K37b9!9x={zaTYgceqMv+aL7gXF%S3{@d9}XS@c?4~0ZdK=-S-XC` z;I+bieH=T)k6&3x7lhe``)vF@)ujuy;vaLaOjyd~=Ne`i9E!+)mX=a&H3e$1nWb>Q z`Hl5+E&CFWG`xko>`oIdyVr{{ucfuytGUZ=7d-OO*YveEID14FYU4NSz=sL^LU-SS zBw&IgF~>w&)>Uhg;8e4UCCTkctKe(-Kkz5cIdv$H@&!@u4r?}RP?vGlijYPiW*cd&x3gut=zC0U_I=>k3L>FUrlc6?J0y&d_q zC;27nFsikj<5jBWJK#G{+c(r{S5{O}B)V(q`&b6%Ju7FA1zRJn%P4pB5Su0F)x7w~ zuf22jjH$L^`GeXAsj6+-&qv@>f2e@Fr-Ez5;i}mBh#Md<0DEW=w*VWJ_;>7AqQ(G= zu*kJ%dj0u1u4mXAbbkmO^X^EB=b@r>+%Y#Fhum{UnksKd$4!4}IFAlDD;4p}1Xb&0 zUf`oz=jwGsTM%3ATcy|wdPXlS!|=*|1_l;ttBIldbc}$p*h3}mU;xH^+1V4>-xccx z#Dn~JIw3DF#LG*%4O;gffV1n_d!9fgO;i)e%RUOn>d!}%RA?##JkBy=Sa}b2$Miv5 zn&@tlUS^ToQenQ{z$(*m;$HpQu|eoHIYIcN(lXcpnp3HF^sJx@A@8Iyqnu5^30&T? zO6{p9k%tF6H|=?Xb@fA5zatJ=T`3M(T_O%yT}X$lmhjyOZ$FH5%ixHMoe12+nBkz) z1?jluBMARCoO)f93g%Jd)p~`QOZ%73UvURRf?tLIF0Saoai(3=#oYZl?*=}#@SD7I zU>LN()s|J-c+mBhw3)_ke&Z(gUwt28)_z0-rCK2uC*;sMko9l~9>gVewQ58fGo3>zrLZp%a3Q_x1_y|{n{n_~GWi=7wc1FpB~3f0VGxSA`2rKodk zN`5~s)P(~vt_gL0;_JcvDYycjbw=|l#T9Z(hsdf^%{-2q*~W4IoISM)Pw3ylzof4n_y%9}79?y(ghb)}ZN6Zn;29Tv>KO0nLCF}fz7S+IeOkGKWVQuM84UviPElb2+} z)mQp((oSb?=D`6*OJN}2X`*2-e&Q*}>F0>hkN1YYKTz>F%a#URbOrcqJva%ZRB>k$ z24kGy)Q>A@72jE0z-ML0dooz?h?diVeTe#fw5gWhP#4cybI zq)3QE;&DR!h*IrZ0mJTZbVl5+M0YNwD=X5~%&}nFW&Lu)SrS(ZYre-Ol;Qdo*gV3% z^suiF7u0J1g*QIvWx(!%?=`M-tGu_-r4M!9x}PDq>= zw=h~znO2lgOh~y|?=B9LwnG82H{vIr*a&laCY8}hgrZc)*N_H;HzcEZQYow9D1hE}6Vt3V@Lq zm%-smt`JR;ra{RskUb}%}~O-o%gIPH3M}*uKlyl61BQ zYAAI9Tx;21e=n*FJ2K`M@~1Q)-iVzKs&%F-ST_c(gaK`o>onIkZS38g<2_02FEBhq zb-b-YSHjA0e#2D|@KKmKr2_?={nA!+kqpue2z-( zRj8|xF9{sQ`N>Yq1kCh~TgjF?aIGiqLW+qJr}oRMS5y{bKh z&r;OPWi*2UZ&uXQunF2ruy|lBQLQC>VgqIy(4Hcg`z*LWPjt8fLx=k^dcWRm6ol!K z5oIp&E(4DaX!!`Ch&XAPZqgoU0^J+w=K=i0acpF~N#`uf$D6Z}@kX=)wy;YNUXjoW zeSEy>k8;wcS%Q^b?R{?fFp&2q8I-*mKg7>a zEO1MJ>rn0GI1e5-d^&Kly&RzX(BaVPI^^r6dj%qYtr;)OHpO0fe>h3HiCU6gN(aO9 zJGwO-KN{vIt%S!s2?avl2NQWFyn2fRv8|LmOCf)v?&1TGLLv8A@BQR;i_^UN&83(E zVv|%TWd{!9^27Ab|=iyGVpIAP3!{3rR{z zF*eg>2{;bz;6XLt8Zz^&ahMCfEs3ieA;{)fTi30_Nl;Vh?Gx8AqG3TX&;%=*?QS1-0a7 zwo^O#e}w4RvCKd5s0lCClta~yNQz06f{x!#h-7@h-cSeM8#-l`{JCqfCuSC8 zY*>_gZsyv|4R8@B|4UvzR(eiS8vtuM(46+jO0T$27GX|AUaQW$yWTE8f6cS+4e*O|Hk+P~_%RqQUg zN$f6}Ocu&-V467NLBXTO>ks&T)wec@?v$Ym>v#qR|8KhR3$&HA?Pr(cx2Xxgn=U*| zkml#7g8)vo>hF2f;X*W$m`GxpOt>1C05lIx0FWUo0YKcG0LU@|vWbb{he$MnIbp&|w608UbBK zK(`T~83BDpK)(@ycQm#QzX|*i2XGi;WEcTiMnJX^FvSR%VFctE0mVju*9cf_1gtay zs*Hd&M!-5FpwS3uF#@(50goC1JB@%{M!;?(pw$RyGXgq{fKDTz%LwQ;0yHC_&j{!@ z0`Lz0aNswAKjHusOo)+T1Y{Wj*+#$=BVdLRkY@xG8v$M;V6hRf(g>(B0@fG->x_U# zBcR0y*lq+oY6R>w0(KbzyN!TWBcRO)=r960jessApxX%0jDS8Ppx+3ryrU_n;5UIk;s6d~j0__n%LvFe0w}wLQf3$dc}7675%7Q6 zd-wRLs(bH$aseU)C$ynWTdb*q(u1uN29jtdWVi$-63E0r5>%7`GY+{xE)z_9Kof^R z#?9DRrKPQH@u)r2dMd5Acuzn{M5^&pj6!X+w4DgG5jhf+GSB zhfZ|rM2}AN>O`MT^y@@eCkAz5NGGsUbJ<)+wT~DgMfZ`W6X`lJT_O_-HH0wm0PPFU9PMz4L6T5X{k4|*xM5j*l=tQqh^yx&uPK0%0 zP$!0T0y`DuBMfG*rO92I?<^UJvz~=6MZ_-uM=UN7}SX&oxo1}aB&^gK4ORz-A9^E zr0c|Vov`UdwoW*8LUJ%FV{V-&*NIg+;nRs)ooLdDW}Rr$iFTdXsS~?&Vz*B0(TNV7 z=+ucGo#@qxKAq^-iLg!#>co&vV5b_~Tt~H!7$QaYk){*rIx$@*Y&wyx6Hc9Q>4aoy zR8GrvVwFz#bfQ)#nslOBC)#wPT_<+x#4erKtrL56qC+P-b)rWndUc{tC;D|FtP_Je zF{BgNk$O4TQSHMdT2AwscwW<|%B%S;|G-mAR{^WeEvLIMTel^Bo$F&Q$N%P(pR7E> zO}(N|(c?_hiFBQqt`jz$$kqv`PPlZ!trJqJQH5Ql6F!}&)rqDUqUH3nV?;g<`I*L+ zXnuK0(@=YU$w2(zMweA+%#^4ar2R6flyQ7D16=|sCu?9_=}IqJ;526bXcC$Lj3tGJG8gE2&k?jubn(sg3GPS|uJTPK`4;nE4WPL%7! zDxL7@M6FIV=|rhfZ|rM2}AN>O`MT^y@@eCkAz5NGGt< zK73q9wT~DgMfZ`W6X`lJT_op9-dTPMnOVwFxvDMl5pRwtTtqFE=}bfR4+ zcIw0~o!G4tdvu~hCpvYaM<;r9qE9FKbt0@2gE}#!6W9@9E!R=)BZf%PeWd9`x=u{j z37bx2>x5G$Tsq;_iE^D-r4v4#kYbH0ZIezk>qMJQwClu9o!F%lyLDoZPITx*r%v?f zM6XWt=|sOygmq$2Cx&zaI}&T+I;wrd5GlHkG@VG-iRn6F(}`@IaO#9hC)_$wt`nop9-dTPMnOVwFz#bfQ)#nslOBC)#wPT_<+x z#4erKtrL56qC+P-b)rWndUc{tC;D|FtP_JeF{BgNX&)klQSBp!NYQXTwqt=sM;a&|;5`zZSKV2cT;o*uGw!l@H3op9?!xlXLo z37<~X>O_-HH0y*&Y?L6|bz-MZ?9z$dI4aM+%43L@uJ_-Oj(!5px-FC6ilVc6KUN*RoSW*`t6~Bu!lx6p zI?<#P%{tMh6YV;&Qzv%m#BQC~qZ1uE(Ww(XI?<~WeLB&v6Jebg)QKUTz)t&-0*q=u zF+_^)BTXmLbz-_s*mNRWC!9LriXj*-FQ!F&Prih1-6B8CYU%p>AEdo}h%f85u`;kS z#to_V^8Bf2dvV7^q1sEiPOQ=ipH9^3M3YW5>qMJQwClu9o!F%lyLDoZPITx*r%v?f zM6XWt#Sr|eZ`$w#BpqqX1cX!If5oeHCA)BpUq$rm#9$23a{9_kq{h7b{KC4Wh-*vN z*|(*}4Z-wYIUMXCz^GlO()uQVy8~*(uv(Vu}3F5bfQxydUT>!C;D`v zUnjykF{l$mI)R<`A=w+%K4ORz-A9^Er0c|Vov`UdwoW*8!le^#oha9dRXX9*iCUd# z(uroBXw!*yo!F@pyL4i=PVCW%4xQ-Ki5{Kk)rmfx=+}v`P7KBnEvMr?6^)~25L*u8 z($e+9TcU9!V(Yf|I;QWW3mJ5Lm%mr72HNX#A!4JT{gAVW_cpDr8XTZcqoR+VH z%YhRVgVmq`YymsKFTmrV2fPK|2cLrEN1c`nz_&pjaDxg^4+7vWa6fnqbb~%H06qZ| z9&=izfNz00pa`r2b>LQTC%6wh3ig9HK^U9{;~#fgz6LG>xnL={0n~yP@O^MEcm%uv z4uQXdA#h%Y)ACg?1317Ea6Q-nn!z35-@wD*dGLGi7jOzBJ>j%`1^f%h0fk^CSPyOi z?cg5pTd)tj0sag=2IHP|S{DBG|68Pyhi`%_!D4V7r~x;F?}1(5H()P#9UKM!3C2F< zw0s#%2Umcrz_p+nYzDW3pMyQ13%myY1Wtl6PdhDN0+)hpPynt0RbUecfuDhgKqq(= zyazr4iO)DKlffk*3%I}vPznAOYzIFDzXs2NUhpnB0TP~dT2jF@kO>xnavUSa04@fzKt3o1 zH-aV*1V0A90#AeAfp@^)fu+l7nFKBZcCY{}2VQU!Xahe24}hn@LGTCg0f>N6z!tCr`~o}R<9pCQ;4+X4mVz5VEocGX2ls+Uzzg6I_$&Co)&GZxtMEm14_FUw0qx)( z@LRACyaE0UJ_h3sI4x;F(zi_fKmLo0Pe>dy_Vc0{mvr9v&vW}P`3e7}>xg5rx4oLO zYzDW3pMyQ13%myY1Wtl6FQIq9r63y=fNMY%*aSl0XW${w30?*7fsa7qLG%u|1Y`jh zSOF@*zk==Hr{LG%InWE<1t&nl@6bD78ps5TKsi_kz6%U+H~0_mEO-Ue2h0TX!7|_h8^JcP6Z{f930?w6 zz~8`GV0{g}12VuoPy*J1Mz9t95bOp|fCJz#I1c^+&VL=f18iU}xEibhe$Wbj0RA0x zfEU5r;21aqCcc5*0ha?OC3UxO#jA37f*e3+9j7x|K`8Uxa?cszTB2E(>`l)qU zSLQf!=j1u(&YQm=f8iom!BvY3mncX@F@EXG*=gBy9E?*;r;+OgBc{(bM0?saTrbIv>1?0vJA0-* zJ9}1k_U!D;&++hoOlN)l9rP=3f?}{5G=ME&2lxee9Q1&Olb91?~rrfo{+T2EZpk$@+qGe{mS&qt=9T^yioo$6Ro(hEgVdj<3(BP0jbu?rHx_ z{a<3fa=`^({n`an+L!h-2L-jD1$-ae3myS4fJ5M~U-R?KnYk28o^fZL$Dh>0S?N;C}EJ=mvdY0DJ-_@QgGCn0;ZwgbDEz61aZ;U*d!bVQ!e9U zKSqU_?@^WTx%0BhLB4-q$Oqhvp;O%!%PSUtaj7xe55Y(24pnj%K~m(9FPp;#ON_zD zKGKr>uymdS76=uqIe!~E#m^T7pEvyJ7ce9WuF`uJWxtKBExy-en7!US73;SEtwb4Pw-Iy!JvzNwi#wJMTksDUO57wCz|H*gE5X4?;jubypD zpY= z+lnshL){#&*gx{EIGC~*t(PB!V)3V6%?BLa#gWO& z5X!lVc+oym5xG*S#%$?nm4lJT{Zf60Dvz5cqmNGteoyMh7z`FpO*OtJwM0uxCIz{t z$Q_roQ~OjOy|R=(7FsYhjj!aD6QzN12Ht$}2-uBn3bs_Y*{`ChsUCT3sc8$J`IJNO z{x>6uM)3qu(Bc-@J1H^E?(odv(xP`ypJA?XmS^3FiNE~Ayq^d6RX~5{dyeYTh38`gs`Bo!Bu|&G(BfDm9$(ClZrC6H9mfEb`(wddqlxF3Y-r3oS+tg;| zWja2vU1s6Lq3-!MEZk;ARJd$7Yz{e%sC&LsKEv4^xQ@D(zwW@Q5!2dXlX-SR=wUgi zs?x$YqAM-kW0miPl21$ct;?vhvE6|!!yy7~Bc|;mruGrjT~SjgunW`vz)rEdy90Me zdA`w)&Ikp1u&eJQ`mlBfhN8|KA`w`oQkD#tV;VV8Fo@ru=*H!N640>^n9o z7?@?6n-+Z|7?^IF+wQdm1DBcR_Tubdz=k<8IhFW>N{)v95y?NV+*cf0(jQKLAQJHn z8S;VAes}nr4h!E~mGSR*Dqk*tVvwB52`zU@4TR>>Kt$M1G?FQ7aqkX@HswDi(UW3o zea%0sP0p?&Xk{>M9!$x|rly3QWbLlH$D7J-IW=U6y$oqFM9fGboY z*4uwg)=SImuU&Dc)KOC%!Tb~ zra5#Chj$FB*ro*bPB2=e9Y{JmO{1~$B(^CXBM!-XMvPP1jTUJhoFWoTZkx*ojg4lp z2Mc#5h8D-4vnRKO7IX6Pu(MeCfN89C%xLS_(bjQdT{#$yJ-O|i>y({*U)fNNH0bCs z4Sc{lIBQRoOl{{RsvNT}wzu-bXO0CP$}#I|dnxuCuJ53REb4pdDZ|G=(LT-7gFbAH zG`Wl)NLA*1vUv`Z%w*2Unw|4AX;GWMW!x?u$;*fMtq;AP@FvGE++lhiaY&6iEVv_H z9mHfNq1t~svGFDN#$-9CXvQCneZeIs`SARYX1r|Q%duec$@rvTezMW^Bm3SNdn1jU z5q&11Gt!u5^m2?K)596PeAIjSkg@z^Fu#8WANtN8G~x};@#167XL9nBt>2>$P&+BD zhm9_)t1;E;>bAN#sU(HM9f%;8%_-JUulUIyTGZP4oiWDBqsJ~~)xY)dcgD7MHgUgs zLTl&Esd?VtgoJA18b98GoYPv?qFqK^k$V zw!UZ_u)0XhwO_JPT81Y{zHw;ta=w!7ZC}{hc?*g6|F?-J&scee#E1SV@xf@~gG3sW zcy!XO zCY%)YY*-TBq1HK=swBAbh(y!Rkpu}ZsROMgcD@*Ho~gd%jIlX+I8B3krD4@B~mZpP-&d8@8VsQ?qyPZJ**3cQ%QB)sEl|E8p9O1;Oz|ldzM7-RnN(@#{O;l5$o^w;6 zlE87-$TzL&PUlW>LZPgDgUj+G!6nR9Q|6}_^OLOmJ1y34=I=2_Yd&pl9zV(9bkR>) zN57^V<%bV@s!g0>V_@kzn_%>~OM{KQ;a!N&9%=pf=6Z=bIh;ODP6eKn&%9Zz8OoZn z{GfGupRuGbdY*V3j>$t@77U@8`k5df0Q;DZlfg;JLxy-L97^Px$dg%&hjQ_N%c7jK zn>y?-wtj5A{aR)x94cnL^+q&{v40vZjx+jPfu13!b-HJdacD-biXvrMPnsmzlOmD# zBypY6V@>U|rXH}SzGYo;gqWvr1X{m!|KUGlzOIKOYWl5{4=Xb@J7VTr^JIzmrl|Nh zmYw*Lxn9?ks6_}98zW~9jl{x%A|vK0!!c&77;P%X=_ZYG^bRmp?Tq(l*_$^{jwm<38IilQ2#lt^1qNHR)^xD-W|MX40)^e)A&SkQyu zCvhG2MD0dBMJs55R8W+f66i{l+^<5>d-cMj-xEwDNAUZJn~^Vf(0oBo>LEl{^ZDHhkTr0ohD^$lSfS>DM(hu z6m%wbtCNc*ev``S@u!!n@97&Em8r$y;Kjo(BKnte1{?o+ePyaUTqpd`-F&J1R-l{i5M53$$7~daA8y@30ZHILr^$P$hu8VDpw~8eRz>bWccx#JalhcMcVS$ zy-jiYqkbEd?|{4dr1BD22IGA3H$G z;^^4h<7FUQo@8`eANpvXwd^nv@!p9urV>6(IK&K=hagUFy2|K|9qWug&gipV*en_5 zL8owhL=Ek$dc4*OaP}=lCU~u@deGxh@>TNp8U?T)4z5hjd1dnwPC4od48?Dn7Fxt~ z?6`pl7Vud0*n;tqgg)zIiR0sVP1+To^U9`U#^x{~*u{D#p^G=E3HgJ#CnXG;XC!eO zGFGN0ydJmmj5?Ke$l97u4U&`B(Pmz2Lm!zq+iOix=0q`jtvA2QdDV@{Mx)HPjpa<# zPFf#Jo`m9&_i``tpvEnFltWKz&z7o*2j9RMK84_e`#LMo15O2?0yueNseoLj&yl&If;s*2U@omotuxT2a4hO zn4C&6YCiU9%zUinc-k1=kS6Y#Yq7t8EMuo+!O&5Dth^`gN%ihC-e{2r~2ooFx1P}C%#06Kf0fp)t+GMAZ~a}#p68=_3#Ye>AfFrsszptV@Frt zJZ2VFUdQD3M<%DC9(CMoJZs9%XeJOE?1a;zMsB%iA z3sm@QuDnipX@;(euUfQl@$M^2(P& z>I`pY#e3W;`N-sI$^SNa5yGh?H@JPgrn2CRuTjY>hslMA3xiKSg&&#h#ujz-H6c^X zq~&w*QW(v@V6s=KZPl*&k#x?1c)(20p3yTIa$* z|EGb@*)w`?+dDPx|POoQG-J?o_s}aJ*@~D{4(JtvjOD#IE5KB(z{C@rApIJfZ7}B&wGF>^c)waiM0Z zyJ4M%OPjo&6;(H^{P=xTcX;eOwdqAeB}X!I6S~c$O)bV~5i*qI43)7_ zk}4}xrEr^tm@I9`^yPdtVwNK?be?tlgo0d=LgfE*c9YOwuTBqQB`00AK zn|L^JiFZJ=joiDjMCaqm7w1Ho-cdMh(WHlCP=RiFGw@6y(VqZ`yC4k7o=N2rD1mh*%rAK<7lW}rOXJh=< zam`PuH~RiBQ(YWY`y1K>4(DwhH~gx9_EEC!^EVUKA>T&x)}(HchhE zv7lmut(%0sE6U0U8EkD5x9I$8msvg5EpM_I#P~B}-;8}kQpPhPGUJsQA(5o<=ux>I z`r3};+`~ZzF=NLOp+X{iNxnlIYH=WYA+emWUf&aZMnba~Mbr!$Vs6OqZhZp-+QDM{ zR7_s$HS*-#`ZQ+pQxe(nh_GElRt|3Wb}z)M_3?4uoNX3ncK%c^4~XOUFO0c;?RcEg;ZoW6DJOuV91iIrnu9-n*)5C^ptj+(ri74hTk|i?K!EN&k+5!tPz+DBm zNO6U2fdZ3r!(FAgtJ?yFCRYNtL~+a80!1cQ3g=c_d0XIW4F0b*|7(@SKUpoZSfm+0 zEHjqKjt41F^HZ`E&-(b+$(f;+WQ>QCXwK#wD6&JU-xv2~@h{aM*&#KLG*zVsj@v}A zQk_Vf9Sh=6K5R>87-d2HFP*rtcC38JL2CP>+=8h|aLH36JEY0@EF*~+YWc|ekXrvC zz#;Go>oDBHhFVZ!{*SY~(q}?%z4mXo@UG%5*78|ve*qk^Uf#EHGOup>8T@%Eco5^n zYTDg1NJhDSNq>;eu71cC2 z>l$x~Qw?WQq<2fFb<5L8JhG!2Pk0GPaLyfP5D+O#j0^|j5RE^P!b=|}|EgU0#nq5p zu(V_bYtAEs>MhPxo^jTjvXPAkij)}q#9)6dxTBZ*6?ZDB+&&9!={@&6V;C|c;XRFN z=A4RnyR68@xIdMRJ0~zcbi%k(UScE+dX2=#y*Y`$ZA|eR3m%X6<}CPaymgDLmf;Xg z{H>vaSRYS(JTR2r_ys0jWrykNvgM)dHBQcM425=*p<<&aw8MuX+;j($YZW7dgrOWM z>@**ur(AuUTFH&tlnoZWp}9%c#|ueTHd}O2%YlAt*Fa+6U8}JvkZ3G364k<-h~Ws3 z`h5dDZpUQ4F83CPAYn zf<{dQjhYA=HKiuJXsCr)YIzF5Wl+FaC=o=>%Ov#?o=BqR>)MDWYF^Mr9!C7L=r`hj z%82>8w)7G6g0`t6=4EZuN6ZV_E;B3LP*rZI*5NekKc~yOeLQ+=p4nz{%r>+AO{ymH zuoTC5^e)T{X)+D3Ok{93JDb@hjc3|%4JA4gI(bCF`Au`)w4(>w_ud5Q0b-0iEK>cv zSYpE)k8U3cddQ6IP-I6NT;Qp4;z$aqZ3LXZj7ZImG%RP(&rSV&ji{K!scv77dafFj zxJ1>u!>OEr&Ka#tpy1rYnomZ$=qzDJGiE~#$phP%AGwxPd7c)O;;Inup2PF?f!e z3|(ueQyf#7ert!Vn)~IL5Bbi`hXThhJ2x4MeK8;hv8t>!KWk2NuH&fE?)>ADTe>HQ zk{SnPamCH$NcfTOP?1XxhNmE>=FP_~{%?nH3f(U6#=npL4KfNzB2P-@Q2Jla2F?H#ykSVIFKb+dSCv+w#)#n;dL;gH^o?H1>}Y zi7AnI+~k5 zt<4gcKbCz%`A6K9k) zZk3$aU$j1?GWR4|vc#Ni`ld9O9vWcqc8QxUBm2w{QU9uo7Khh;RbrCTsqGI>rjL&0 zj&rd{wiq9_&@duqh_3!Ibo1iyG~%$+)|0PzRwCM%Z)Ze~&fg4GoY2+j#_f__ly71D zQPXUwLi2)hQ7GAE`opn5M@3Sdt zA1-_@zoYca=>GIp$QUB8_D;G+K)MDhd7|uXl(AaocD%Je!J^g+>HjL+HE`F<0@sow z;T^0%oRH0pF6lJS4YK@eKA+yEwwuWWs$3+ZHl2l@5|PLm#jVtWx$#WBt~r!{)J@Ma zYu2nXbnoP-koMw)QL?8Rpxe&+R9Ioj`B0X2xF_Gdy z6gvAUsGV`fozlUL1BTjC*Y)n0gszXBDe=s)(-~u=r}J7)^tKWj;pHRtp~Ok-6Ul!gcK)XJ*%^k90L-v>87u+~8*vQfTW&)< z#IpQI%zVzB6mq;~+<6`TyZ#!_x`DCm%6=+{6+$ZLeN{ofKUYC&&uMR*JJ}3k)F$Wn zuROW=>&B)l*{;H(!&&2Di7R1qzp?B~gRCniKY1*1zcVGyu;g?#w#%B`=6*9zDwC2k zzrDGMO~BdyuN$sJ)_sCk7%7;7S0zS_Y9b4-)02gKwU?QIQbEHpGSQSJfF5r!IX{ZZ;3B1cjd3}>{;#nB=(HF!D^sry2PN)tQp1N> z`#YxA{u<$6ZaDX@CmKJ`KIj?ZElRwiD9O);f+TB-#X2d@n&M2cPMVuyIMai3(*uhV zr$)CbB@+1z_T!yV(-U)I@^;s;3lrXM`BbeLs*i^mMMjach$bE6(y?(+4=bbrrzB|0!q{*ehE7HTEP-07PQ6fcChNKbknhU?-3*&ftdmx_6{amEF4wf1p8s{_Fs4_~U znS1rr``VK2P43Z|b6=d(yU}6n{kZGG#@R;knVeS}zCGhW;B;dBrGe8I-*l02z+D_J zC3EIJX0q6AZe{LX$V|XuxqhFSpJ;ojObwqFF)c}wA>hTviAE3m?2>}+#FcxK=)uV& zW&(WfdRzLEkyniDe@Y*jQ;%9~GfoFL_f{r_gOZ-CH5l_*Ykh=nA4N8$mD~VjQn27VeI_G-01Km%7C6O8y%8j`*u{G z{Jw?PH)EO^$hlA4$vy1nHM3F<^ncYqjm9MDqBkV6d-%&U_R=(G9OxQKU|$9eJm*8} z)`^T?Ehxdrg{-$@HeT%zi`l_8jx+Yt^NvjjC63S8Z=JWFWobs}MEl`mdAVj7`DsH} ze}cKhe_^zP{U;(T4o@Ke(!t6v4*aPc@y1ZXdJFWJjw{(a({_WPSh@eoyIn2Fsh-v3$sx zWE3O?T``(lVmo+{-Pqp{Jv}^sA3cliN(#Im*Lck68cButQCp?PiJX@upv9UJXPv}# zE}GeyViczbx~2x*>4DCvIWIT9cm2wdfM#;h`Ea^xq_dTlMY~$;gPO5WYDRT~rBtA3 z0CRe7+??1wRyu*PF^LBMKkEqp)C<_)9?ecPKkk^$|5+JgTd8`KHf~DkUL;RwmdH&> z(FckPM$O4zC$Y#)iTf8Nv#K*{GH$vM>RhII01?(W-&n}XL}YR=y|(pZDR*hO)NCK4 zy02{G+?O*^_5qw|j6bpO#6U4xk}5eiQEL3>Ja()=ZdS4Ej9UJ(a$jI`QpCFDJ;tAb zPA;m6B!&NuamC7g!xidsuo(CF+|*EUT0$>|u~mc96HeqD@_&WhEIC&t*N->mB_+&D z&grR74(y$tbExrsdnfg4>^*imoR40n|MIB1Z|yy4TJMmZ|Q-KtFr*UA= z`kV9dGdQ#J`h9!iu9t+#n5A*t{xK`?8y8+fhQi0^Mh{MUQzV?^3)Fh)b6+3vsp{^umT zNf5;3ZrDFYauY|nq>3uXg!9P2!OA3`i=R(~Pa&Rw=ZvLe$u+z>xPm(4N46{Pad2zt z5Q{sXNpIm36KLz+E85b;c@Ev|32~dzC8HslbcNLIrGciYmQCNFJ&JDK6L;)FW{(CB z^DWs#+iAV4i_U6z!ch-1JHwqMF2WA)LcY>Ennmj8**l+*D%xfWv^iJm01ec*Xh#0|ktNWPatq zm!R465dMfqg>V;Ta)-Y_D&Z_H#$fnC7=9%>HvA6$cr|f7FX*ZdzZV{Zci&xkRD!(6 z2=7El?rJNbh8vRbzFsa~@lwde9e!3hx#mh6^^Xb9 z#~PlAi@kFnKjT?UlcLq#Aq!UnA8_%N zi!Z16dR_VI9wUL{aXL2Uwzo(v(s(5NXOVUI_cA*lv`4O(zctC1bvBDcZt3U2N)93PyisspM3=@4zj zmcxx>!r#E1*PwA>dF#4jMKt@y{$sP5q8L0&^1BE&YsxFQ6)U7YhsT@2r#El@tAz-r z<1!p7zdw8!SE&MdSyCGQ8wH9q_J@y2Ck!tn`vbSoU1&$L!tO-N(ikI?7hv$6muTta zjS@!Q17vek?XuA)Z{EA=5xV}w>*PAQT&@=^lk04^TyqP# zdbdw*Qn!iK>UM!k-Nr0Xw`opw`<8>--QOEi%gxBQ+`CrY6YuqL=e_K`ezCV6p(S*W z*+Z?x@$VjSytf)#_n1{&CEwj+)+$#-F{Ua=t>`>{)Hn|4DD8o|ObL)g3e z`PpmI9uKY|aD$)J$L_*0+7$y8JZ5zTDw5SS5}FDKrJ~F>MpOe^`*4T4Fyu|GW#5 zkwxYB@LBxGtCwo}Tfj3&uMSKUr+E8|{P5)h2@~3Uq{RAf#z)r%#|3Xn3>GD^Gp3B4 z*GG)aM;Q}ZPObNQs+q*kUbo&;z>s0&_s?GBS>MPoVefRBK|?E#1m!nX!G^>M-4hsA z7AFNuk{jm}%e29~h&8v9HDmw9xvR#;^OLyTTDeWmb&pN3_|J#rHV!hu(m2V;Z6;5F zSK@-oaBOUp*nE=TU6G$Nn`TyyE4RP+PwART@>>yp$4evepC5Q7J{m^?Ki9h9`d?bu ztLFEb6>@!)dyXddS!eV~7AU}$XPx9<_L1$vUq zfb=BGy~2FFFWvuzKu@|!BW;tmcdCgE0@`Qqykbl$KCL@CrBRsOu!MfNnsGq-Ay0X~ z`(|ZASbm(>ojCO%nF^_%BER007jE*KzE)X83Wj9aD7Zt`c!EC_JrjIX)@Xvy$l8o| zyIR2ZZdc2X-t9k?wNvkQwUNTR{eD@u^=|(UAGf=|r}kbL`H8_+Squw_K^CgJRl3qQ z*=b(s4z{Yru>9SUlX!AM2r|>1znf<#XLtUCmz$??P=+q*v@grldBcZD$ zTTLnc5}i`G(i#s3n*8T){C%J!Oe9LazHPbkFe#~tteI~ZmX_=_4K@9Z(*jK?35^%> zS}iwy%$;BHe=YYLa!bv(LqOxq$|YF}iUgMK|cyFPf( zm&1^{HZofh<@ewz{N5-f1ZMOPzX;EuYrNnNdcyoZie3>_>LQi=!L|2FxD)(9b#P?2 zzWKORd_wXZX-wq@@8$!KZ>Ak}F0-}pZPJ0CPgClQU#kW@Cq+X9JVT=6r;6Cvrz}&t zGrao4N){rMFEb6H{Ns3%Z94qEEwto}J^#3!9CnFzG_`1H*>g+(;(Y2^#+XRXEB?gR zmm4n$e;;jQ^yIwKIPQre9FLt3JAUG%Me-1vGNC7^^zW%xZwM;Idp!+(Ux&qsz4-}l%_Cf?3Xx3p$H~a{0i(gej&?ln3umn-F;^nLh7GzB!+Zk6w8djn zJt$=lJ?Jh{n97g+_jRPpwWRdfy90AAPst_cqlUCHV@_+tf1dG5SPqqqycyV^nDbHn z`$$*3-X~t=GTQ6cb+3_WeY|LlOzTzGgX-F&u1~9Lhq^wZuId*A(}tAU!UHjlBr?s9 zAu_E=T{o#~v$}3k*EV&nRo7~DO;=Z+x~@{!>(sSeU6-lr-RkOA*R|?esID$`O;XpB zDjU0%`HV6zQ07_G!@1Ub@L*YKi9JHYO`XuWIdze-Y--A>zEf`p`Ye3-BQ=N>w<*JJ zY3JOg;FdO_lb?LD<3+;TUpw_7Zzt#i++RO77qhr5<0}hruO55#tykZFwQs`V#*?@Y zo?1I~3a_#FoWiNUB)nRGeh&U_N~#~1gWrvDOYN^2`->XKoqGFJU*K)awb$PuOHJyJ zAW1|_ljEA)cpmn|Q}5zja_U``d=gQoCP+q-PQ9D(`n4k=&G1i%mg2R~w|28;b8N~J zqV&bON30W{5LG_x${zd9X*8W%a(R<}i#1S4(2rQ<37n2^WRp+IlJ%4X?%pPRIGLlxR9lPX}(g%*%!n zA~@i`$o-SI98=ktGS&Ob6ia#I7u0HK_z0=kJD=p*ybr%0348rf;iQvGm_eN05vLRTo@9z^3Cm@;OuUzra%VldJRmEXzpEvF zUYT9UQWEq5bNwv&_d=gwV_B2Bmv`+8-8Yp&TO_>l7#q!qH#0io?PA#MR7xntR6^=H zRb8j3Ynr;Is;iCbh>8j=NgnWp&$3y#O)|am$k_-wB`Gv7*|=>BjJ+rP(%&e^RcA1D zVd~h8Rv%cv{$dqJCCe)v?r<0v-m>irEqP<$Jl;d|PI{n@`*2)WbrAek710o;CW+{X zMD!^$^O4M87zrTh6^pm@EqHrl*c-FU(BhHeN$WqN_xsiT>4Bd{?K{+c=fFzF z*idK_ZDZhSjKi_t)&xTWgU6+4lB2(1>R2F2ts72CWozJ9LuVrczu+<)_NrdIKQ7*P ziT5i1=+8Ecec_=W?WCtq}jS#Q#x#zNpd{ z0XO%NTpS_(4E&olbE$-UP(nU19MY7k)Nk8gVsifm;Z7eL4)=FV9sLrn-V7I7k~B~r z>%Ry8FDn01qUXea@>cxM8}{Efu=CGS8T|w8+(+`a8{c<~gxeRQz>{zXwczX6a+v(@A{Ai7! z-S>y(@+PK^#BIcP&9Kj?JVWIl)0n31$7g66#z0r*V!Ud)uqxIYP?EzLy3I}G**HYxvb zNHf986eGXW9Uj8n?C-k=Uc6cgp!(ReVqCI&;IVVmucFkw=ct_+hts{yXZA$zNYd9y z(hDT%Kk_Ap(319no~rcq&L0zoV>59aG0H30Q9iugmu%1&Sp6QJ#3nl`2A1_l zZL()-VC;TMAa1hrX&`CXCU4IM#t+*TFf|x>5GjphRdm*7N%5ehXps~}Z@6Nb{Z*nj zR;c_*n=caI>9^tgwU3l!Lh45?1FbKd6X9+0%Od<uNaB$zhGU|n0O!Nj$b|Sz`qz5h%fWU;_<^j7JuM@2gcuY4V#NcR+0aQ**t>SRW>D3 z`Rg7dshTE9Q~tDUn!@L*_5_>1pI;%1jJ|#Xm_M#xu}_8k|1Yr;7`r3Rk_t}VB~Jks z%UAD=vy25Nz8_~f3VgfbEK5N)xCopNPW(L1@(wrv9t7>62frQQ7vKk=4ai@Zxc7oz zgMR}He-LNc4i13vY^z-Wwu154Z^OM36oQ$c2R>Y3@5WE9?gq6QT$gl@I26lmV&<2`7B`60jFbiA+lEGPI{XRGZo(I1M%fK$M9W;Y_ z;05!5{Pq6?|DY4xOnvO;eml4o)B-PX1Nl3jdnWjpd`^jS?w`e39=RL8+&j6;-<6o< zuZ{SQgV#U@X!@i`gY9t^#~pE&yTK;f z-xP2i<}>i=-21uPxIfL^iQChdd%;QYAn?H@0~U9nN(Tv5?;ICh|{>F2j7I;=S-neALS`YCosc5=-!|DzGh8VNU zQ&BCc47=s8_tf9`9X=c=9+zy`=qai7c-F?)m#wRjxRm>{#`^WK?qZL+6W^kW8jrFq zYFs~m!-nd$8*W-}nb%ld?eUv|O!I<@nu;4GN=u2S+PlC*2^!b-{G=m(ngGSORIId+SBmu>c$G@ma6=t5G>U7u)D?8_21IIp#)7)Jf%W{@D!rA6P~I6E|ryf>^h^aciCl$DggH ztf2j6&>oRPaE_a4)_%EZuUsPCQpNYRbyM0Z8V{u(o zmMtrxD+h*1H{q@yF4IZCoX63bOOTBJynZLS-^sB3ElD@adpX>1x##TS4BCnyY zx~R@k;jEuy&vUry))#rQGE0kB=SrLn86_EZSCzlc<}5AqWn^8u-mcR3xr^-rZm)9L?KV$whRZd~mgYHXinD6!>{)*9Dx8aOHO0AAH9l9B z%Vu}DGVS8q<0!TJ9QHiB%j4i~cX(_zhr@1L>hcxY9ri4{%a&OO$6uz;Yjb#9zAU@V z=kR%K_RKnaCQf#nt0c=+lWEIzd;NZ!C(q{bEOn4~pUnqfoJT4Iw`a=DpJBJx+T0l~ z4|$eI>oRfh6?sV8M^p~VKy~rw&Mk6PIS6Af_2S_1*vh<~Y~mzsj{`q;N1cj;^zHVN z^_ezRe!@Ctx%2#WC0RA=i?fgeWkqh3Sz?X-6M=sApF5AZ^NLjc6VH+=dr594vh?OH zts{L$mCILGmSwNWm}zsAW|bC{=4KWwD?lcW(gM$tvVxKVr0iIY&yw6KM@fN{H^Ww& zTT)MO$!ZmF6P*8t;-SkGp_cRBg;>mfv2NTavZ3u&PMPpXc@!EJX$m zQdvs*ZR^kV*Uy$?EbH-8nHe^>-B!rmVe@6oIp;ic*jVHCW~|Aeu4o4y+JM|g)tzb^ z$~R5cGbh_!oRwLV;aKW&FRrRV{$7uxsKz^I&FYf8hN%4g$kbu8(GN=UkgFrlQLJ1xne z2jOOSx$`9LDv#$XYCW&PWph+GO0xU~C3&SPeUHmlQ#EI4O=(u14LLdlRpi^|$g9i9 zT;p};l3JdW-&QAG*8D5twlr$aqYDrJ2_OB-l(yw_XB3sJx6{{bwuZVq+P#~u>-J-{ zBX5tr2${MwGOI|_<#O0E3g~V&r&Icl=+e^Fxi(vko03$y=>C|?u4t&q$Skxu7go7w z66MEnA#_K4m^TDGR?e4rRcS)Ad?XxdcdQQsgLlhohv*9Zj zS=%XbCjACs5j*-7J&j%!?dd^3`SO;MkEQg*x|)ovLQhGq59!-zv$R`R$$GB?6#1x} zOwrf0!L>_7uaxALdc8|U3yJ&}qoaIvz9P(7OVRa9MK98i{ltsUf~vYI<#Q?WLKo#B zwLHcs;&f-A>(PzAIdw8Fczmuh(w8wHlbn(p{QC-#oYJKdcaZ}p%IiXIDybik9}w5Td#);^CiYTgsp5sPUO{L$8VaPE`HK)Owk?C-en~Lfz zsW+)h2fBpXEGl#{${=THb9Ly0Dv#Y(=PAxwQ-Ed=o)|Ofw}p<1+&ak{9I{2<*=?ok zGxBV8j9eMCS;kf76tm^doUMqq?O1B_<%vw0W7*J`=)vm64Yc=6ht!W(-PaVj7yD_0 zGKR?{gN8(HxEYVofhtaK-a@%M(8(fCHO@+VFU=6Wn@Rp@(agCdtb4B9OV^ha5ML2< zCwqgJ{4g&hzj@65v)0s0K4rWv%k$S{6nQ+%pybb;fwuYVKaUI%HV^YFx`)SJxU{gW zAgj)o<*)NPeDpuYueuVNTG3L9xz3 z&(2Z89wzGCc2BX-#l)C4k;z<;*`$Ylonb5V)Z3+Ux3x7pPL2aFHP9}ZN#`x?g z^w`(t`j+~5lqjmGc2uGFkY$!{b#cL(^(Bi-*Js-8h3+g=k3G}N91PjkN?XkJk&@_B zpQDyG$DApzv>165+A^1-YaRF#U5QWEh)R=QqSBRc`jJZyhZ$qE}*S0mhnn*B=ZjHq=>n{qrt-*ns5!oqjbEw zla3_II4Awnk1nR8@KE5)VXQu=bLBWZGwlwgTSxrSCaa4aHFkSx!PNz&uF@+qYrI)5=77Y_+^Q_AVSRB? z^qyBzP?F`T;hC_8;5>=WE>Gz|?uR`=)ULEStQ%t)6D z@-ity19L$pD3Z2n*ACQrX>o?VqBtXOF?Hv0*=c`SG99tk(zcXWvq?yp0(4Xn&j6Vo zbgF;7fAO`nNuCd8og0(!CiCSQx`QV((^cV>Izxt41++P&p^2gXE7o(kFMgm%0~Z^=SE*eY3{YTrHo~zc_ru`ml}UcvzRl|j=V1a z>ax5N#(UDty4G9FoX_K^Ced+ z=3b06Ga5Y3EDz5-#NeQ>p)+V}4&NMK1ZY$?SB7OeT|=oi;61BNPZ&p^p@( z*sB$b7VVL6s93c~JXS1PwP?knRf4x@<Zw~q>RkH*t{(jlwhWR~^qP^?vSaov4fgmlYfGz~(>8jKA? zgm_6;n)p>X5)qd-aZz^;lblB%vvymTC_-*Q{zxJuZA&feanN;3S9b$i5_WRXM-1rR zuvvTKD=bh9dZCGH!TLc>T!gFvmFLoRjeV_Pnk}`iAZD9toG@sZ_$m4yx{#E&yOw7m ztDO$WCD07Yp>-2J*>+1!OKoy_9y$R^2i~qam|yj<^y^mVU}T{j*ZxO;*LAF_ZOMsL zK1zb^4_qYMTQJXf8+@}N$$PqWmzf36Xw5*U#9XyNztsMw2GB1i{aIZB3>#?NCG9TA z6yU(z!N}FEZUBdaZj~=e<0XczM0L7lE%ZgSIo@3dn}oIFSPv?4u5Rqg+-|H{C1JlO z_0=6MToYCeR%6`)Jf*QO(^Ur?;no%DumfCq$o4$ePp}S9w>p!Az5rbnRy>Uq(KPjc zyn*C7G%d`Q)m>dW%B^eZxJ8eG=a3$c{6V#p*jSLe@fNGKp&R^z%v8^TCW-HRyx#{xce=2%Ijr-M zbJx{?VZxM%y6{Oq7CRvduz&|$C27)Hm*4*q`k!=d%p;CPA3z7u8zHD%n-P2(Yh6YC zZ=?TdZYHsA1b)djiS^)gBzwTM!4r!5KZ^bbO?B3G!TT#286ObR}33 z-T74PueE^CuwIi6H_|-lVZrlYSLBkc6PHZAuc;RHd$=i=v{u6wfZhb1*0X46O>Q-r z4@i^eM3Lx<{-RuW9ohCGbgULv4HCsLWD3kmJqkPBMm|c{ZNoChJS3g~CRB$m(VJFq zphpis`C(92XGWBI!eV%J2lX>r1KR6>^%cg0IR`OJJ};8xgk=|uUUx0oUsy{3wezso z-15;(9{8ZCMzIEfr4*fQ31StEG2 zfJu{*I+-}C>s&b&CK*E%>*^lS74A4l5vZq-lRXnu>8|_2e#L4f@uQYntjpIs=&ypK zg+t$gra(tnmm+p!nUv+ zr&wenEqWhLa)MU3wht_zTJ>TB5bBZs?}zT)F|WozE8%ILP8w26b)4SP+=X zC(9NZ3hXm1g-$rP0IK6zoo^T5n}Ej=u-@d#b)=4R^;F}QoQ1V5=%=tP@?;(q>9wSr zK|<*|t@(jx;TJ-roBl;4Lq zuZ^te)*VRmpeK|AQz~k>YmulA%C}goJrOcmqaq)eMbqYZe7`~YO|%wC_2HdpS2r7X z)nZzZhI1?Lz7>KXc+`j5k94(#s<%K#W3m1}kq~8J{ok$66cnVRLw_tvO6wzTeLNV} zd9gTRnU!{5BfcfUBEafGpDiW(YzRRJGnnm#+#)oaMp%YsWOW{r3fvAW?X_ZkC+UJ+ z-B7(<)`Qg1U_!5~vh<|rtnLkiH+g?y$VFI?-4V?29`}0|Y={+HWA!R-dA!S1&2GIu zo?h;BIAA@_^1cq3iXHfN1^ozX1Z)k~V5@U%qp4f8PV0~{+6P(Nhq)R7@kZ(~xor+> zAuF(+(ulQyjv(fQ!H45H%pgde)&{IixjwCRbN=pyf~)xk!hC6G|S z>I3~m!q64mDMVw%cXwh93cMe498&;{4x}QsU`@^LX@zV6^#Q5flg4^@&UCQW0^Sxu zrAQQt&WbK499?16Tip`$d<*T=>%j@#IMC7Rj#k*xnD~I_Yef+{RCg2U^#Ft;$!>fP zbM2ro>XZPt1Y-q0&T8(F@LeGd2>|^B6QR2YYqA(KY9nSAF8wdYmFr2VyMrpcv1EZHU~W zrvQd9aNCFB8mD_P4wbd&@sbiw6i;o0n#%S$urJVcafho<_&#_I@wejgQ3rf?V9`Zj zosea|Pe^+lF5eh+B!^s5gxJr64#)>AC*|V^gRBr~Qd?ZS)HTG0+y}kkI`F!27?!dD z^XiKB`)w|y1br6u19fMiuh4$K&3;lpT;zA-GlC7tfMFW{{y=N50_+6a!T2=h{kowq z*i@Fsa$v>H1uG&P!A68G4j;O)l0x5}ligYOL7(A6X zoEgYtp6FQB?5?e8nUY{4XgAf``2Ox>cqh-p-pMHwaZ}z}>su1^P2a(H|7b2blO>OD-N@=wx)*mR2Yx5oV*?lMi79jISB4!mw6i9G z6zi#~%=)$IruFe`EzY^;upOwnt=R?tKkFZ_U#~}>dT%vM6zx_Kx}M>&2Pc5mlcN-G z#~=CRsNdJtFhhfH^0p5{`@c3JZ>M$O1UkDeuLFD%z z4>IvPhH{G?p=Iv%>obrHk-TKW{>K2#<{R_-P0~xnf+! z`6zA=aP2F?H3P7N2p^q-kJ3>&+TUin8iyM35>}^_uxKqem3V*7jJQVLc zDtA#)n952ce73w)7bnSP`7?3?Y27DE%y?@m+B{cNOKM`s_uR@DUDj z#c7MnptyFV+d_N&&XIl-;wX*pig8iC`FPIOR;nx2%O7(;Kh>`pd3;B8bc(_Rr@BzN znmLVWs-DJt8qZ-oOKPyU9M4ufBY4K}EL?`Y=6JT^If!QrPgxjyp7A`7r?eLDc=qAh zTn8V||H8Ad9(%m;9K`dVcvze$PvY5%X9Uj)JY@~oTaIUCQ&ki9r{5jgbpN$AAA9xX zlj6JH_Rm!BYIV)}-dy*ZPHY{Hb!M|`defc7`&HMhw9|GxZ|A2*#449n$CgE6OH*r? zWD_0K=Bx26E<6{(Uw~&>Wpjl7jn^pNmpJwS$=p$d+x>8PB868bbQyjG^be&e@4=2~ zz|dbT_@Dj!Cs7McPNXdvAu`G>st^x`f5&GVGrY`Qgfy z&E;*C?!Z+|fe|na7_s9eUb z?yDtk!R53an_094bUqC*Z!7}@OMeXKUEmjBJi!J5D;jfwV01>H4TD!mClYM#XK>a5 zegP&Cj2SM#3hf<0c!c`KC7&E4KZ*G_19aGt_Y@;UwjTW0h5^9&*_@v!^g6P||({FgN_ff(w_#75PaXA3WwZsC~nam6+U>9PxT@EexDFOYC~GeGc+X+!6@x; zp$_}t6O5ei`?#pRgp=x5T*n>283N3AFEn~}q%?=8l;@}M%N<2|64kE4JR`Q3YgsJU*Jk^!jH+%t{BSkoT`Z#`pJjLbs z<@vtfM+@&de;@f{PyDg%JZY(W33oQ^;ky++_4D2EseKO>;E%$m@gIgy^!0W4lo}5x1!6+>QaY6np_yi;8`vj*tpNqzrp~*BR<|k-OK-}4!UoPv3DMp4* zb#>?e1@dGIb^UOmu6Gr}ABRtK^Wj4HQTWs!{|ujSlMFq09^6zcAD6?Y`rinj##V)YG5l)yxZRS8!oLLmYWRe&3qILAeelV? z*#O_Cq4eG7@3-NiwA(L`cC;w%feWN9tDm5mu?wV)A}zTOz^Cy{!>4v_girl61fOW; zLHK^Vs_r>|yGDxAW-pL-tSIe{3#4twf5MGAZ_s+_w$^&K%P^WNSDZCWt`c~l~=f@u^ioZ;*V6MkQ?sdiUx1nrm>rVKk zTkznW>_>9Q>Xt#a_A-_J{V$POe|ocCV~c|$l?NNyI&nFgQABj?*T z#d%7RHUgc)_#=|+oS5uBlI z2P{#X7iU(F1J-`u1il)aVI2iNp$PV8!1e+b+B%VL2AnD%4wzh&?hs)6 z04ptmodj$vU~Ca=_NTG70oc|ed`kc;1M~RW%K$bD>CWc%%gB5hEui)Ixy?nfOT$~R6f(+(_zC>9dyIahOA9% z_mqDvKK9^vFTxwWxZYI8vDex;H-Bv*e*uQIZ_1N#7ap#r_Xq;(aX=Ucf^RM4PkV={ z8O;p?|JrnKM?B4JD#Uwr8GtQGxE!2b?Zui1eYc=+9`)(XQhVl8I1=m4+L?5!BhRdM z$fwV8<;iIss~a|G}qgS?2JP>ak3o;r^E3~B2Cz460EWpr@#?RWpLsg zHw?T1V`XnYC?m(zSFc=N9pM8@5@`&>W7DI(d1pm`%C8+`&uFq84^0FI?t%X&GuG#` zIAHH|wK7%HBOuU9953(9wK6qssJ62=)5>h0(Tt%Ul$K{IUApnm zRS&c>cNDdxm03H172SvL5`?p=lG*L{<^-M@Ohw|B{(PZYD{-4aHRk1RrpoRr)D**t zI^9nB$Fu!xq9uh9Sk+X$(jT!bl^snD*lCZda8KY2`lza zRkeleADwQ++Hf61%)O2wly?%L&`lL>Z8w9mtXOk0*SkJe8MBEj$0Ng`o0)qlpLaRK zl-TIfD#U+eW~{*(h$ZMEgWe6XR3?^BIsLeG*@I&qbTga1_f#h3j3>C;gY{?J6b&Ssxt=K#z6Ljs#aO$ zoymAE=H%ijCm*Wv5{9@?1;e~tV&${_PA25%@-lVwcbzkz+ixo>s$+F%eyk21fSF;% z8uMKP<5(_KwvU54pZPZGfb;G|*y9LNy{PCpDn6b$99vGM3p1_tDct_>7BVlbuA-5P zmsn{0c)mAV>xzmNE`WYS*~uAJR~mf%{}2b7|4VNKI>#}*`4I?H6)u`)S)xCBZ=nS& z7mA7%XbN7u-_Xs}qLm&U=2L4kkbF@uG>LAP7bvwi){z54>;x~5#hsXw1i2+ZqbYE< z$RuiJn5Sma1WUy7Y1_^+tGw9tphxERiJ8HC#ZTqLa}IN8W~_~F+8{w6b5fn%;5LI^ z>cU*Z?t+Q>G5r&>IGdd^Q)wRWEnugcIKEtXzXdu2?m}s5sf=<0>&b;9%-VFcH`@>g zha6y9rmao4r0n(CUQ`L+(ATD`GbD{GapgzG@z`-Gpq0=Ba#(Fxn_dbzL=2b2dJ17& zV_yj7nN@_%&f(ie)YVpU_|ooUM6K}d73*E#GKa8%^CW7(0+^eoVGn`FoFVam0`=L2 z6eN|r+fN)^WYS5gz*0$SRwNQRlAIDsMHR-cB}p+0JqT`T$tC=nyaTx0R#f(;Niv5K zuFA#NSI`YL=zE>=_;Rj`KGL{YYw0$ZKox`gN06Vg`Ush6NYMl>&qnGR!j{Urip-06 zk7Cvox|m5&of~k)3WRIUm5c|FqA}+rdzr>)_3~xoGW#iJeSDqW*pKGf*?10HN8<_7 z4)^--M(AA3em}G-lbxLIX+Nelm!g|saJxuDgFEb{Oe|J8Fi=A`w|MGBj)!)_VB<#* zZpC5VgLrVB7=(3CDnnYM!Njr{W71-o>k6^s9NK5(%(ZWb>Fmv7oTf2UoGW#zC$`dr zsXTB)(%}m1K9+QqotzNs>_t^FOiSg;Rm+=e8>+`C_G-F$22_0uZng1ir!f#T?!7ZH zbd+};O)LYV>*|GOlMD)d4=ifi4Sx!C*Y%CtkK1&x1e_?A**{_ zIn3Y<=rUcYwdl-PCJy<#J{HfdUFY@zah?=z&)GO0WoOoY#_|{SeYJ4&VD+E1>0kC0fkYXQ;gQd8} z#}euT^d59?=(S0x3k-7u5C>p=$$K2YcG#g)Y1YQ#;C^A)PO=8-Hz{wW2oIxaQoX!>ZY^_ z8?_T-6LX6OeNxFT&Cz92^N0rE|D!uv&~(yWSsL|tHnq4Xzj%ErvluoQ_hCP zX>KvM*kh#nkAjWndbnde4&|q6Rar$ zV})2QxHai(;~v z>fr zrixZ5{Rgks_YWO1X`dc_1EGiZtI_lQLlJY48-D9Q{oB1f^#8`lEwL(Eh#`WPb@hfq zOfh_lk@dzn(kNV3mkEJnuI!29hCS%mW7Hq68H6nYMdN`jJB76nx{9wlvljnMX<4MPF*;>@8Z$PmYr7I6 zCKoH-oO-`qHzr*;^+Hm~pBM7O^MJa6blkH-H0`^EjIXcV5o z$!(kF@&)!RaQquIX!!VMG&0@YOEozkw0&!!CtbD<%PP3G(pbPPVHaR8GFRm zHP$nAFJ@HJc3(F_4j3!D0s=^yRL}EuS7JLS_k>I*3OSd7DrbPAS>IyOgbCSF$&=9qcV^j@`)K#@@qzoPC&mg#7~hFYMRYe`UYR{)l~< z{XIL4dmA^Kdk^1X)_)_4Pf!6~w_`CQA_$T=N{1RcQ5EhOL ze-NsJoxxj!4+Os&JQDo(;5)>{VuQFrRHd+Vi}Z78y?lrKdHE`3zw%w>lrmjiuTIl0 z)q>i?+I{+y`b+w&`nz#M!iNmo%3DuZsC*e#P~cs`E<{~F#BOGHvIE>R+z+^)b0@gh zxw`|8@z3!u@~j{Vw+j1&V?t^0%HV<^7c_!T2BXpm>1ml)_9-7!$J7CBz0Mf#H3CM& zxY@YP*lN7ZeBS)2c`22H3E<8T^hzJQo=tI|=Wnu7784@Nk>dY>y@u^#f5`rV{T=&0 zu7X?3-OGKM`x^HW_Zx0@U~WJOR0O&LnZW&lPX-PIejE6G;3~d?Px3u{MsS2`@kR01 z;%RY?bUChvte38rWw}aTA-`Wv$|LgkIxmsybwkf-m?~FQ`wc-%@|5{!G=h zh!)e*+Vfh2{v-Vl`d{>mjP=Hy#{I_UjVFyi8q>`hv(21l-DKTnJ!bvd`Zo(p5X??g z`C|48_Ig(3>NuP>9 z!Y1J^As4I>-!Fbfd_jCge3x{+6p%vFOVVsPAwMjCM*gOpP##mhs?@7%)Pw3v>TlFi zP0>2EKWZhoORiVnsNb*e*DH+`#uLUf#t)638Z*r@bE(;6wwqhb&zej-)|pYiFR;Vx z^Xwm3mJ>Lc)432=#f7;DPQx{GZQL5Jol9^@F3n{*hr5$|fIH3og%bm-0uKfr4g5NA zGSJ6=ir>Ynpu4AqmxU6HdTY=Q?h76Z{v~*=SS2RKL2@})vnh5Nz=9aw08X&{aO75 z{ac1);e10D{1A07;RaBr?Oapf@xTuQF9v4vHGDmv;@9za@ZaIr33mv;6)p*G3=RgL z3tk~Fl&j^Kyg|NSeoj6qU#5u4uaqU~x3x?3M~oB3Rn`JawhZe|Yr93=W$b zKuw@MkPB=KJQo-c<^~&rCxcDmi1<1A`|`(>Z)(@+A2hy-e-OoN_2!7f{+yl8eTDl5 zcZ6FX_)uV5faCuMf3eVy-f6&WxI%nD{H1tYtdbs(o|Jmza^)W7cS@tWNgY%-t3&D* z^+9#FTBd(lpJUWwv{sub^A7V~^Ygf4^jYhuMMTw%;tpaK{E~fz{U`3SfW^myUl6P1 zU(1!MpgpNwYV;Y87~e5oFkUrYH{NZsX0;hH6XvbvZRS(vx6PC0)z*zx$f~n8S*K{; z4SVx$HqH?3V!zHFWB-k9=eBXo2>q8thN)1(N?c8<%waFx=fs%OrQD!$>gDEDCK8O| zft;!YPkWs^!>tuFVp2*=S;>(Gq)pPGv{@RGwn$s0ZPIqoz)oqGv|HLE4NH5aebRnu zL^>cHlnzOUr6bZ&>6kPs9S6@iDUC^|q|?$FiIGd>S@LYTRGur(ljqB2aydqGk<7{h zX0R@YFpI--M2^bMa+|zHZpR!+%4s<(JMw_MNgk9p%R}-Od8@ol-Y)Nucgnlu-SQrJ zSl%n|llNopACM2qhvdWZ5&5WmOdgew%O^l1WAZ8aw0uTploDl@GFvHC<|^})`AQjR zccHRKVHH7<6gWlgb#l^J(Rb!l)(cEOoY8s?JsC zsq@t`wOn1OE>c-lP-RtDLu!>8RwHUuZC2aB585$*l4@Gbs*XBPL|I$aZR&P)hq_bU z1sdC<4y$|BeVAt>>H+YOL+WAmhq11h>p;jy9le(gwB7+K{$I+p2BT zwre}Io!TyKx3)(c*7j=qwEfzMc0fC*9nubKN3^5bF>O>kuAR_MYGc|d?X-4AWAqYz zmOfiA)#vK-^!a+3Ual|H7vcP@fO~*+J)~FRUV?}o)tmJ;eU09(CvZ+Wjk|dqeL&x& z59*usA$^O!Ro|v>*LUbU^x3R|?+N?EJyOpq#R@%y1jx_)dFlcSIhO8~t zR%;u0-wtc1waeOV?XiZfz1BW!zcpeVunt;>ti#q3@SkJWsCC>rVV$(bz&lP`XNZ4{ zVPDjA_-qL~i=EAuvUAya?0mM2Er-Ui2wX+LeK|TCVykclIl@NSX10x8!?v>t+%J@7 zv$&0DfZfCnLbDiRx3F8;ZR~b-2egb`>~3}sB-CDZAG@C&VGpne*+cAM_6U0vTxyg( z&YoaTvSaKi_B4BjWw;V<7IdXjZZ0>Eo6nVT<=jGU5%*?f+W@zT8{{@~&3qfbhHvK+ z&>GTwmUs98eiJ{)Z{~;iE&Nt~8^4|3!S963+s*IchxxtyK4@MeJpSzoe~3TKAK{Pk z$M{kHIDdja$&c};_|yCuo)JofS;C;OSr`(w2wR11!ggVYuv6G2>=yP2!@^!+pRivT z5e^6kg+sz&;fQb)TGS|Xxf8-kVN5tBoEFXqOt2(4D>yq?8k`%P7n~m~3zi2L1{Vd{ zpb(UUdN35M3WkG`U^LhqYzwXlwg=n9HDbG%fD}%PS$}!#pl+PU z0cl?f3Z6=@cY;bEXTQuH;0^~~hOT)fzk&Y@f0a-#M1?k?Q@C6Cgz~Vt$NYwQkM$7r z!pCW@lwvO(yes)dd>tR-ZvkC>3>5W6{wZjHukjbTI`p%`E5d8SUxl{^FAH89ydh`? z>w_zTpAC)#e-QiyG>{qM#p1if`OxVvkygu}lD`F==CJ%{`EAN&imKcM>3o~=i1JP4 zH_CelZ{D=HU`M>0u${gjL%C*W3%8g1;v6Nc$aZ1^M zUjDT573FE=N6M>^)_+zmQ% z7Tkbq!`%Zu`w4D0bk$?f>E9FR4&(#3LYG{|Z{+U=mpj5=CNv5k6&@F!6@Gx;{EKjL z@Y3KrgI9wh7X%jv`Cu;i_29394d5w{h(8i%OI6VK_CR-=EeD{5jmg(R7y7C4S@pZ> zCD0I_f>ggsZ^T^O0~@LYaLBFeL(o$HYFuo-)4UeCs%|bbcbK0uhavmOGJM(m z4e6Ls{I3ysI$y~pE>^h`5$#GAD4?${^y!IvaQDe+FWz059&4u6`tXXA-VF4!1 zv^i*QHiyuHooE3`@DWIDlGh~#`AkyTlf|A49)i>*d5d}sV(%>IeHe1&C?rUUFk6@_ z%oid;8rJJ3NRKU$96KO0h9M`8LMoI%7O>#_Rp9ms(D~-z_TcW|UReFdf+s-jb45W6 zK>{Sf{RhDLhrsjqg2x{ePl#ja7Y04D2=ic#lz_hOKv&-kJ$)NA^xe?P4?+_^0sVV6 zv~C$%b_6=LgSoO9y7V^a(YrBEjzC{N4qf>a^yCug$aA3|mqRxepchv`BhEtq-2rX) zAavVP&}B=Y$IgWgyAT>{GxXCz%#=OQJ&!=^WT0ivg-)r1TGl{|B+YRMS|jOWHJz49~#Qoehm}p-yMS=A-)tK+$`k8H{nW0xbB%Aij0{cf0=$Wa6wI literal 0 HcmV?d00001 diff --git a/Releases/olcBattleRoyale_CGE_RasterFont.exe b/Releases/olcBattleRoyale_CGE_RasterFont.exe new file mode 100644 index 0000000000000000000000000000000000000000..51e612dd96fca3da45fdc44e45ed89330d9bf3b1 GIT binary patch literal 75776 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 delta 28146 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 +#include "../Core/BattleRoyale_Engine.h" + #ifdef CGE_OLC #include "olcConsoleGameEngineOOP.h" #endif -#include "../Core/BattleRoyale_Engine.h" +#ifdef CGE_GL +#include "olcConsoleGameEngineGLOOP.h" +#endif + -#include #ifdef CGE_OLC class OneLoneCoder_BattleRoyaleConsole : public olcConsoleGameEngineOOP #endif + +#ifdef CGE_GL +class OneLoneCoder_BattleRoyaleConsole : public olcConsoleGameEngineGLOOP +#endif + { public: OneLoneCoder_BattleRoyaleConsole(); diff --git a/VS2018_BUILD_CGE/olcBattleRoyale.sln b/VS2018_BUILD_CGE/olcBattleRoyale.sln index 8a7d07a..cf08280 100644 --- a/VS2018_BUILD_CGE/olcBattleRoyale.sln +++ b/VS2018_BUILD_CGE/olcBattleRoyale.sln @@ -11,6 +11,10 @@ Global Debug|x86 = Debug|x86 Release_CGE_Big|x64 = Release_CGE_Big|x64 Release_CGE_Big|x86 = Release_CGE_Big|x86 + Release_CGE_GL_Big|x64 = Release_CGE_GL_Big|x64 + Release_CGE_GL_Big|x86 = Release_CGE_GL_Big|x86 + Release_CGE_Raster|x64 = Release_CGE_Raster|x64 + Release_CGE_Raster|x86 = Release_CGE_Raster|x86 Release_CGE_Small|x64 = Release_CGE_Small|x64 Release_CGE_Small|x86 = Release_CGE_Small|x86 Release|x64 = Release|x64 @@ -25,6 +29,14 @@ Global {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Big|x64.Build.0 = Release_CGE_Big|x64 {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Big|x86.ActiveCfg = Release_CGE_Big|Win32 {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Big|x86.Build.0 = Release_CGE_Big|Win32 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_GL_Big|x64.ActiveCfg = Release_CGE_GL_Big|x64 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_GL_Big|x64.Build.0 = Release_CGE_GL_Big|x64 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_GL_Big|x86.ActiveCfg = Release_CGE_GL_Big|Win32 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_GL_Big|x86.Build.0 = Release_CGE_GL_Big|Win32 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Raster|x64.ActiveCfg = Release_CGE_Raster|x64 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Raster|x64.Build.0 = Release_CGE_Raster|x64 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Raster|x86.ActiveCfg = Release_CGE_Raster|Win32 + {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Raster|x86.Build.0 = Release_CGE_Raster|Win32 {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Small|x64.ActiveCfg = Release_CGE_Small|x64 {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Small|x64.Build.0 = Release_CGE_Small|x64 {57B3E944-D224-4D2A-92F9-674070570C12}.Release_CGE_Small|x86.ActiveCfg = Release_CGE_Small|Win32 diff --git a/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj b/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj index 15f27e1..f5823b3 100644 --- a/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj +++ b/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj @@ -13,6 +13,22 @@ Release_CGE_Big x64 + + Release_CGE_GL_Big + Win32 + + + Release_CGE_GL_Big + x64 + + + Release_CGE_Raster + Win32 + + + Release_CGE_Raster + x64 + Release_CGE_Small Win32 @@ -40,14 +56,21 @@ - + + + true + - + + + + true + 15.0 @@ -77,6 +100,13 @@ true Unicode + + Application + false + v141 + true + Unicode + Application false @@ -84,6 +114,13 @@ true Unicode + + Application + false + v141 + true + Unicode + Application true @@ -104,6 +141,13 @@ true Unicode + + Application + false + v141 + true + Unicode + Application false @@ -111,6 +155,13 @@ true Unicode + + Application + false + v141 + true + Unicode + @@ -125,9 +176,15 @@ + + + + + + @@ -137,9 +194,15 @@ + + + + + + true @@ -165,6 +228,13 @@ $(VC_IncludePath);$(WindowsSDK_IncludePath); .\BuildOutputs\ + + false + ..\Releases\ + olcBattleRoyale_CGE_GL_Big + $(VC_IncludePath);$(WindowsSDK_IncludePath); + .\BuildOutputs\ + false ..\Releases\ @@ -172,15 +242,28 @@ $(VC_IncludePath);$(WindowsSDK_IncludePath); .\BuildOutputs\ + + false + ..\Releases\ + olcBattleRoyale_CGE_RasterFont + $(VC_IncludePath);$(WindowsSDK_IncludePath); + .\BuildOutputs\ + false false + + false + false + + false + NotUsing @@ -257,6 +340,29 @@ liblua53.a;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + NotUsing + Level3 + MaxSpeed + true + true + true + CGE_GL;SIZE_BIG;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + "..\Lua533\include" + + + Console + true + true + true + "..\Lua533" + liblua53.a;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + NotUsing @@ -279,6 +385,29 @@ liblua53.a;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + + NotUsing + Level3 + MaxSpeed + true + true + true + RASTER_FONT;CGE_OLC;SIZE_SMALL;WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + "..\Lua533\include" + + + Console + true + true + true + "..\Lua533" + liblua53.a;kernel32.lib;user32.lib;gdi32.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies) + + Use @@ -315,6 +444,24 @@ true + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + Use @@ -333,6 +480,24 @@ true + + + Use + Level3 + MaxSpeed + true + true + true + NDEBUG;_CONSOLE;%(PreprocessorDefinitions) + true + + + Console + true + true + true + + diff --git a/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj.filters b/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj.filters index f2e024c..631a2a9 100644 --- a/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj.filters +++ b/VS2018_BUILD_CGE/olcBattleRoyale.vcxproj.filters @@ -33,6 +33,9 @@ Source Files + + Source Files + @@ -50,5 +53,11 @@ Header Files + + Header Files + + + Header Files + \ No newline at end of file diff --git a/VS2018_BUILD_CGE/olcConsoleGameEngineGL.h b/VS2018_BUILD_CGE/olcConsoleGameEngineGL.h new file mode 100644 index 0000000..b28d0fc --- /dev/null +++ b/VS2018_BUILD_CGE/olcConsoleGameEngineGL.h @@ -0,0 +1,2043 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 +License +~~~~~~~ +One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions; See license for details. +Original works located at: +https://www.github.com/onelonecoder +https://www.onelonecoder.com +https://www.youtube.com/javidx9 +GNU GPLv3 +https://github.com/OneLoneCoder/videos/blob/master/LICENSE +From Javidx9 :) +~~~~~~~~~~~~~~~ +Hello! Ultimately I don't care what you use this for. It's intended to be +educational, and perhaps to the oddly minded - a little bit of fun. +Please hack this, change it and use it in any way you see fit. You acknowledge +that I am not responsible for anything bad that happens as a result of +your actions. However this code is protected by GNU GPLv3, see the license in the +github repo. This means you must attribute me if you use it. You can view this +license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE +Cheers! +Background +~~~~~~~~~~ +If you've seen any of my videos - I like to do things using the windows console. It's quick +and easy, and allows you to focus on just the code that matters - ideal when you're +experimenting. Thing is, I have to keep doing the same initialisation and display code +each time, so this class wraps that up. +Author +~~~~~~ +Twitter: @javidx9 http://twitter.com/javidx9 +Blog: http://www.onelonecoder.com +YouTube: http://www.youtube.com/javidx9 +Videos: +~~~~~~ +Original: https://youtu.be/cWc0hgYwZyc +Added mouse support: https://youtu.be/tdqc9hZhHxM +Beginners Guide: https://youtu.be/u5BhrA8ED0o +Shout Outs! +~~~~~~~~~~~ +Thanks to cool people who helped with testing, bug-finding and fixing! +wowLinh, JavaJack59, idkwid, kingtatgi, Return Null, CPP Guy +Last Updated: 18/03/2018 +Usage: +~~~~~~ +This class is abstract, so you must inherit from it. Override the OnUserCreate() function +with all the stuff you need for your application (for thready reasons it's best to do +this in this function and not your class constructor). Override the OnUserUpdate(float fElapsedTime) +function with the good stuff, it gives you the elapsed time since the last call so you +can modify your stuff dynamically. Both functions should return true, unless you need +the application to close. +int main() +{ +// Use olcConsoleGameEngine derived app +OneLoneCoder_Example game; +// Create a console with resolution 160x100 characters +// Each character occupies 8x8 pixels +game.ConstructConsole(160, 100, 8, 8); +// Start the engine! +game.Start(); +return 0; +} +Input is also handled for you - interrogate the m_keys[] array with the virtual +keycode you want to know about. bPressed is set for the frame the key is pressed down +in, bHeld is set if the key is held down, bReleased is set for the frame the key +is released in. The same applies to mouse! m_mousePosX and Y can be used to get +the current cursor position, and m_mouse[1..5] returns the mouse buttons. +The draw routines treat characters like pixels. By default they are set to white solid +blocks - but you can draw any unicode character, using any of the colours listed below. +There may be bugs! +See my other videos for examples! +http://www.youtube.com/javidx9 +Lots of programs to try: +http://www.github.com/OneLoneCoder/videos +Chat on the Discord server: +https://discord.gg/WhwHUMV +Be bored by Twitch: +http://www.twitch.tv/javidx9 +*/ + +#pragma once + +#ifndef UNICODE +#pragma message("Please enable UNICODE for your compiler! VS: Project Properties -> General -> \ +Character Set -> Use Unicode. Thanks! For now, I'll try enabling it for you - Javidx9") +#define UNICODE +#define _UNICODE +#endif + +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "winmm.lib") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +typedef BOOL(WINAPI wglSwapInterval_t) (int interval); +wglSwapInterval_t *wglSwapInterval; + +enum COLOUR +{ + FG_BLACK = 0x0000, + FG_DARK_BLUE = 0x0001, + FG_DARK_GREEN = 0x0002, + FG_DARK_CYAN = 0x0003, + FG_DARK_RED = 0x0004, + FG_DARK_MAGENTA = 0x0005, + FG_DARK_YELLOW = 0x0006, + FG_GREY = 0x0007, // Thanks MS :-/ + FG_DARK_GREY = 0x0008, + FG_BLUE = 0x0009, + FG_GREEN = 0x000A, + FG_CYAN = 0x000B, + FG_RED = 0x000C, + FG_MAGENTA = 0x000D, + FG_YELLOW = 0x000E, + FG_WHITE = 0x000F, + BG_BLACK = 0x0000, + BG_DARK_BLUE = 0x0010, + BG_DARK_GREEN = 0x0020, + BG_DARK_CYAN = 0x0030, + BG_DARK_RED = 0x0040, + BG_DARK_MAGENTA = 0x0050, + BG_DARK_YELLOW = 0x0060, + BG_GREY = 0x0070, + BG_DARK_GREY = 0x0080, + BG_BLUE = 0x0090, + BG_GREEN = 0x00A0, + BG_CYAN = 0x00B0, + BG_RED = 0x00C0, + BG_MAGENTA = 0x00D0, + BG_YELLOW = 0x00E0, + BG_WHITE = 0x00F0, +}; +enum PIXEL_TYPE +{ + PIXEL_SOLID = 0x2588, + PIXEL_THREEQUARTERS = 0x2593, + PIXEL_HALF = 0x2592, + PIXEL_QUARTER = 0x2591, +}; + +// Based on the PxPlus IBM CGA Font from +// "The Ultimate Oldschool PC Font Pack" http://int10h.org/oldschool-pc-fonts/ +// Rendered into an image (256x56) and converted to char array +// +// The Ultimate Oldschool PC Font Pack is licensed under a Creative Commons +// Attribution-ShareAlike 4.0 International License. +// +// You should have received a copy of the license along with this work. If +// not, see < http://creativecommons.org/licenses/by-sa/4.0/ >. +// +// (c) 2016 VileR +// +// (256x256x8) +unsigned char pxplus_ibm_cga[65536]; +char pxplus_ibm_cga_enc[8509 + 1] = +"0mWOkP0780et0q?qqedtH40@HHVtq106HP10000000001stOLm320q??33cIa?sqlOq3?0000000000200000000mPf1c1030H00e0P=0H000H03000000000e0000000003Hee>8PQ76m1cbS?s0H30sHk0hqS1hH63em13t=hNmV7?0hINhO?00HmlIsq36L0@Fsf<<0D50000000P?H3060mhNqmQ=kehHW=hsf16q=k?e30VQfmQ30003>mQ3S=6<0HSIWI036eP16Hk0he03dnf0m1>m0000mP30000>qP3eQhH0Hh<6<60t1eHhe60q10Ler30kHSlme17N0036e000L60Lmf0W100tmS70eV=0000V107L<30kHh<0eV3qLk7drfI00eKOe1" +"0L00706mS?0HP?m<3m00000000e17NmW" +"7snQONmW7snQ?SIS7emfOtql?trh300e3>0000000000000000000e17000P700000000000L0q3?q07LeQS70q320q32KmhH0e033l30=jF6Hm760m1Os?W?6m33smV7k00000P=0000<@00VQ13aQ1?KeQ7eH3mfH34kPfhakH<Omf<30S=hee6hqW7e00008002000000060ef1kq1LqmQ7hPl7Kq167ef<pLkIWnh?hqh??qlO[nfHSMkHS1e0" +"000000000000000000000e000000S10000000000hef?teh?hPeHhNqhm0pLkI0000?000Oe10P100P10000000000e0000000N0000000<00H0mlHOefOtm0?t<3?trh0ta7>e13Le130HS3HP176H03tPfk0a=00006600000e000000000000" +"00e03e1000000q77O00?>CeqOh103?qe3?qe7h" +"qe??qf?t<0H00000006IhL[sk0eHS=VISI6Kc=kHSfkHaf0?000000P5d:>kt1P1Htr3006H000HP160P10aQ1KaQ1Ka10" +"000000P1KaQ1KaQ1KaQ160006HP10H00KHe6KHe6K00000000He6KHe6KHe6KHP100P16H0060e66af66af6000000006af66af66af66af66afkt00et?0?00D5000" +"0000@0004200ett3POsQ1KaQ1Kar?00ip3e30AZ2000000HLetef1?qqte04mtthkpthkpt300tt0qT8BGqm30000?Oq?OkHVOWW97Uc=0a106a1et00er?0?00X>?k" +"eS?0mlOsrlH2nlerJCPttttttttd300tts3e300GfS=qHSOtt1?qIS@s=Ldkipe0000He6K00ettt0qT@TZqm3?smlt7P1LLHVOWW97IN>00006af60ttOd?0?0Ri50" +"0000s1@00042qeht33@Pt3000P1Ka1etttt3e300Z2000000000000000qtt0mWOHe3q8eQ=8edt0h7O33000000000000000000000000ekV1>2sq77> 0) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 1) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 2) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 3) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 4) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 5) & 1) * 0xFF; + } +} + +#define FUV(a,b,c) case a: *x=b; *y=c; break; + +void GetFontCoords(int id, int *x, int *y) { + switch (id) { + FUV(0, 0, 0)FUV(1, 8, 0)FUV(2, 16, 0)FUV(3, 24, 0)FUV(4, 32, 0)FUV(5, 40, 0)FUV(6, 48, 0) + FUV(7, 56, 0)FUV(8, 64, 0)FUV(9, 72, 0)FUV(10, 80, 0)FUV(11, 88, 0)FUV(12, 96, 0)FUV(13, 104, 0) + FUV(14, 112, 0)FUV(15, 120, 0)FUV(16, 128, 0)FUV(17, 136, 0)FUV(18, 144, 0)FUV(19, 152, 0)FUV(20, 160, 0) + FUV(21, 168, 0)FUV(22, 176, 0)FUV(23, 184, 0)FUV(24, 192, 0)FUV(25, 200, 0)FUV(26, 208, 0)FUV(27, 216, 0) + FUV(28, 224, 0)FUV(29, 232, 0)FUV(30, 240, 0)FUV(31, 248, 0)FUV(32, 0, 8)FUV(33, 8, 8)FUV(34, 16, 8) + FUV(35, 24, 8)FUV(36, 32, 8)FUV(37, 40, 8)FUV(38, 48, 8)FUV(39, 56, 8)FUV(40, 64, 8)FUV(41, 72, 8) + FUV(42, 80, 8)FUV(43, 88, 8)FUV(44, 96, 8)FUV(45, 104, 8)FUV(46, 112, 8)FUV(47, 120, 8)FUV(48, 128, 8) + FUV(49, 136, 8)FUV(50, 144, 8)FUV(51, 152, 8)FUV(52, 160, 8)FUV(53, 168, 8)FUV(54, 176, 8)FUV(55, 184, 8) + FUV(56, 192, 8)FUV(57, 200, 8)FUV(58, 208, 8)FUV(59, 216, 8)FUV(60, 224, 8)FUV(61, 232, 8)FUV(62, 240, 8) + FUV(63, 248, 8)FUV(64, 0, 16)FUV(65, 8, 16)FUV(66, 16, 16)FUV(67, 24, 16)FUV(68, 32, 16)FUV(69, 40, 16) + FUV(70, 48, 16)FUV(71, 56, 16)FUV(72, 64, 16)FUV(73, 72, 16)FUV(74, 80, 16)FUV(75, 88, 16)FUV(76, 96, 16) + FUV(77, 104, 16)FUV(78, 112, 16)FUV(79, 120, 16)FUV(80, 128, 16)FUV(81, 136, 16)FUV(82, 144, 16)FUV(83, 152, 16) + FUV(84, 160, 16)FUV(85, 168, 16)FUV(86, 176, 16)FUV(87, 184, 16)FUV(88, 192, 16)FUV(89, 200, 16)FUV(90, 208, 16) + FUV(91, 216, 16)FUV(92, 224, 16)FUV(93, 232, 16)FUV(94, 240, 16)FUV(95, 248, 16)FUV(96, 0, 24)FUV(97, 8, 24) + FUV(98, 16, 24)FUV(99, 24, 24)FUV(100, 32, 24)FUV(101, 40, 24)FUV(102, 48, 24)FUV(103, 56, 24)FUV(104, 64, 24) + FUV(105, 72, 24)FUV(106, 80, 24)FUV(107, 88, 24)FUV(108, 96, 24)FUV(109, 104, 24)FUV(110, 112, 24)FUV(111, 120, 24) + FUV(112, 128, 24)FUV(113, 136, 24)FUV(114, 144, 24)FUV(115, 152, 24)FUV(116, 160, 24)FUV(117, 168, 24)FUV(118, 176, 24) + FUV(119, 184, 24)FUV(120, 192, 24)FUV(121, 200, 24)FUV(122, 208, 24)FUV(123, 216, 24)FUV(124, 224, 24)FUV(125, 232, 24) + FUV(126, 240, 24)FUV(127, 248, 24)FUV(160, 0, 32)FUV(161, 8, 32)FUV(162, 16, 32)FUV(163, 24, 32)FUV(164, 32, 32) + FUV(165, 40, 32)FUV(166, 48, 32)FUV(167, 56, 32)FUV(168, 64, 32)FUV(169, 72, 32)FUV(170, 80, 32)FUV(171, 88, 32) + FUV(172, 96, 32)FUV(173, 104, 32)FUV(174, 112, 32)FUV(175, 120, 32)FUV(176, 128, 32)FUV(177, 136, 32)FUV(178, 144, 32) + FUV(179, 152, 32)FUV(180, 160, 32)FUV(181, 168, 32)FUV(182, 176, 32)FUV(183, 184, 32)FUV(184, 192, 32)FUV(185, 200, 32) + FUV(186, 208, 32)FUV(187, 216, 32)FUV(188, 224, 32)FUV(189, 232, 32)FUV(190, 240, 32)FUV(191, 248, 32)FUV(192, 0, 40) + FUV(193, 8, 40)FUV(194, 16, 40)FUV(195, 24, 40)FUV(196, 32, 40)FUV(197, 40, 40)FUV(198, 48, 40)FUV(199, 56, 40) + FUV(200, 64, 40)FUV(201, 72, 40)FUV(202, 80, 40)FUV(203, 88, 40)FUV(204, 96, 40)FUV(205, 104, 40)FUV(206, 112, 40) + FUV(207, 120, 40)FUV(208, 128, 40)FUV(209, 136, 40)FUV(210, 144, 40)FUV(211, 152, 40)FUV(212, 160, 40)FUV(213, 168, 40) + FUV(214, 176, 40)FUV(215, 184, 40)FUV(216, 192, 40)FUV(217, 200, 40)FUV(218, 208, 40)FUV(219, 216, 40)FUV(220, 224, 40) + FUV(221, 232, 40)FUV(222, 240, 40)FUV(223, 248, 40)FUV(224, 0, 48)FUV(225, 8, 48)FUV(226, 16, 48)FUV(227, 24, 48) + FUV(228, 32, 48)FUV(229, 40, 48)FUV(230, 48, 48)FUV(231, 56, 48)FUV(232, 64, 48)FUV(233, 72, 48)FUV(234, 80, 48) + FUV(235, 88, 48)FUV(236, 96, 48)FUV(237, 104, 48)FUV(238, 112, 48)FUV(239, 120, 48)FUV(240, 128, 48)FUV(241, 136, 48) + FUV(242, 144, 48)FUV(243, 152, 48)FUV(244, 160, 48)FUV(245, 168, 48)FUV(246, 176, 48)FUV(247, 184, 48)FUV(248, 192, 48) + FUV(249, 200, 48)FUV(250, 208, 48)FUV(251, 216, 48)FUV(252, 224, 48)FUV(253, 232, 48)FUV(254, 240, 48)FUV(255, 248, 48) + FUV(256, 0, 56)FUV(257, 8, 56)FUV(258, 16, 56)FUV(259, 24, 56)FUV(260, 32, 56)FUV(261, 40, 56)FUV(262, 48, 56) + FUV(263, 56, 56)FUV(264, 64, 56)FUV(265, 72, 56)FUV(266, 80, 56)FUV(267, 88, 56)FUV(268, 96, 56)FUV(269, 104, 56) + FUV(270, 112, 56)FUV(271, 120, 56)FUV(272, 128, 56)FUV(273, 136, 56)FUV(274, 144, 56)FUV(275, 152, 56)FUV(276, 160, 56) + FUV(277, 168, 56)FUV(278, 176, 56)FUV(279, 184, 56)FUV(280, 192, 56)FUV(281, 200, 56)FUV(282, 208, 56)FUV(283, 216, 56) + FUV(284, 224, 56)FUV(285, 232, 56)FUV(286, 240, 56)FUV(287, 248, 56)FUV(288, 0, 64)FUV(289, 8, 64)FUV(290, 16, 64) + FUV(291, 24, 64)FUV(292, 32, 64)FUV(293, 40, 64)FUV(294, 48, 64)FUV(295, 56, 64)FUV(296, 64, 64)FUV(297, 72, 64) + FUV(298, 80, 64)FUV(299, 88, 64)FUV(300, 96, 64)FUV(301, 104, 64)FUV(302, 112, 64)FUV(303, 120, 64)FUV(304, 128, 64) + FUV(305, 136, 64)FUV(306, 144, 64)FUV(307, 152, 64)FUV(308, 160, 64)FUV(309, 168, 64)FUV(310, 176, 64)FUV(311, 184, 64) + FUV(312, 192, 64)FUV(313, 200, 64)FUV(314, 208, 64)FUV(315, 216, 64)FUV(316, 224, 64)FUV(317, 232, 64)FUV(318, 240, 64) + FUV(319, 248, 64)FUV(320, 0, 72)FUV(321, 8, 72)FUV(322, 16, 72)FUV(323, 24, 72)FUV(324, 32, 72)FUV(325, 40, 72) + FUV(326, 48, 72)FUV(327, 56, 72)FUV(328, 64, 72)FUV(329, 72, 72)FUV(330, 80, 72)FUV(331, 88, 72)FUV(332, 96, 72) + FUV(333, 104, 72)FUV(334, 112, 72)FUV(335, 120, 72)FUV(336, 128, 72)FUV(337, 136, 72)FUV(338, 144, 72)FUV(339, 152, 72) + FUV(340, 160, 72)FUV(341, 168, 72)FUV(342, 176, 72)FUV(343, 184, 72)FUV(344, 192, 72)FUV(345, 200, 72)FUV(346, 208, 72) + FUV(347, 216, 72)FUV(348, 224, 72)FUV(349, 232, 72)FUV(350, 240, 72)FUV(351, 248, 72)FUV(352, 0, 80)FUV(353, 8, 80) + FUV(354, 16, 80)FUV(355, 24, 80)FUV(356, 32, 80)FUV(357, 40, 80)FUV(358, 48, 80)FUV(359, 56, 80)FUV(360, 64, 80) + FUV(361, 72, 80)FUV(362, 80, 80)FUV(363, 88, 80)FUV(364, 96, 80)FUV(365, 104, 80)FUV(366, 112, 80)FUV(367, 120, 80) + FUV(368, 128, 80)FUV(369, 136, 80)FUV(370, 144, 80)FUV(371, 152, 80)FUV(372, 160, 80)FUV(373, 168, 80)FUV(374, 176, 80) + FUV(375, 184, 80)FUV(376, 192, 80)FUV(377, 200, 80)FUV(378, 208, 80)FUV(379, 216, 80)FUV(380, 224, 80)FUV(381, 232, 80) + FUV(382, 240, 80)FUV(383, 248, 80)FUV(402, 0, 88)FUV(417, 8, 88)FUV(439, 16, 88)FUV(506, 24, 88)FUV(507, 32, 88) + FUV(508, 40, 88)FUV(509, 48, 88)FUV(510, 56, 88)FUV(511, 64, 88)FUV(536, 72, 88)FUV(537, 80, 88)FUV(538, 88, 88) + FUV(539, 96, 88)FUV(593, 104, 88)FUV(632, 112, 88)FUV(710, 120, 88)FUV(711, 128, 88)FUV(713, 136, 88)FUV(728, 144, 88) + FUV(729, 152, 88)FUV(730, 160, 88)FUV(731, 168, 88)FUV(732, 176, 88)FUV(733, 184, 88)FUV(894, 192, 88)FUV(900, 200, 88) + FUV(901, 208, 88)FUV(902, 216, 88)FUV(903, 224, 88)FUV(904, 232, 88)FUV(905, 240, 88)FUV(906, 248, 88)FUV(908, 0, 96) + FUV(910, 8, 96)FUV(911, 16, 96)FUV(912, 24, 96)FUV(913, 32, 96)FUV(914, 40, 96)FUV(915, 48, 96)FUV(916, 56, 96) + FUV(917, 64, 96)FUV(918, 72, 96)FUV(919, 80, 96)FUV(920, 88, 96)FUV(921, 96, 96)FUV(922, 104, 96)FUV(923, 112, 96) + FUV(924, 120, 96)FUV(925, 128, 96)FUV(926, 136, 96)FUV(927, 144, 96)FUV(928, 152, 96)FUV(929, 160, 96)FUV(931, 168, 96) + FUV(932, 176, 96)FUV(933, 184, 96)FUV(934, 192, 96)FUV(935, 200, 96)FUV(936, 208, 96)FUV(937, 216, 96)FUV(938, 224, 96) + FUV(939, 232, 96)FUV(940, 240, 96)FUV(941, 248, 96)FUV(942, 0, 104)FUV(943, 8, 104)FUV(944, 16, 104)FUV(945, 24, 104) + FUV(946, 32, 104)FUV(947, 40, 104)FUV(948, 48, 104)FUV(949, 56, 104)FUV(950, 64, 104)FUV(951, 72, 104)FUV(952, 80, 104) + FUV(953, 88, 104)FUV(954, 96, 104)FUV(955, 104, 104)FUV(956, 112, 104)FUV(957, 120, 104)FUV(958, 128, 104)FUV(959, 136, 104) + FUV(960, 144, 104)FUV(961, 152, 104)FUV(962, 160, 104)FUV(963, 168, 104)FUV(964, 176, 104)FUV(965, 184, 104)FUV(966, 192, 104) + FUV(967, 200, 104)FUV(968, 208, 104)FUV(969, 216, 104)FUV(970, 224, 104)FUV(971, 232, 104)FUV(972, 240, 104)FUV(973, 248, 104) + FUV(974, 0, 112)FUV(976, 8, 112)FUV(1012, 16, 112)FUV(1024, 24, 112)FUV(1025, 32, 112)FUV(1026, 40, 112)FUV(1027, 48, 112) + FUV(1028, 56, 112)FUV(1029, 64, 112)FUV(1030, 72, 112)FUV(1031, 80, 112)FUV(1032, 88, 112)FUV(1033, 96, 112)FUV(1034, 104, 112) + FUV(1035, 112, 112)FUV(1036, 120, 112)FUV(1037, 128, 112)FUV(1038, 136, 112)FUV(1039, 144, 112)FUV(1040, 152, 112)FUV(1041, 160, 112) + FUV(1042, 168, 112)FUV(1043, 176, 112)FUV(1044, 184, 112)FUV(1045, 192, 112)FUV(1046, 200, 112)FUV(1047, 208, 112)FUV(1048, 216, 112) + FUV(1049, 224, 112)FUV(1050, 232, 112)FUV(1051, 240, 112)FUV(1052, 248, 112)FUV(1053, 0, 120)FUV(1054, 8, 120)FUV(1055, 16, 120) + FUV(1056, 24, 120)FUV(1057, 32, 120)FUV(1058, 40, 120)FUV(1059, 48, 120)FUV(1060, 56, 120)FUV(1061, 64, 120)FUV(1062, 72, 120) + FUV(1063, 80, 120)FUV(1064, 88, 120)FUV(1065, 96, 120)FUV(1066, 104, 120)FUV(1067, 112, 120)FUV(1068, 120, 120)FUV(1069, 128, 120) + FUV(1070, 136, 120)FUV(1071, 144, 120)FUV(1072, 152, 120)FUV(1073, 160, 120)FUV(1074, 168, 120)FUV(1075, 176, 120)FUV(1076, 184, 120) + FUV(1077, 192, 120)FUV(1078, 200, 120)FUV(1079, 208, 120)FUV(1080, 216, 120)FUV(1081, 224, 120)FUV(1082, 232, 120)FUV(1083, 240, 120) + FUV(1084, 248, 120)FUV(1085, 0, 128)FUV(1086, 8, 128)FUV(1087, 16, 128)FUV(1088, 24, 128)FUV(1089, 32, 128)FUV(1090, 40, 128) + FUV(1091, 48, 128)FUV(1092, 56, 128)FUV(1093, 64, 128)FUV(1094, 72, 128)FUV(1095, 80, 128)FUV(1096, 88, 128)FUV(1097, 96, 128) + FUV(1098, 104, 128)FUV(1099, 112, 128)FUV(1100, 120, 128)FUV(1101, 128, 128)FUV(1102, 136, 128)FUV(1103, 144, 128)FUV(1104, 152, 128) + FUV(1105, 160, 128)FUV(1106, 168, 128)FUV(1107, 176, 128)FUV(1108, 184, 128)FUV(1109, 192, 128)FUV(1110, 200, 128)FUV(1111, 208, 128) + FUV(1112, 216, 128)FUV(1113, 224, 128)FUV(1114, 232, 128)FUV(1115, 240, 128)FUV(1116, 248, 128)FUV(1117, 0, 136)FUV(1118, 8, 136) + FUV(1119, 16, 136)FUV(1168, 24, 136)FUV(1169, 32, 136)FUV(1470, 40, 136)FUV(1488, 48, 136)FUV(1489, 56, 136)FUV(1490, 64, 136) + FUV(1491, 72, 136)FUV(1492, 80, 136)FUV(1493, 88, 136)FUV(1494, 96, 136)FUV(1495, 104, 136)FUV(1496, 112, 136)FUV(1497, 120, 136) + FUV(1498, 128, 136)FUV(1499, 136, 136)FUV(1500, 144, 136)FUV(1501, 152, 136)FUV(1502, 160, 136)FUV(1503, 168, 136)FUV(1504, 176, 136) + FUV(1505, 184, 136)FUV(1506, 192, 136)FUV(1507, 200, 136)FUV(1508, 208, 136)FUV(1509, 216, 136)FUV(1510, 224, 136)FUV(1511, 232, 136) + FUV(1512, 240, 136)FUV(1513, 248, 136)FUV(1514, 0, 144)FUV(1520, 8, 144)FUV(1521, 16, 144)FUV(1522, 24, 144)FUV(1523, 32, 144) + FUV(1524, 40, 144)FUV(7451, 48, 144)FUV(7462, 56, 144)FUV(7464, 64, 144)FUV(7808, 72, 144)FUV(7809, 80, 144)FUV(7810, 88, 144) + FUV(7811, 96, 144)FUV(7812, 104, 144)FUV(7813, 112, 144)FUV(7839, 120, 144)FUV(7922, 128, 144)FUV(7923, 136, 144)FUV(8208, 144, 144) + FUV(8210, 152, 144)FUV(8211, 160, 144)FUV(8212, 168, 144)FUV(8213, 176, 144)FUV(8215, 184, 144)FUV(8216, 192, 144)FUV(8217, 200, 144) + FUV(8218, 208, 144)FUV(8219, 216, 144)FUV(8220, 224, 144)FUV(8221, 232, 144)FUV(8222, 240, 144)FUV(8223, 248, 144)FUV(8224, 0, 152) + FUV(8225, 8, 152)FUV(8226, 16, 152)FUV(8230, 24, 152)FUV(8231, 32, 152)FUV(8240, 40, 152)FUV(8242, 48, 152)FUV(8243, 56, 152) + FUV(8245, 64, 152)FUV(8249, 72, 152)FUV(8250, 80, 152)FUV(8252, 88, 152)FUV(8254, 96, 152)FUV(8255, 104, 152)FUV(8256, 112, 152) + FUV(8260, 120, 152)FUV(8276, 128, 152)FUV(8308, 136, 152)FUV(8309, 144, 152)FUV(8310, 152, 152)FUV(8311, 160, 152)FUV(8312, 168, 152) + FUV(8313, 176, 152)FUV(8314, 184, 152)FUV(8315, 192, 152)FUV(8319, 200, 152)FUV(8321, 208, 152)FUV(8322, 216, 152)FUV(8323, 224, 152) + FUV(8324, 232, 152)FUV(8325, 240, 152)FUV(8326, 248, 152)FUV(8327, 0, 160)FUV(8328, 8, 160)FUV(8329, 16, 160)FUV(8330, 24, 160) + FUV(8331, 32, 160)FUV(8355, 40, 160)FUV(8356, 48, 160)FUV(8359, 56, 160)FUV(8362, 64, 160)FUV(8364, 72, 160)FUV(8453, 80, 160) + FUV(8467, 88, 160)FUV(8470, 96, 160)FUV(8482, 104, 160)FUV(8486, 112, 160)FUV(8494, 120, 160)FUV(8528, 128, 160)FUV(8529, 136, 160) + FUV(8531, 144, 160)FUV(8532, 152, 160)FUV(8533, 160, 160)FUV(8534, 168, 160)FUV(8535, 176, 160)FUV(8536, 184, 160)FUV(8537, 192, 160) + FUV(8538, 200, 160)FUV(8539, 208, 160)FUV(8540, 216, 160)FUV(8541, 224, 160)FUV(8542, 232, 160)FUV(8592, 240, 160)FUV(8593, 248, 160) + FUV(8594, 0, 168)FUV(8595, 8, 168)FUV(8596, 16, 168)FUV(8597, 24, 168)FUV(8616, 32, 168)FUV(8706, 40, 168)FUV(8709, 48, 168) + FUV(8710, 56, 168)FUV(8712, 64, 168)FUV(8719, 72, 168)FUV(8721, 80, 168)FUV(8722, 88, 168)FUV(8725, 96, 168)FUV(8729, 104, 168) + FUV(8730, 112, 168)FUV(8734, 120, 168)FUV(8735, 128, 168)FUV(8745, 136, 168)FUV(8747, 144, 168)FUV(8776, 152, 168)FUV(8800, 160, 168) + FUV(8801, 168, 168)FUV(8804, 176, 168)FUV(8805, 184, 168)FUV(8857, 192, 168)FUV(8960, 200, 168)FUV(8962, 208, 168)FUV(8976, 216, 168) + FUV(8992, 224, 168)FUV(8993, 232, 168)FUV(9472, 240, 168)FUV(9474, 248, 168)FUV(9484, 0, 176)FUV(9488, 8, 176)FUV(9492, 16, 176) + FUV(9496, 24, 176)FUV(9500, 32, 176)FUV(9508, 40, 176)FUV(9516, 48, 176)FUV(9524, 56, 176)FUV(9532, 64, 176)FUV(9552, 72, 176) + FUV(9553, 80, 176)FUV(9554, 88, 176)FUV(9555, 96, 176)FUV(9556, 104, 176)FUV(9557, 112, 176)FUV(9558, 120, 176)FUV(9559, 128, 176) + FUV(9560, 136, 176)FUV(9561, 144, 176)FUV(9562, 152, 176)FUV(9563, 160, 176)FUV(9564, 168, 176)FUV(9565, 176, 176)FUV(9566, 184, 176) + FUV(9567, 192, 176)FUV(9568, 200, 176)FUV(9569, 208, 176)FUV(9570, 216, 176)FUV(9571, 224, 176)FUV(9572, 232, 176)FUV(9573, 240, 176) + FUV(9574, 248, 176)FUV(9575, 0, 184)FUV(9576, 8, 184)FUV(9577, 16, 184)FUV(9578, 24, 184)FUV(9579, 32, 184)FUV(9580, 40, 184) + FUV(9600, 48, 184)FUV(9601, 56, 184)FUV(9604, 64, 184)FUV(9608, 72, 184)FUV(9612, 80, 184)FUV(9616, 88, 184)FUV(9617, 96, 184) + FUV(9618, 104, 184)FUV(9619, 112, 184)FUV(9632, 120, 184)FUV(9633, 128, 184)FUV(9642, 136, 184)FUV(9643, 144, 184)FUV(9644, 152, 184) + FUV(9650, 160, 184)FUV(9658, 168, 184)FUV(9660, 176, 184)FUV(9668, 184, 184)FUV(9674, 192, 184)FUV(9675, 200, 184)FUV(9679, 208, 184) + FUV(9688, 216, 184)FUV(9689, 224, 184)FUV(9702, 232, 184)FUV(9786, 240, 184)FUV(9787, 248, 184)FUV(9788, 0, 192)FUV(9792, 8, 192) + FUV(9794, 16, 192)FUV(9824, 24, 192)FUV(9827, 32, 192)FUV(9829, 40, 192)FUV(9830, 48, 192)FUV(9834, 56, 192)FUV(9835, 64, 192) + FUV(10003, 72, 192)FUV(64257, 80, 192)FUV(64258, 88, 192)FUV(65533, 96, 192) default: *x = 96; *y = 192; break; + } +} + +class olcSprite +{ +public: + olcSprite() + { + + } + + olcSprite(int w, int h) + { + Create(w, h); + } + + olcSprite(wstring sFile) + { + if (!Load(sFile)) + Create(8, 8); + } + + int nWidth = 0; + int nHeight = 0; + + //Yer touching private things king! + //private: + wchar_t *m_Glyphs = nullptr; + short *m_Colours = nullptr; + +private: + void Create(int w, int h) + { + nWidth = w; + nHeight = h; + m_Glyphs = new wchar_t[w*h]; + m_Colours = new short[w*h]; + for (int i = 0; i < w*h; i++) + { + m_Glyphs[i] = L' '; + m_Colours[i] = FG_BLACK; + } + } + +public: + void SetGlyph(int x, int y, wchar_t c) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return; + else + m_Glyphs[y * nWidth + x] = c; + } + + void SetColour(int x, int y, short c) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return; + else + m_Colours[y * nWidth + x] = c; + } + + wchar_t GetGlyph(int x, int y) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return L' '; + else + return m_Glyphs[y * nWidth + x]; + } + + short GetColour(int x, int y) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return FG_BLACK; + else + return m_Colours[y * nWidth + x]; + } + + wchar_t SampleGlyph(float x, float y) + { + int sx = (int)(x * (float)nWidth); + int sy = (int)(y * (float)nHeight - 1.0f); + if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) + return L' '; + else + return m_Glyphs[sy * nWidth + sx]; + } + + short SampleColour(float x, float y) + { + int sx = (int)(x * (float)nWidth); + int sy = (int)(y * (float)nHeight - 1.0f); + if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) + return FG_BLACK; + else + return m_Colours[sy * nWidth + sx]; + } + + bool Save(wstring sFile) + { + FILE *f = nullptr; + _wfopen_s(&f, sFile.c_str(), L"wb"); + if (f == nullptr) + return false; + + fwrite(&nWidth, sizeof(int), 1, f); + fwrite(&nHeight, sizeof(int), 1, f); + fwrite(m_Colours, sizeof(short), nWidth * nHeight, f); + fwrite(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); + + fclose(f); + + return true; + } + + bool Load(wstring sFile) + { + delete[] m_Glyphs; + delete[] m_Colours; + nWidth = 0; + nHeight = 0; + + FILE *f = nullptr; + _wfopen_s(&f, sFile.c_str(), L"rb"); + if (f == nullptr) + return false; + + fread(&nWidth, sizeof(int), 1, f); + fread(&nHeight, sizeof(int), 1, f); + + Create(nWidth, nHeight); + + fread(m_Colours, sizeof(short), nWidth * nHeight, f); + fread(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); + + fclose(f); + return true; + } + +}; + +class olcConsoleGameEngineGL +{ + uint32_t m_ColourPalette[16] = // 0xAABBGGRR + { + 0xFF000000, // BLACK + 0xFF800000, // DARK_BLUE + 0xFF008000, // DARK_GREEN + 0xFF808000, // DARK_CYAN + 0xFF000080, // DARK_RED + 0xFF800080, // DARK_MAGENTA + 0xFF008080, // DARK_YELLOW + 0xFFC0C0C0, // GREY + 0xFF808080, // DARK_GREY + 0xFFFF0000, // BLUE + 0xFF00FF00, // GREEN + 0xFFFFFF00, // CYAN + 0xFF0000FF, // RED + 0xFFFF00FF, // MAGENTA + 0xFF00FFFF, // YELLOW + 0xFFFFFFFF // WHITE + }; + + void UpdateMousePosition(int x, int y) + { + float fx = (x - m_fDrawOffsetX) / (m_nScreenWidth * m_nFontWidth * m_fDrawScale); + float fy = (y - m_fDrawOffsetY) / (m_nScreenHeight * m_nFontHeight * m_fDrawScale); + + fx = fx < 0 ? 0 : fx > 1.0f ? 1.0f : fx; + fy = fy < 0 ? 0 : fy > 1.0f ? 1.0f : fy; + + m_mousePosX = (int)(fx * m_nScreenWidth); + m_mousePosY = (int)(fy * m_nScreenHeight); + } + + void ToggleFullscreen(HWND hWnd) + { + static WINDOWPLACEMENT prev = { sizeof(WINDOWPLACEMENT) }; + + DWORD style = GetWindowLong(hWnd, GWL_STYLE); + + if (style & WS_OVERLAPPEDWINDOW) + { + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + if (GetWindowPlacement(hWnd, &prev)) + { + SetWindowLong(hWnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(hWnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(hWnd, &prev); + SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + + void WindowResize(void) + { + DWORD style = GetWindowLong(m_hWnd, GWL_STYLE); + DWORD stylex = GetWindowLong(m_hWnd, GWL_EXSTYLE); + + RECT rWndRect = { 0, 0, m_nWindowWidth, m_nWindowHeight }; + + if (style & WS_OVERLAPPEDWINDOW) + { + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + } + else + { + ToggleFullscreen(m_hWnd); + + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + + ToggleFullscreen(m_hWnd); + } + + } + + void WindowUpdateScale(void) + { + int width = m_nWindowWidth; + int height = m_nWindowHeight; + + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 100); + glMatrixMode(GL_MODELVIEW); + + float scaleX = (float)width / (float)(m_nScreenWidth * m_nFontWidth); + float scaleY = (float)height / (float)(m_nScreenHeight * m_nFontHeight); + + if (scaleX < scaleY) + { + m_fDrawScale = scaleX; + m_fDrawOffsetX = 0; + m_fDrawOffsetY = ((float)height - (float)(m_nScreenHeight * m_nFontHeight * scaleX)) * 0.5f; + } + else + { + m_fDrawScale = scaleY; + m_fDrawOffsetX = ((float)width - (float)(m_nScreenWidth * m_nFontWidth * scaleY)) * 0.5f; + m_fDrawOffsetY = 0; + } + } + + int SetPixelFormatGL(void) + { + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = ChoosePixelFormat(m_hDevCtx, &pfd); + if (!pf) return 0; + + return SetPixelFormat(m_hDevCtx, pf, &pfd); + } + + static + LRESULT CALLBACK olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) + { + static olcConsoleGameEngineGL *cge; + + + switch (uMsg) + { + case WM_CREATE: + cge = static_cast(((LPCREATESTRUCT)lParam)->lpCreateParams); + + cge->m_hDevCtx = GetDC(hWnd); + if (!cge->SetPixelFormatGL()) return -1; + + cge->m_hRenCtx = wglCreateContext(cge->m_hDevCtx); + if (!cge->m_hRenCtx) return -1; + + wglMakeCurrent(cge->m_hDevCtx, cge->m_hRenCtx); + ShowWindow(cge->m_hConsole, SW_HIDE); + return 0; + + case WM_SYSCHAR: + //ding ding ding + return 0; + + case WM_MOUSEMOVE: + cge->UpdateMousePosition(LOWORD(lParam), HIWORD(lParam)); + return 0; + + case WM_SIZE: + cge->m_nWindowWidth = LOWORD(lParam); + cge->m_nWindowHeight = HIWORD(lParam); + cge->m_bDoWindowUpdate = true; + return 0; + + case WM_SETFOCUS: + cge->m_bConsoleInFocus = true; + return 0; + + case WM_KILLFOCUS: + cge->m_bConsoleInFocus = false; + return 0; + + case WM_CLOSE: + m_bAtomActive = false; + return 0; + + case WM_DESTROY: + ShowWindow(cge->m_hConsole, SW_SHOW); + PostQuitMessage(0); + return 0; + + case 0x8000: + cge->ToggleFullscreen(hWnd); + return 0; + + case 0x8001: + cge->WindowResize(); + return 0; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); + } + + HWND ConstructWindow(int width, int height) + { + wchar_t wnd_title[] = L"OneLoneCoder.com - Console Game Engine (OGL)"; + wchar_t wnd_class[] = L"OLC_CONSOLE_GAME_ENGINE_CLASS"; + + HINSTANCE hInstance = GetModuleHandle(NULL); + + WNDCLASS wc = + { + CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + olcWndProc, + 0, + 0, + hInstance, + LoadIcon(NULL, IDI_APPLICATION), + LoadCursor(NULL, IDC_ARROW), + NULL, + NULL, + wnd_class + }; + + RegisterClass(&wc); + + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + DWORD dwStyle = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE; + RECT rWndRect = { 0, 0, width, height }; + + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + width = rWndRect.right - rWndRect.left; + height = rWndRect.bottom - rWndRect.top; + + return CreateWindowEx(dwExStyle, wnd_class, wnd_title, dwStyle, + CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, this); + } + +public: + olcConsoleGameEngineGL() + { + m_nScreenWidth = 80; + m_nScreenHeight = 30; + + memset(m_keyNewState, 0, 256 * sizeof(short)); + memset(m_keyOldState, 0, 256 * sizeof(short)); + memset(m_keys, 0, 256 * sizeof(sKeyState)); + m_mousePosX = 0; + m_mousePosY = 0; + + m_bEnableSound = false; + + m_sAppName = L"Default"; + + //grab 1 GB or memory + m_bufMemory = (uint8_t*)VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!m_bufMemory) throw exception("No Memory!"); + + m_bufScreen = (CHAR_INFO*)&m_bufMemory[0]; + m_bufScreen_old = (CHAR_INFO*)&m_bufMemory[21474304]; + + m_fVertexArray = (float*)&m_bufMemory[42948608]; + m_fTexCoordArray = (float*)&m_bufMemory[300640256]; + m_uIndicesArray = (uint32_t*)&m_bufMemory[558331904]; + + m_uForegroundColorArray = (uint32_t*)&m_bufMemory[816023552]; + m_uBackgroundColorArray = (uint32_t*)&m_bufMemory[944869376]; + + m_hConsole = GetConsoleWindow(); + } + + void EnableSound() + { + m_bEnableSound = true; + } + + int ConstructConsole(int width, int height, int fontw, int fonth) + { + m_nScreenWidth = width; + m_nScreenHeight = height; + + m_nFontWidth = fontw; + m_nFontHeight = fonth; + + int newWndWidth = width * fontw; + int newWndHeight = height * fonth; + + if (m_hWnd && ((m_nWindowWidth != newWndWidth) || (m_nWindowHeight != newWndHeight))) + { + SendMessage(m_hWnd, 0x8001, 0, 0); + } + + m_nWindowWidth = newWndWidth; + m_nWindowHeight = newWndHeight; + + // Allocate memory for screen buffer + size_t bufLen = m_nScreenWidth * m_nScreenHeight; + + if (bufLen > 0x51EB00) + { + MessageBoxA(NULL, "Not enough memory!", "ERROR!", MB_OK); + ExitProcess(0xDEADC0DE); + } + + memset(m_bufMemory, 0, bufLen * 200); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + float x1 = (float)(x); + float y1 = (float)(y); + float x2 = (float)(x + 1); + float y2 = (float)(y + 1); + + pos *= 12; + + m_fVertexArray[pos + 0] = x1; + m_fVertexArray[pos + 1] = y1; + m_fVertexArray[pos + 2] = x2; + m_fVertexArray[pos + 3] = y1; + m_fVertexArray[pos + 4] = x1; + m_fVertexArray[pos + 5] = y2; + m_fVertexArray[pos + 6] = x2; + m_fVertexArray[pos + 7] = y1; + m_fVertexArray[pos + 8] = x1; + m_fVertexArray[pos + 9] = y2; + m_fVertexArray[pos + 10] = x2; + m_fVertexArray[pos + 11] = y2; + + m_uIndicesArray[pos + 0] = pos + 0; + m_uIndicesArray[pos + 1] = pos + 1; + m_uIndicesArray[pos + 2] = pos + 2; + m_uIndicesArray[pos + 3] = pos + 3; + m_uIndicesArray[pos + 4] = pos + 4; + m_uIndicesArray[pos + 5] = pos + 5; + m_uIndicesArray[pos + 6] = pos + 6; + m_uIndicesArray[pos + 7] = pos + 7; + m_uIndicesArray[pos + 8] = pos + 8; + m_uIndicesArray[pos + 9] = pos + 9; + m_uIndicesArray[pos + 10] = pos + 10; + m_uIndicesArray[pos + 11] = pos + 11; + } + + return 1; + } + + virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F) + { + if (x >= 0 && x < m_nScreenWidth && y >= 0 && y < m_nScreenHeight) + { + m_bufScreen[y * m_nScreenWidth + x].Char.UnicodeChar = c; + m_bufScreen[y * m_nScreenWidth + x].Attributes = col; + } + } + + void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F) + { + Clip(x1, y1); + Clip(x2, y2); + for (int x = x1; x < x2; x++) + for (int y = y1; y < y2; y++) + Draw(x, y, c, col); + } + + void DrawString(int x, int y, wstring c, short col = 0x000F) + { + for (size_t i = 0; i < c.size(); i++) + { + m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; + m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; + } + } + + void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F) + { + for (size_t i = 0; i < c.size(); i++) + { + if (c[i] != L' ') + { + m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; + m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; + } + } + } + + void Clip(int &x, int &y) + { + if (x < 0) x = 0; + if (x >= m_nScreenWidth) x = m_nScreenWidth; + if (y < 0) y = 0; + if (y >= m_nScreenHeight) y = m_nScreenHeight; + } + + void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F) + { + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; + dy = y2 - y1; + dx1 = abs(dx); + dy1 = abs(dy); + px = 2 * dy1 - dx1; + py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; + y = y1; + xe = x2; + } + else + { + x = x2; + y = y2; + xe = x1; + } + Draw(x, y, c, col); + for (i = 0; x0 && dy>0)) + y = y + 1; + else + y = y - 1; + px = px + 2 * (dy1 - dx1); + } + Draw(x, y, c, col); + } + } + else + { + if (dy >= 0) + { + x = x1; + y = y1; + ye = y2; + } + else + { + x = x2; + y = y2; + ye = y1; + } + Draw(x, y, c, col); + for (i = 0; y0 && dy>0)) + x = x + 1; + else + x = x - 1; + py = py + 2 * (dx1 - dy1); + } + Draw(x, y, c, col); + } + } + } + + void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F) + { + int x = 0; + int y = r; + int p = 3 - 2 * r; + if (!r) return; + + while (y >= x) // only formulate 1/8 of circle + { + Draw(xc - x, yc - y, c, col);//upper left left + Draw(xc - y, yc - x, c, col);//upper upper left + Draw(xc + y, yc - x, c, col);//upper upper right + Draw(xc + x, yc - y, c, col);//upper right right + Draw(xc - x, yc + y, c, col);//lower left left + Draw(xc - y, yc + x, c, col);//lower lower left + Draw(xc + y, yc + x, c, col);//lower lower right + Draw(xc + x, yc + y, c, col);//lower right right + if (p < 0) p += 4 * x++ + 6; + else p += 4 * (x++ - y--) + 10; + } + } + + void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F) + { + DrawLine(x1, y1, x2, y2, c, col); + DrawLine(x2, y2, x3, y3, c, col); + DrawLine(x3, y3, x1, y1, c, col); + } + + // https://www.avrfreaks.net/sites/default/files/triangles.c + void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F) + { + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, c, col); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxxy3) return; + } + } + + void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F) + { + // Taken from wikipedia + int x = 0; + int y = r; + int p = 3 - 2 * r; + if (!r) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, c, col); + }; + + while (y >= x) + { + // Modified to draw scan-lines instead of edges + drawline(xc - x, xc + x, yc - y); + drawline(xc - y, xc + y, yc - x); + drawline(xc - x, xc + x, yc + y); + drawline(xc - y, xc + y, yc + x); + if (p < 0) p += 4 * x++ + 6; + else p += 4 * (x++ - y--) + 10; + } + }; + + void DrawSprite(int x, int y, olcSprite *sprite) + { + if (sprite == nullptr) + return; + + for (int i = 0; i < sprite->nWidth; i++) + { + for (int j = 0; j < sprite->nHeight; j++) + { + if (sprite->GetGlyph(i, j) != L' ') + Draw(x + i, y + j, sprite->GetGlyph(i, j), sprite->GetColour(i, j)); + } + } + } + + void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h) + { + if (sprite == nullptr) + return; + + for (int i = 0; i < w; i++) + { + for (int j = 0; j < h; j++) + { + if (sprite->GetGlyph(i + ox, j + oy) != L' ') + Draw(x + i, y + j, sprite->GetGlyph(i + ox, j + oy), sprite->GetColour(i + ox, j + oy)); + } + } + } + + void DrawWireFrameModel(const std::vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, short c = PIXEL_SOLID) + { + // pair.first = x coordinate + // pair.second = y coordinate + + // Create translated model vector of coordinate pairs + vector> vecTransformedCoordinates; + int verts = vecModelCoordinates.size(); + vecTransformedCoordinates.resize(verts); + + // Rotate + for (int i = 0; i < verts; i++) + { + vecTransformedCoordinates[i].first = vecModelCoordinates[i].first * cosf(r) - vecModelCoordinates[i].second * sinf(r); + vecTransformedCoordinates[i].second = vecModelCoordinates[i].first * sinf(r) + vecModelCoordinates[i].second * cosf(r); + } + + // Scale + for (int i = 0; i < verts; i++) + { + vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first * s; + vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second * s; + } + + // Translate + for (int i = 0; i < verts; i++) + { + vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first + x; + vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second + y; + } + + // Draw Closed Polygon + for (int i = 0; i < verts + 1; i++) + { + int j = (i + 1); + DrawLine((int)vecTransformedCoordinates[i % verts].first, (int)vecTransformedCoordinates[i % verts].second, + (int)vecTransformedCoordinates[j % verts].first, (int)vecTransformedCoordinates[j % verts].second, c, col); + } + } + + ~olcConsoleGameEngineGL() + { + if (m_bufMemory) VirtualFree(m_bufMemory, 0, MEM_RELEASE); + + m_bufMemory = nullptr; + + m_bufScreen = nullptr; + m_bufScreen_old = nullptr; + + m_fVertexArray = nullptr; + m_fTexCoordArray = nullptr; + m_uIndicesArray = nullptr; + + m_uForegroundColorArray = nullptr; + m_uBackgroundColorArray = nullptr; + } + + void GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size) + { + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + uint8_t *p0 = &tex_old[(y << 1) * (size << 1) + (x << 1)]; + uint8_t *p1 = &p0[(size << 1)]; + + tex_new[y * size + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + int char_size = size >> 5; + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) * char_size; + int posx = (i & 0x1F) * char_size; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + alpha += tex_new[(posy + y) * size + (posx + x)]; + } + + alpha /= char_size * char_size; + + float factor = (float)ref_alpha[i] / (float)alpha; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + int value = (int)((float)(tex_new[(posy + y) * size + (posx + x)]) * factor); + tex_new[(posy + y) * size + (posx + x)] = value > 255 ? 255 : value; + } + } + + } + +public: + void Start() + { + m_bAtomActive = true; + + m_hWnd = ConstructWindow(m_nWindowWidth, m_nWindowHeight); + if (!m_hWnd) + { + Error(L"Could not create GL window"); + return; + } + + glGenTextures(1, &m_uFontTexture); + glBindTexture(GL_TEXTURE_2D, m_uFontTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + + //mipmap generation + { + font_decode_custom_base64(); // fill pxplus_ibm_cga + + glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY, 256, 256, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pxplus_ibm_cga); + + uint8_t *glyph_alpha = new uint8_t[1024]; // 32 * 32 + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) << 3; + int posx = (i & 0x1F) << 3; + + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + { + alpha += pxplus_ibm_cga[(posy + y) * 256 + (posx + x)]; + } + + glyph_alpha[i] = (uint8_t)(alpha >> 6); + } + + int texsize = 128; + uint8_t *texbuf = new uint8_t[texsize * texsize]; + + GenerateMipmapPow2(texbuf, pxplus_ibm_cga, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 1, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 64; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 2, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 32; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 3, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 16; + for (int level = 4; level < 9; level++) + { + for (int y = 0; y < texsize; y++) + for (int x = 0; x < texsize; x++) + { + uint8_t *p0 = &texbuf[(y << 1) * (texsize << 1) + (x << 1)]; + uint8_t *p1 = &p0[texsize << 1]; + + texbuf[y * texsize + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + texsize >>= 1; + } + + texsize = 1; + for (int level = 9; level < 1000; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + } + + delete[] texbuf; + delete[] glyph_alpha; + } + + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + wglMakeCurrent(NULL, NULL); + + // Star the thread + thread t = thread(&olcConsoleGameEngineGL::GameThread, this); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Wait for thread to be exited + t.join(); + } + + int ScreenWidth() + { + return m_nScreenWidth; + } + + int ScreenHeight() + { + return m_nScreenHeight; + } + +private: + void GameThread() + { + wglMakeCurrent(m_hDevCtx, m_hRenCtx); + + // Create user resources as part of this thread + if (!OnUserCreate()) + m_bAtomActive = false; + + // Check if sound system should be enabled + if (m_bEnableSound) + { + if (!CreateAudio()) + { + m_bAtomActive = false; // Failed to create audio system + m_bEnableSound = false; + } + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, 0, m_fVertexArray); + glTexCoordPointer(2, GL_FLOAT, 0, m_fTexCoordArray); + + LARGE_INTEGER timeFreq, timeNew, timeOld; + QueryPerformanceFrequency(&timeFreq); + QueryPerformanceCounter(&timeOld); + QueryPerformanceCounter(&timeNew); + + int nFrameCounter = 0; + float fFrameTimeAccum = 0; + + while (m_bAtomActive) + { + // Run as fast as possible + while (m_bAtomActive) + { + QueryPerformanceCounter(&timeNew); + float fElapsedTime = (float)((timeNew.QuadPart - timeOld.QuadPart) / (double)timeFreq.QuadPart); + timeOld = timeNew; + + for (int i = 0; i < 256; i++) + { + m_keyNewState[i] = GetAsyncKeyState(i) >> 15; + + m_keys[i].bPressed = false; + m_keys[i].bReleased = false; + + if (m_keyNewState[i] != m_keyOldState[i]) + { + if (m_keyNewState[i]) + { + m_keys[i].bPressed = true; + m_keys[i].bHeld = true; + } + else + { + m_keys[i].bReleased = true; + m_keys[i].bHeld = false; + } + } + + m_keyOldState[i] = m_keyNewState[i]; + } + + m_mouse[0x00] = m_keys[VK_LBUTTON]; + m_mouse[0x01] = m_keys[VK_RBUTTON]; + m_mouse[0x02] = m_keys[VK_MBUTTON]; + m_mouse[0x03] = m_keys[0x05]; // VK_XBUTTON1 + m_mouse[0x04] = m_keys[0x06]; // VK_XBUTTON2 + + if (m_keys[VK_MENU].bHeld && m_keys[VK_RETURN].bPressed) + { + SendMessage(m_hWnd, 0x8000, 0, 0); + } + + if (m_bDoWindowUpdate) + { + WindowUpdateScale(); + m_bDoWindowUpdate = false; + } + + glClear(GL_COLOR_BUFFER_BIT); + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + { + m_bAtomActive = false; + break; + } + + // draw the things + glPushMatrix(); + glTranslatef(m_fDrawOffsetX, m_fDrawOffsetY, 0.0f); + glScalef(m_fDrawScale * m_nFontWidth, m_fDrawScale * m_nFontHeight, 1.0f); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + if ((m_bufScreen[pos].Char.UnicodeChar == m_bufScreen_old[pos].Char.UnicodeChar) && + (m_bufScreen[pos].Attributes == m_bufScreen_old[pos].Attributes)) + continue; + + m_bufScreen_old[pos] = m_bufScreen[pos]; + + WCHAR id = m_bufScreen[pos].Char.UnicodeChar; + WORD col = m_bufScreen[pos].Attributes; + + int u, v; + float u1, v1, u2, v2; + uint32_t fg, bg; + + if (id == L' ') + { + u1 = u2 = v1 = v2 = 0.0f; + fg = bg = 0; + } + else + { + GetFontCoords(id, &u, &v); + + u1 = (u) / 256.0f; + v1 = (v) / 256.0f; + u2 = (u + 8) / 256.0f; + v2 = (v + 8) / 256.0f; + + fg = m_ColourPalette[col & 0xF]; + bg = m_ColourPalette[(col >> 4) & 0xF]; + } + + pos *= 6; + + m_uForegroundColorArray[pos + 0] = fg; + m_uForegroundColorArray[pos + 1] = fg; + m_uForegroundColorArray[pos + 2] = fg; + m_uForegroundColorArray[pos + 3] = fg; + m_uForegroundColorArray[pos + 4] = fg; + m_uForegroundColorArray[pos + 5] = fg; + + m_uBackgroundColorArray[pos + 0] = bg; + m_uBackgroundColorArray[pos + 1] = bg; + m_uBackgroundColorArray[pos + 2] = bg; + m_uBackgroundColorArray[pos + 3] = bg; + m_uBackgroundColorArray[pos + 4] = bg; + m_uBackgroundColorArray[pos + 5] = bg; + + pos *= 2; + + m_fTexCoordArray[pos + 0] = u1; + m_fTexCoordArray[pos + 1] = v1; + m_fTexCoordArray[pos + 2] = u2; + m_fTexCoordArray[pos + 3] = v1; + m_fTexCoordArray[pos + 4] = u1; + m_fTexCoordArray[pos + 5] = v2; + m_fTexCoordArray[pos + 6] = u2; + m_fTexCoordArray[pos + 7] = v1; + m_fTexCoordArray[pos + 8] = u1; + m_fTexCoordArray[pos + 9] = v2; + m_fTexCoordArray[pos + 10] = u2; + m_fTexCoordArray[pos + 11] = v2; + } + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uBackgroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + //glDrawElements(GL_TRIANGLES, m_nScreenWidth * m_nScreenHeight * 6, GL_UNSIGNED_INT, m_uIndicesArray); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uForegroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + glPopMatrix(); + + // Update Title & Present Screen Buffer + wchar_t sNewTitle[256]; + swprintf_s(sNewTitle, 256, L"OneLoneCoder.com - Console Game Engine (OGL) - %s - FPS: %3.2f", m_sAppName.c_str(), 1.0f / fElapsedTime); + SetWindowText(m_hWnd, sNewTitle); + + SwapBuffers(m_hDevCtx); + } + + if (m_bEnableSound) + { + // Close and Clean up audio system + } + + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + m_cvGameFinished.notify_one(); + } + else + { + // User denied destroy for some reason, so continue running + m_bAtomActive = true; + } + } + + PostMessage(m_hWnd, WM_DESTROY, 0, 0); + } + +public: + // User MUST OVERRIDE THESE!! + virtual bool OnUserCreate() = 0; + virtual bool OnUserUpdate(float fElapsedTime) = 0; + + // Optional for clean up + virtual bool OnUserDestroy() + { + return true; + } + + +protected: + + + struct sKeyState + { + bool bPressed; + bool bReleased; + bool bHeld; + } m_keys[256], m_mouse[5]; + + int m_mousePosX; + int m_mousePosY; + +public: + sKeyState GetKey(int nKeyID) { return m_keys[nKeyID]; } + int GetMouseX() { return m_mousePosX; } + int GetMouseY() { return m_mousePosY; } + sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; } + bool IsFocused() { return m_bConsoleInFocus; } + + +protected: + int Error(const wchar_t *msg) + { + wchar_t buff1[256]; + wchar_t buff2[256]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, buff1, 256, NULL); + wsprintf(buff2, L"%s\n\n%s", msg, buff1); + MessageBox(NULL, buff2, L"ERROR", MB_ICONERROR | MB_OK); + return 0; + } + + protected: // Audio Engine ===================================================================== + + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::wstring sWavFile) + { + // Load Wav file and convert to float format + FILE *f = nullptr; + _wfopen_s(&f, sWavFile.c_str(), L"rb"); + if (f == nullptr) + return; + + char dump[4]; + std::fread(&dump, sizeof(char), 4, f); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return; + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&dump, sizeof(char), 4, f); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return; + + // Read Wave description chunk + std::fread(&dump, sizeof(char), 4, f); // Read "fmt " + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcCGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + { + std::fclose(f); + return; + } + + // Search for audio data chunk + long nChunksize = 0; + std::fread(&dump, sizeof(char), 4, f); // Read chunk header + std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + std::fseek(f, nChunksize, SEEK_CUR); + std::fread(&dump, sizeof(char), 4, f); + std::fread(&nChunksize, sizeof(long), 1, f); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + std::fread(&s, sizeof(short), 1, f); + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + + // All done, flag sound as valid + std::fclose(f); + bSampleValid = true; + } + + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::wstring sWavFile) + { + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; + } + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false) + { + listActiveSamples.push_back({ id, 0, false, bLoop }); + } + + void StopSample(int id) + { + + } + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512) + { + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&olcConsoleGameEngineGL::AudioThread, this); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; + } + + // Stop and clean up audio system + bool DestroyAudio() + { + m_bAudioThreadActive = false; + return false; + } + + // Handler for soundcard request for more data + void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) + { + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + } + + // Static wrapper for sound card handler + static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) + { + ((olcConsoleGameEngineGL*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); + } + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread() + { + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } + } + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) + { + return 0.0f; + } + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) + { + return fSample; + } + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in whcih case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) + { + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); + } + + unsigned int m_nSampleRate; + unsigned int m_nChannels; + unsigned int m_nBlockCount; + unsigned int m_nBlockSamples; + unsigned int m_nBlockCurrent; + + short* m_pBlockMemory = nullptr; + WAVEHDR *m_pWaveHeaders = nullptr; + HWAVEOUT m_hwDevice = nullptr; + + std::thread m_AudioThread; + std::atomic m_bAudioThreadActive = false; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + std::atomic m_fGlobalTime = 0.0f; + bool m_bEnableSound = false; + +protected: + int m_nScreenWidth; + int m_nScreenHeight; + int m_nWindowWidth; + int m_nWindowHeight; + int m_nFontWidth; + int m_nFontHeight; + float m_fDrawScale; + float m_fDrawOffsetX; + float m_fDrawOffsetY; + float *m_fVertexArray; + uint32_t *m_uIndicesArray; + uint32_t *m_uForegroundColorArray; + uint32_t *m_uBackgroundColorArray; + float *m_fTexCoordArray; + CHAR_INFO *m_bufScreen; + CHAR_INFO *m_bufScreen_old; + uint8_t *m_bufMemory; + wstring m_sAppName; + SMALL_RECT m_rectWindow; + short m_keyOldState[256] = { 0 }; + short m_keyNewState[256] = { 0 }; + bool m_mouseOldState[5] = { 0 }; + bool m_mouseNewState[5] = { 0 }; + bool m_bConsoleInFocus = true; + bool m_bDoWindowUpdate = false; + HWND m_hConsole = nullptr; + HWND m_hWnd = nullptr; + HDC m_hDevCtx = nullptr; + HGLRC m_hRenCtx = nullptr; + GLuint m_uFontTexture; + static atomic m_bAtomActive; + static condition_variable m_cvGameFinished; + static mutex m_muxGame; +}; + +atomic olcConsoleGameEngineGL::m_bAtomActive(false); +condition_variable olcConsoleGameEngineGL::m_cvGameFinished; +mutex olcConsoleGameEngineGL::m_muxGame; \ No newline at end of file diff --git a/VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.cpp b/VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.cpp new file mode 100644 index 0000000..891a5a7 --- /dev/null +++ b/VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.cpp @@ -0,0 +1,1626 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 +License +~~~~~~~ +One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions; See license for details. +Original works located at: +https://www.github.com/onelonecoder +https://www.onelonecoder.com +https://www.youtube.com/javidx9 +GNU GPLv3 +https://github.com/OneLoneCoder/videos/blob/master/LICENSE +From Javidx9 :) +~~~~~~~~~~~~~~~ +Hello! Ultimately I don't care what you use this for. It's intended to be +educational, and perhaps to the oddly minded - a little bit of fun. +Please hack this, change it and use it in any way you see fit. You acknowledge +that I am not responsible for anything bad that happens as a result of +your actions. However this code is protected by GNU GPLv3, see the license in the +github repo. This means you must attribute me if you use it. You can view this +license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE +Cheers! +Background +~~~~~~~~~~ +If you've seen any of my videos - I like to do things using the windows console. It's quick +and easy, and allows you to focus on just the code that matters - ideal when you're +experimenting. Thing is, I have to keep doing the same initialisation and display code +each time, so this class wraps that up. +Author +~~~~~~ +Twitter: @javidx9 http://twitter.com/javidx9 +Blog: http://www.onelonecoder.com +YouTube: http://www.youtube.com/javidx9 +Videos: +~~~~~~ +Original: https://youtu.be/cWc0hgYwZyc +Added mouse support: https://youtu.be/tdqc9hZhHxM +Beginners Guide: https://youtu.be/u5BhrA8ED0o +Shout Outs! +~~~~~~~~~~~ +Thanks to cool people who helped with testing, bug-finding and fixing! +wowLinh, JavaJack59, idkwid, kingtatgi, Return Null, CPP Guy +Last Updated: 18/03/2018 +Usage: +~~~~~~ +This class is abstract, so you must inherit from it. Override the OnUserCreate() function +with all the stuff you need for your application (for thready reasons it's best to do +this in this function and not your class constructor). Override the OnUserUpdate(float fElapsedTime) +function with the good stuff, it gives you the elapsed time since the last call so you +can modify your stuff dynamically. Both functions should return true, unless you need +the application to close. +int main() +{ +// Use olcConsoleGameEngine derived app +OneLoneCoder_Example game; +// Create a console with resolution 160x100 characters +// Each character occupies 8x8 pixels +game.ConstructConsole(160, 100, 8, 8); +// Start the engine! +game.Start(); +return 0; +} +Input is also handled for you - interrogate the m_keys[] array with the virtual +keycode you want to know about. bPressed is set for the frame the key is pressed down +in, bHeld is set if the key is held down, bReleased is set for the frame the key +is released in. The same applies to mouse! m_mousePosX and Y can be used to get +the current cursor position, and m_mouse[1..5] returns the mouse buttons. +The draw routines treat characters like pixels. By default they are set to white solid +blocks - but you can draw any unicode character, using any of the colours listed below. +There may be bugs! +See my other videos for examples! +http://www.youtube.com/javidx9 +Lots of programs to try: +http://www.github.com/OneLoneCoder/videos +Chat on the Discord server: +https://discord.gg/WhwHUMV +Be bored by Twitch: +http://www.twitch.tv/javidx9 +*/ + +#include "olcConsoleGameEngineGLOOP.h" + +wglSwapInterval_t *wglSwapInterval; + +// Based on the PxPlus IBM CGA Font from +// "The Ultimate Oldschool PC Font Pack" http://int10h.org/oldschool-pc-fonts/ +// Rendered into an image (256x56) and converted to char array +// +// The Ultimate Oldschool PC Font Pack is licensed under a Creative Commons +// Attribution-ShareAlike 4.0 International License. +// +// You should have received a copy of the license along with this work. If +// not, see < http://creativecommons.org/licenses/by-sa/4.0/ >. +// +// (c) 2016 VileR +// +// (256x256x8) +unsigned char pxplus_ibm_cga[65536]; +char pxplus_ibm_cga_enc[8509 + 1] = +"0mWOkP0780et0q?qqedtH40@HHVtq106HP10000000001stOLm320q??33cIa?sqlOq3?0000000000200000000mPf1c1030H00e0P=0H000H03000000000e0000000003Hee>8PQ76m1cbS?s0H30sHk0hqS1hH63em13t=hNmV7?0hINhO?00HmlIsq36L0@Fsf<<0D50000000P?H3060mhNqmQ=kehHW=hsf16q=k?e30VQfmQ30003>mQ3S=6<0HSIWI036eP16Hk0he03dnf0m1>m0000mP30000>qP3eQhH0Hh<6<60t1eHhe60q10Ler30kHSlme17N0036e000L60Lmf0W100tmS70eV=0000V107L<30kHh<0eV3qLk7drfI00eKOe1" +"0L00706mS?0HP?m<3m00000000e17NmW" +"7snQONmW7snQ?SIS7emfOtql?trh300e3>0000000000000000000e17000P700000000000L0q3?q07LeQS70q320q32KmhH0e033l30=jF6Hm760m1Os?W?6m33smV7k00000P=0000<@00VQ13aQ1?KeQ7eH3mfH34kPfhakH<Omf<30S=hee6hqW7e00008002000000060ef1kq1LqmQ7hPl7Kq167ef<pLkIWnh?hqh??qlO[nfHSMkHS1e0" +"000000000000000000000e000000S10000000000hef?teh?hPeHhNqhm0pLkI0000?000Oe10P100P10000000000e0000000N0000000<00H0mlHOefOtm0?t<3?trh0ta7>e13Le130HS3HP176H03tPfk0a=00006600000e000000000000" +"00e03e1000000q77O00?>CeqOh103?qe3?qe7h" +"qe??qf?t<0H00000006IhL[sk0eHS=VISI6Kc=kHSfkHaf0?000000P5d:>kt1P1Htr3006H000HP160P10aQ1KaQ1Ka10" +"000000P1KaQ1KaQ1KaQ160006HP10H00KHe6KHe6K00000000He6KHe6KHe6KHP100P16H0060e66af66af6000000006af66af66af66af66afkt00et?0?00D5000" +"0000@0004200ett3POsQ1KaQ1Kar?00ip3e30AZ2000000HLetef1?qqte04mtthkpthkpt300tt0qT8BGqm30000?Oq?OkHVOWW97Uc=0a106a1et00er?0?00X>?k" +"eS?0mlOsrlH2nlerJCPttttttttd300tts3e300GfS=qHSOtt1?qIS@s=Ldkipe0000He6K00ettt0qT@TZqm3?smlt7P1LLHVOWW97IN>00006af60ttOd?0?0Ri50" +"0000s1@00042qeht33@Pt3000P1Ka1etttt3e300Z2000000000000000qtt0mWOHe3q8eQ=8edt0h7O33000000000000000000000000ekV1>2sq77> 0) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 1) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 2) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 3) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 4) & 1) * 0xFF; + pxplus_ibm_cga[o++] = ((c >> 5) & 1) * 0xFF; + } +} + +#define FUV(a,b,c) case a: *x=b; *y=c; break; + +void GetFontCoords(int id, int *x, int *y) { + switch (id) { + FUV(0, 0, 0)FUV(1, 8, 0)FUV(2, 16, 0)FUV(3, 24, 0)FUV(4, 32, 0)FUV(5, 40, 0)FUV(6, 48, 0) + FUV(7, 56, 0)FUV(8, 64, 0)FUV(9, 72, 0)FUV(10, 80, 0)FUV(11, 88, 0)FUV(12, 96, 0)FUV(13, 104, 0) + FUV(14, 112, 0)FUV(15, 120, 0)FUV(16, 128, 0)FUV(17, 136, 0)FUV(18, 144, 0)FUV(19, 152, 0)FUV(20, 160, 0) + FUV(21, 168, 0)FUV(22, 176, 0)FUV(23, 184, 0)FUV(24, 192, 0)FUV(25, 200, 0)FUV(26, 208, 0)FUV(27, 216, 0) + FUV(28, 224, 0)FUV(29, 232, 0)FUV(30, 240, 0)FUV(31, 248, 0)FUV(32, 0, 8)FUV(33, 8, 8)FUV(34, 16, 8) + FUV(35, 24, 8)FUV(36, 32, 8)FUV(37, 40, 8)FUV(38, 48, 8)FUV(39, 56, 8)FUV(40, 64, 8)FUV(41, 72, 8) + FUV(42, 80, 8)FUV(43, 88, 8)FUV(44, 96, 8)FUV(45, 104, 8)FUV(46, 112, 8)FUV(47, 120, 8)FUV(48, 128, 8) + FUV(49, 136, 8)FUV(50, 144, 8)FUV(51, 152, 8)FUV(52, 160, 8)FUV(53, 168, 8)FUV(54, 176, 8)FUV(55, 184, 8) + FUV(56, 192, 8)FUV(57, 200, 8)FUV(58, 208, 8)FUV(59, 216, 8)FUV(60, 224, 8)FUV(61, 232, 8)FUV(62, 240, 8) + FUV(63, 248, 8)FUV(64, 0, 16)FUV(65, 8, 16)FUV(66, 16, 16)FUV(67, 24, 16)FUV(68, 32, 16)FUV(69, 40, 16) + FUV(70, 48, 16)FUV(71, 56, 16)FUV(72, 64, 16)FUV(73, 72, 16)FUV(74, 80, 16)FUV(75, 88, 16)FUV(76, 96, 16) + FUV(77, 104, 16)FUV(78, 112, 16)FUV(79, 120, 16)FUV(80, 128, 16)FUV(81, 136, 16)FUV(82, 144, 16)FUV(83, 152, 16) + FUV(84, 160, 16)FUV(85, 168, 16)FUV(86, 176, 16)FUV(87, 184, 16)FUV(88, 192, 16)FUV(89, 200, 16)FUV(90, 208, 16) + FUV(91, 216, 16)FUV(92, 224, 16)FUV(93, 232, 16)FUV(94, 240, 16)FUV(95, 248, 16)FUV(96, 0, 24)FUV(97, 8, 24) + FUV(98, 16, 24)FUV(99, 24, 24)FUV(100, 32, 24)FUV(101, 40, 24)FUV(102, 48, 24)FUV(103, 56, 24)FUV(104, 64, 24) + FUV(105, 72, 24)FUV(106, 80, 24)FUV(107, 88, 24)FUV(108, 96, 24)FUV(109, 104, 24)FUV(110, 112, 24)FUV(111, 120, 24) + FUV(112, 128, 24)FUV(113, 136, 24)FUV(114, 144, 24)FUV(115, 152, 24)FUV(116, 160, 24)FUV(117, 168, 24)FUV(118, 176, 24) + FUV(119, 184, 24)FUV(120, 192, 24)FUV(121, 200, 24)FUV(122, 208, 24)FUV(123, 216, 24)FUV(124, 224, 24)FUV(125, 232, 24) + FUV(126, 240, 24)FUV(127, 248, 24)FUV(160, 0, 32)FUV(161, 8, 32)FUV(162, 16, 32)FUV(163, 24, 32)FUV(164, 32, 32) + FUV(165, 40, 32)FUV(166, 48, 32)FUV(167, 56, 32)FUV(168, 64, 32)FUV(169, 72, 32)FUV(170, 80, 32)FUV(171, 88, 32) + FUV(172, 96, 32)FUV(173, 104, 32)FUV(174, 112, 32)FUV(175, 120, 32)FUV(176, 128, 32)FUV(177, 136, 32)FUV(178, 144, 32) + FUV(179, 152, 32)FUV(180, 160, 32)FUV(181, 168, 32)FUV(182, 176, 32)FUV(183, 184, 32)FUV(184, 192, 32)FUV(185, 200, 32) + FUV(186, 208, 32)FUV(187, 216, 32)FUV(188, 224, 32)FUV(189, 232, 32)FUV(190, 240, 32)FUV(191, 248, 32)FUV(192, 0, 40) + FUV(193, 8, 40)FUV(194, 16, 40)FUV(195, 24, 40)FUV(196, 32, 40)FUV(197, 40, 40)FUV(198, 48, 40)FUV(199, 56, 40) + FUV(200, 64, 40)FUV(201, 72, 40)FUV(202, 80, 40)FUV(203, 88, 40)FUV(204, 96, 40)FUV(205, 104, 40)FUV(206, 112, 40) + FUV(207, 120, 40)FUV(208, 128, 40)FUV(209, 136, 40)FUV(210, 144, 40)FUV(211, 152, 40)FUV(212, 160, 40)FUV(213, 168, 40) + FUV(214, 176, 40)FUV(215, 184, 40)FUV(216, 192, 40)FUV(217, 200, 40)FUV(218, 208, 40)FUV(219, 216, 40)FUV(220, 224, 40) + FUV(221, 232, 40)FUV(222, 240, 40)FUV(223, 248, 40)FUV(224, 0, 48)FUV(225, 8, 48)FUV(226, 16, 48)FUV(227, 24, 48) + FUV(228, 32, 48)FUV(229, 40, 48)FUV(230, 48, 48)FUV(231, 56, 48)FUV(232, 64, 48)FUV(233, 72, 48)FUV(234, 80, 48) + FUV(235, 88, 48)FUV(236, 96, 48)FUV(237, 104, 48)FUV(238, 112, 48)FUV(239, 120, 48)FUV(240, 128, 48)FUV(241, 136, 48) + FUV(242, 144, 48)FUV(243, 152, 48)FUV(244, 160, 48)FUV(245, 168, 48)FUV(246, 176, 48)FUV(247, 184, 48)FUV(248, 192, 48) + FUV(249, 200, 48)FUV(250, 208, 48)FUV(251, 216, 48)FUV(252, 224, 48)FUV(253, 232, 48)FUV(254, 240, 48)FUV(255, 248, 48) + FUV(256, 0, 56)FUV(257, 8, 56)FUV(258, 16, 56)FUV(259, 24, 56)FUV(260, 32, 56)FUV(261, 40, 56)FUV(262, 48, 56) + FUV(263, 56, 56)FUV(264, 64, 56)FUV(265, 72, 56)FUV(266, 80, 56)FUV(267, 88, 56)FUV(268, 96, 56)FUV(269, 104, 56) + FUV(270, 112, 56)FUV(271, 120, 56)FUV(272, 128, 56)FUV(273, 136, 56)FUV(274, 144, 56)FUV(275, 152, 56)FUV(276, 160, 56) + FUV(277, 168, 56)FUV(278, 176, 56)FUV(279, 184, 56)FUV(280, 192, 56)FUV(281, 200, 56)FUV(282, 208, 56)FUV(283, 216, 56) + FUV(284, 224, 56)FUV(285, 232, 56)FUV(286, 240, 56)FUV(287, 248, 56)FUV(288, 0, 64)FUV(289, 8, 64)FUV(290, 16, 64) + FUV(291, 24, 64)FUV(292, 32, 64)FUV(293, 40, 64)FUV(294, 48, 64)FUV(295, 56, 64)FUV(296, 64, 64)FUV(297, 72, 64) + FUV(298, 80, 64)FUV(299, 88, 64)FUV(300, 96, 64)FUV(301, 104, 64)FUV(302, 112, 64)FUV(303, 120, 64)FUV(304, 128, 64) + FUV(305, 136, 64)FUV(306, 144, 64)FUV(307, 152, 64)FUV(308, 160, 64)FUV(309, 168, 64)FUV(310, 176, 64)FUV(311, 184, 64) + FUV(312, 192, 64)FUV(313, 200, 64)FUV(314, 208, 64)FUV(315, 216, 64)FUV(316, 224, 64)FUV(317, 232, 64)FUV(318, 240, 64) + FUV(319, 248, 64)FUV(320, 0, 72)FUV(321, 8, 72)FUV(322, 16, 72)FUV(323, 24, 72)FUV(324, 32, 72)FUV(325, 40, 72) + FUV(326, 48, 72)FUV(327, 56, 72)FUV(328, 64, 72)FUV(329, 72, 72)FUV(330, 80, 72)FUV(331, 88, 72)FUV(332, 96, 72) + FUV(333, 104, 72)FUV(334, 112, 72)FUV(335, 120, 72)FUV(336, 128, 72)FUV(337, 136, 72)FUV(338, 144, 72)FUV(339, 152, 72) + FUV(340, 160, 72)FUV(341, 168, 72)FUV(342, 176, 72)FUV(343, 184, 72)FUV(344, 192, 72)FUV(345, 200, 72)FUV(346, 208, 72) + FUV(347, 216, 72)FUV(348, 224, 72)FUV(349, 232, 72)FUV(350, 240, 72)FUV(351, 248, 72)FUV(352, 0, 80)FUV(353, 8, 80) + FUV(354, 16, 80)FUV(355, 24, 80)FUV(356, 32, 80)FUV(357, 40, 80)FUV(358, 48, 80)FUV(359, 56, 80)FUV(360, 64, 80) + FUV(361, 72, 80)FUV(362, 80, 80)FUV(363, 88, 80)FUV(364, 96, 80)FUV(365, 104, 80)FUV(366, 112, 80)FUV(367, 120, 80) + FUV(368, 128, 80)FUV(369, 136, 80)FUV(370, 144, 80)FUV(371, 152, 80)FUV(372, 160, 80)FUV(373, 168, 80)FUV(374, 176, 80) + FUV(375, 184, 80)FUV(376, 192, 80)FUV(377, 200, 80)FUV(378, 208, 80)FUV(379, 216, 80)FUV(380, 224, 80)FUV(381, 232, 80) + FUV(382, 240, 80)FUV(383, 248, 80)FUV(402, 0, 88)FUV(417, 8, 88)FUV(439, 16, 88)FUV(506, 24, 88)FUV(507, 32, 88) + FUV(508, 40, 88)FUV(509, 48, 88)FUV(510, 56, 88)FUV(511, 64, 88)FUV(536, 72, 88)FUV(537, 80, 88)FUV(538, 88, 88) + FUV(539, 96, 88)FUV(593, 104, 88)FUV(632, 112, 88)FUV(710, 120, 88)FUV(711, 128, 88)FUV(713, 136, 88)FUV(728, 144, 88) + FUV(729, 152, 88)FUV(730, 160, 88)FUV(731, 168, 88)FUV(732, 176, 88)FUV(733, 184, 88)FUV(894, 192, 88)FUV(900, 200, 88) + FUV(901, 208, 88)FUV(902, 216, 88)FUV(903, 224, 88)FUV(904, 232, 88)FUV(905, 240, 88)FUV(906, 248, 88)FUV(908, 0, 96) + FUV(910, 8, 96)FUV(911, 16, 96)FUV(912, 24, 96)FUV(913, 32, 96)FUV(914, 40, 96)FUV(915, 48, 96)FUV(916, 56, 96) + FUV(917, 64, 96)FUV(918, 72, 96)FUV(919, 80, 96)FUV(920, 88, 96)FUV(921, 96, 96)FUV(922, 104, 96)FUV(923, 112, 96) + FUV(924, 120, 96)FUV(925, 128, 96)FUV(926, 136, 96)FUV(927, 144, 96)FUV(928, 152, 96)FUV(929, 160, 96)FUV(931, 168, 96) + FUV(932, 176, 96)FUV(933, 184, 96)FUV(934, 192, 96)FUV(935, 200, 96)FUV(936, 208, 96)FUV(937, 216, 96)FUV(938, 224, 96) + FUV(939, 232, 96)FUV(940, 240, 96)FUV(941, 248, 96)FUV(942, 0, 104)FUV(943, 8, 104)FUV(944, 16, 104)FUV(945, 24, 104) + FUV(946, 32, 104)FUV(947, 40, 104)FUV(948, 48, 104)FUV(949, 56, 104)FUV(950, 64, 104)FUV(951, 72, 104)FUV(952, 80, 104) + FUV(953, 88, 104)FUV(954, 96, 104)FUV(955, 104, 104)FUV(956, 112, 104)FUV(957, 120, 104)FUV(958, 128, 104)FUV(959, 136, 104) + FUV(960, 144, 104)FUV(961, 152, 104)FUV(962, 160, 104)FUV(963, 168, 104)FUV(964, 176, 104)FUV(965, 184, 104)FUV(966, 192, 104) + FUV(967, 200, 104)FUV(968, 208, 104)FUV(969, 216, 104)FUV(970, 224, 104)FUV(971, 232, 104)FUV(972, 240, 104)FUV(973, 248, 104) + FUV(974, 0, 112)FUV(976, 8, 112)FUV(1012, 16, 112)FUV(1024, 24, 112)FUV(1025, 32, 112)FUV(1026, 40, 112)FUV(1027, 48, 112) + FUV(1028, 56, 112)FUV(1029, 64, 112)FUV(1030, 72, 112)FUV(1031, 80, 112)FUV(1032, 88, 112)FUV(1033, 96, 112)FUV(1034, 104, 112) + FUV(1035, 112, 112)FUV(1036, 120, 112)FUV(1037, 128, 112)FUV(1038, 136, 112)FUV(1039, 144, 112)FUV(1040, 152, 112)FUV(1041, 160, 112) + FUV(1042, 168, 112)FUV(1043, 176, 112)FUV(1044, 184, 112)FUV(1045, 192, 112)FUV(1046, 200, 112)FUV(1047, 208, 112)FUV(1048, 216, 112) + FUV(1049, 224, 112)FUV(1050, 232, 112)FUV(1051, 240, 112)FUV(1052, 248, 112)FUV(1053, 0, 120)FUV(1054, 8, 120)FUV(1055, 16, 120) + FUV(1056, 24, 120)FUV(1057, 32, 120)FUV(1058, 40, 120)FUV(1059, 48, 120)FUV(1060, 56, 120)FUV(1061, 64, 120)FUV(1062, 72, 120) + FUV(1063, 80, 120)FUV(1064, 88, 120)FUV(1065, 96, 120)FUV(1066, 104, 120)FUV(1067, 112, 120)FUV(1068, 120, 120)FUV(1069, 128, 120) + FUV(1070, 136, 120)FUV(1071, 144, 120)FUV(1072, 152, 120)FUV(1073, 160, 120)FUV(1074, 168, 120)FUV(1075, 176, 120)FUV(1076, 184, 120) + FUV(1077, 192, 120)FUV(1078, 200, 120)FUV(1079, 208, 120)FUV(1080, 216, 120)FUV(1081, 224, 120)FUV(1082, 232, 120)FUV(1083, 240, 120) + FUV(1084, 248, 120)FUV(1085, 0, 128)FUV(1086, 8, 128)FUV(1087, 16, 128)FUV(1088, 24, 128)FUV(1089, 32, 128)FUV(1090, 40, 128) + FUV(1091, 48, 128)FUV(1092, 56, 128)FUV(1093, 64, 128)FUV(1094, 72, 128)FUV(1095, 80, 128)FUV(1096, 88, 128)FUV(1097, 96, 128) + FUV(1098, 104, 128)FUV(1099, 112, 128)FUV(1100, 120, 128)FUV(1101, 128, 128)FUV(1102, 136, 128)FUV(1103, 144, 128)FUV(1104, 152, 128) + FUV(1105, 160, 128)FUV(1106, 168, 128)FUV(1107, 176, 128)FUV(1108, 184, 128)FUV(1109, 192, 128)FUV(1110, 200, 128)FUV(1111, 208, 128) + FUV(1112, 216, 128)FUV(1113, 224, 128)FUV(1114, 232, 128)FUV(1115, 240, 128)FUV(1116, 248, 128)FUV(1117, 0, 136)FUV(1118, 8, 136) + FUV(1119, 16, 136)FUV(1168, 24, 136)FUV(1169, 32, 136)FUV(1470, 40, 136)FUV(1488, 48, 136)FUV(1489, 56, 136)FUV(1490, 64, 136) + FUV(1491, 72, 136)FUV(1492, 80, 136)FUV(1493, 88, 136)FUV(1494, 96, 136)FUV(1495, 104, 136)FUV(1496, 112, 136)FUV(1497, 120, 136) + FUV(1498, 128, 136)FUV(1499, 136, 136)FUV(1500, 144, 136)FUV(1501, 152, 136)FUV(1502, 160, 136)FUV(1503, 168, 136)FUV(1504, 176, 136) + FUV(1505, 184, 136)FUV(1506, 192, 136)FUV(1507, 200, 136)FUV(1508, 208, 136)FUV(1509, 216, 136)FUV(1510, 224, 136)FUV(1511, 232, 136) + FUV(1512, 240, 136)FUV(1513, 248, 136)FUV(1514, 0, 144)FUV(1520, 8, 144)FUV(1521, 16, 144)FUV(1522, 24, 144)FUV(1523, 32, 144) + FUV(1524, 40, 144)FUV(7451, 48, 144)FUV(7462, 56, 144)FUV(7464, 64, 144)FUV(7808, 72, 144)FUV(7809, 80, 144)FUV(7810, 88, 144) + FUV(7811, 96, 144)FUV(7812, 104, 144)FUV(7813, 112, 144)FUV(7839, 120, 144)FUV(7922, 128, 144)FUV(7923, 136, 144)FUV(8208, 144, 144) + FUV(8210, 152, 144)FUV(8211, 160, 144)FUV(8212, 168, 144)FUV(8213, 176, 144)FUV(8215, 184, 144)FUV(8216, 192, 144)FUV(8217, 200, 144) + FUV(8218, 208, 144)FUV(8219, 216, 144)FUV(8220, 224, 144)FUV(8221, 232, 144)FUV(8222, 240, 144)FUV(8223, 248, 144)FUV(8224, 0, 152) + FUV(8225, 8, 152)FUV(8226, 16, 152)FUV(8230, 24, 152)FUV(8231, 32, 152)FUV(8240, 40, 152)FUV(8242, 48, 152)FUV(8243, 56, 152) + FUV(8245, 64, 152)FUV(8249, 72, 152)FUV(8250, 80, 152)FUV(8252, 88, 152)FUV(8254, 96, 152)FUV(8255, 104, 152)FUV(8256, 112, 152) + FUV(8260, 120, 152)FUV(8276, 128, 152)FUV(8308, 136, 152)FUV(8309, 144, 152)FUV(8310, 152, 152)FUV(8311, 160, 152)FUV(8312, 168, 152) + FUV(8313, 176, 152)FUV(8314, 184, 152)FUV(8315, 192, 152)FUV(8319, 200, 152)FUV(8321, 208, 152)FUV(8322, 216, 152)FUV(8323, 224, 152) + FUV(8324, 232, 152)FUV(8325, 240, 152)FUV(8326, 248, 152)FUV(8327, 0, 160)FUV(8328, 8, 160)FUV(8329, 16, 160)FUV(8330, 24, 160) + FUV(8331, 32, 160)FUV(8355, 40, 160)FUV(8356, 48, 160)FUV(8359, 56, 160)FUV(8362, 64, 160)FUV(8364, 72, 160)FUV(8453, 80, 160) + FUV(8467, 88, 160)FUV(8470, 96, 160)FUV(8482, 104, 160)FUV(8486, 112, 160)FUV(8494, 120, 160)FUV(8528, 128, 160)FUV(8529, 136, 160) + FUV(8531, 144, 160)FUV(8532, 152, 160)FUV(8533, 160, 160)FUV(8534, 168, 160)FUV(8535, 176, 160)FUV(8536, 184, 160)FUV(8537, 192, 160) + FUV(8538, 200, 160)FUV(8539, 208, 160)FUV(8540, 216, 160)FUV(8541, 224, 160)FUV(8542, 232, 160)FUV(8592, 240, 160)FUV(8593, 248, 160) + FUV(8594, 0, 168)FUV(8595, 8, 168)FUV(8596, 16, 168)FUV(8597, 24, 168)FUV(8616, 32, 168)FUV(8706, 40, 168)FUV(8709, 48, 168) + FUV(8710, 56, 168)FUV(8712, 64, 168)FUV(8719, 72, 168)FUV(8721, 80, 168)FUV(8722, 88, 168)FUV(8725, 96, 168)FUV(8729, 104, 168) + FUV(8730, 112, 168)FUV(8734, 120, 168)FUV(8735, 128, 168)FUV(8745, 136, 168)FUV(8747, 144, 168)FUV(8776, 152, 168)FUV(8800, 160, 168) + FUV(8801, 168, 168)FUV(8804, 176, 168)FUV(8805, 184, 168)FUV(8857, 192, 168)FUV(8960, 200, 168)FUV(8962, 208, 168)FUV(8976, 216, 168) + FUV(8992, 224, 168)FUV(8993, 232, 168)FUV(9472, 240, 168)FUV(9474, 248, 168)FUV(9484, 0, 176)FUV(9488, 8, 176)FUV(9492, 16, 176) + FUV(9496, 24, 176)FUV(9500, 32, 176)FUV(9508, 40, 176)FUV(9516, 48, 176)FUV(9524, 56, 176)FUV(9532, 64, 176)FUV(9552, 72, 176) + FUV(9553, 80, 176)FUV(9554, 88, 176)FUV(9555, 96, 176)FUV(9556, 104, 176)FUV(9557, 112, 176)FUV(9558, 120, 176)FUV(9559, 128, 176) + FUV(9560, 136, 176)FUV(9561, 144, 176)FUV(9562, 152, 176)FUV(9563, 160, 176)FUV(9564, 168, 176)FUV(9565, 176, 176)FUV(9566, 184, 176) + FUV(9567, 192, 176)FUV(9568, 200, 176)FUV(9569, 208, 176)FUV(9570, 216, 176)FUV(9571, 224, 176)FUV(9572, 232, 176)FUV(9573, 240, 176) + FUV(9574, 248, 176)FUV(9575, 0, 184)FUV(9576, 8, 184)FUV(9577, 16, 184)FUV(9578, 24, 184)FUV(9579, 32, 184)FUV(9580, 40, 184) + FUV(9600, 48, 184)FUV(9601, 56, 184)FUV(9604, 64, 184)FUV(9608, 72, 184)FUV(9612, 80, 184)FUV(9616, 88, 184)FUV(9617, 96, 184) + FUV(9618, 104, 184)FUV(9619, 112, 184)FUV(9632, 120, 184)FUV(9633, 128, 184)FUV(9642, 136, 184)FUV(9643, 144, 184)FUV(9644, 152, 184) + FUV(9650, 160, 184)FUV(9658, 168, 184)FUV(9660, 176, 184)FUV(9668, 184, 184)FUV(9674, 192, 184)FUV(9675, 200, 184)FUV(9679, 208, 184) + FUV(9688, 216, 184)FUV(9689, 224, 184)FUV(9702, 232, 184)FUV(9786, 240, 184)FUV(9787, 248, 184)FUV(9788, 0, 192)FUV(9792, 8, 192) + FUV(9794, 16, 192)FUV(9824, 24, 192)FUV(9827, 32, 192)FUV(9829, 40, 192)FUV(9830, 48, 192)FUV(9834, 56, 192)FUV(9835, 64, 192) + FUV(10003, 72, 192)FUV(64257, 80, 192)FUV(64258, 88, 192)FUV(65533, 96, 192) default: *x = 96; *y = 192; break; + } +} + + + +void olcConsoleGameEngineGLOOP::UpdateMousePosition(int x, int y) +{ + float fx = (x - m_fDrawOffsetX) / (m_nScreenWidth * m_nFontWidth * m_fDrawScale); + float fy = (y - m_fDrawOffsetY) / (m_nScreenHeight * m_nFontHeight * m_fDrawScale); + + fx = fx < 0 ? 0 : fx > 1.0f ? 1.0f : fx; + fy = fy < 0 ? 0 : fy > 1.0f ? 1.0f : fy; + + m_mousePosX = (int)(fx * m_nScreenWidth); + m_mousePosY = (int)(fy * m_nScreenHeight); +} + +void olcConsoleGameEngineGLOOP::ToggleFullscreen(HWND hWnd) +{ + static WINDOWPLACEMENT prev = { sizeof(WINDOWPLACEMENT) }; + + DWORD style = GetWindowLong(hWnd, GWL_STYLE); + + if (style & WS_OVERLAPPEDWINDOW) + { + int width = GetSystemMetrics(SM_CXSCREEN); + int height = GetSystemMetrics(SM_CYSCREEN); + + if (GetWindowPlacement(hWnd, &prev)) + { + SetWindowLong(hWnd, GWL_STYLE, style & ~WS_OVERLAPPEDWINDOW); + SetWindowPos(hWnd, HWND_TOP, 0, 0, width, height, SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } + } + else + { + SetWindowLong(hWnd, GWL_STYLE, style | WS_OVERLAPPEDWINDOW); + SetWindowPlacement(hWnd, &prev); + SetWindowPos(hWnd, NULL, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_FRAMECHANGED); + } +} + +void olcConsoleGameEngineGLOOP::WindowResize(void) +{ + DWORD style = GetWindowLong(m_hWnd, GWL_STYLE); + DWORD stylex = GetWindowLong(m_hWnd, GWL_EXSTYLE); + + RECT rWndRect = { 0, 0, m_nWindowWidth, m_nWindowHeight }; + + if (style & WS_OVERLAPPEDWINDOW) + { + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + } + else + { + ToggleFullscreen(m_hWnd); + + AdjustWindowRectEx(&rWndRect, style, FALSE, stylex); + + int width = rWndRect.right - rWndRect.left; + int height = rWndRect.bottom - rWndRect.top; + + SetWindowPos(m_hWnd, HWND_TOP, 0, 0, width, height, SWP_NOMOVE | SWP_NOOWNERZORDER); + + ToggleFullscreen(m_hWnd); + } + +} + +void olcConsoleGameEngineGLOOP::WindowUpdateScale(void) +{ + int width = m_nWindowWidth; + int height = m_nWindowHeight; + + glViewport(0, 0, width, height); + + glMatrixMode(GL_PROJECTION); + glLoadIdentity(); + glOrtho(0, width, height, 0, -1, 100); + glMatrixMode(GL_MODELVIEW); + + float scaleX = (float)width / (float)(m_nScreenWidth * m_nFontWidth); + float scaleY = (float)height / (float)(m_nScreenHeight * m_nFontHeight); + + if (scaleX < scaleY) + { + m_fDrawScale = scaleX; + m_fDrawOffsetX = 0; + m_fDrawOffsetY = ((float)height - (float)(m_nScreenHeight * m_nFontHeight * scaleX)) * 0.5f; + } + else + { + m_fDrawScale = scaleY; + m_fDrawOffsetX = ((float)width - (float)(m_nScreenWidth * m_nFontWidth * scaleY)) * 0.5f; + m_fDrawOffsetY = 0; + } +} + +int olcConsoleGameEngineGLOOP::SetPixelFormatGL(void) +{ + PIXELFORMATDESCRIPTOR pfd = + { + sizeof(PIXELFORMATDESCRIPTOR), 1, + PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER, + PFD_TYPE_RGBA, 32, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + PFD_MAIN_PLANE, 0, 0, 0, 0 + }; + + int pf = ChoosePixelFormat(m_hDevCtx, &pfd); + if (!pf) return 0; + + return SetPixelFormat(m_hDevCtx, pf, &pfd); +} + + +LRESULT CALLBACK olcConsoleGameEngineGLOOP::olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + static olcConsoleGameEngineGLOOP *cge; + + + switch (uMsg) + { + case WM_CREATE: + cge = static_cast(((LPCREATESTRUCT)lParam)->lpCreateParams); + + cge->m_hDevCtx = GetDC(hWnd); + if (!cge->SetPixelFormatGL()) return -1; + + cge->m_hRenCtx = wglCreateContext(cge->m_hDevCtx); + if (!cge->m_hRenCtx) return -1; + + wglMakeCurrent(cge->m_hDevCtx, cge->m_hRenCtx); + ShowWindow(cge->m_hConsole, SW_HIDE); + return 0; + + case WM_SYSCHAR: + //ding ding ding + return 0; + + case WM_MOUSEMOVE: + cge->UpdateMousePosition(LOWORD(lParam), HIWORD(lParam)); + return 0; + + case WM_SIZE: + cge->m_nWindowWidth = LOWORD(lParam); + cge->m_nWindowHeight = HIWORD(lParam); + cge->m_bDoWindowUpdate = true; + return 0; + + case WM_SETFOCUS: + cge->m_bConsoleInFocus = true; + return 0; + + case WM_KILLFOCUS: + cge->m_bConsoleInFocus = false; + return 0; + + case WM_CLOSE: + m_bAtomActive = false; + return 0; + + case WM_DESTROY: + ShowWindow(cge->m_hConsole, SW_SHOW); + PostQuitMessage(0); + return 0; + + case 0x8000: + cge->ToggleFullscreen(hWnd); + return 0; + + case 0x8001: + cge->WindowResize(); + return 0; + } + + return DefWindowProc(hWnd, uMsg, wParam, lParam); +} + +HWND olcConsoleGameEngineGLOOP::ConstructWindow(int width, int height) +{ + wchar_t wnd_title[] = L"OneLoneCoder.com - Console Game Engine (OGL)"; + wchar_t wnd_class[] = L"OLC_CONSOLE_GAME_ENGINE_CLASS"; + + HINSTANCE hInstance = GetModuleHandle(NULL); + + WNDCLASS wc = + { + CS_HREDRAW | CS_VREDRAW | CS_OWNDC, + olcWndProc, + 0, + 0, + hInstance, + LoadIcon(NULL, IDI_APPLICATION), + LoadCursor(NULL, IDC_ARROW), + NULL, + NULL, + wnd_class + }; + + RegisterClass(&wc); + + DWORD dwExStyle = WS_EX_APPWINDOW | WS_EX_WINDOWEDGE | WS_EX_ACCEPTFILES; + DWORD dwStyle = WS_CLIPSIBLINGS | WS_CLIPCHILDREN | WS_OVERLAPPEDWINDOW | WS_VISIBLE; + RECT rWndRect = { 0, 0, width, height }; + + AdjustWindowRectEx(&rWndRect, dwStyle, FALSE, dwExStyle); + + width = rWndRect.right - rWndRect.left; + height = rWndRect.bottom - rWndRect.top; + + return CreateWindowEx(dwExStyle, wnd_class, wnd_title, dwStyle, + CW_USEDEFAULT, CW_USEDEFAULT, width, height, NULL, NULL, hInstance, this); +} + + +olcConsoleGameEngineGLOOP::olcConsoleGameEngineGLOOP() +{ + m_nScreenWidth = 80; + m_nScreenHeight = 30; + + memset(m_keyNewState, 0, 256 * sizeof(short)); + memset(m_keyOldState, 0, 256 * sizeof(short)); + memset(m_keys, 0, 256 * sizeof(sKeyState)); + m_mousePosX = 0; + m_mousePosY = 0; + + m_bEnableSound = false; + + m_sAppName = L"Default"; + + //grab 1 GB or memory + m_bufMemory = (uint8_t*)VirtualAlloc(NULL, 1024 * 1024 * 1024, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE); + if (!m_bufMemory) throw exception("No Memory!"); + + m_bufScreen = (CHAR_INFO*)&m_bufMemory[0]; + m_bufScreen_old = (CHAR_INFO*)&m_bufMemory[21474304]; + + m_fVertexArray = (float*)&m_bufMemory[42948608]; + m_fTexCoordArray = (float*)&m_bufMemory[300640256]; + m_uIndicesArray = (uint32_t*)&m_bufMemory[558331904]; + + m_uForegroundColorArray = (uint32_t*)&m_bufMemory[816023552]; + m_uBackgroundColorArray = (uint32_t*)&m_bufMemory[944869376]; + + m_hConsole = GetConsoleWindow(); +} + +void olcConsoleGameEngineGLOOP::EnableSound() +{ + m_bEnableSound = true; +} + +int olcConsoleGameEngineGLOOP::ConstructConsole(int width, int height, int fontw, int fonth) +{ + m_nScreenWidth = width; + m_nScreenHeight = height; + + m_nFontWidth = fontw; + m_nFontHeight = fonth; + + int newWndWidth = width * fontw; + int newWndHeight = height * fonth; + + if (m_hWnd && ((m_nWindowWidth != newWndWidth) || (m_nWindowHeight != newWndHeight))) + { + SendMessage(m_hWnd, 0x8001, 0, 0); + } + + m_nWindowWidth = newWndWidth; + m_nWindowHeight = newWndHeight; + + // Allocate memory for screen buffer + size_t bufLen = m_nScreenWidth * m_nScreenHeight; + + if (bufLen > 0x51EB00) + { + MessageBoxA(NULL, "Not enough memory!", "ERROR!", MB_OK); + ExitProcess(0xDEADC0DE); + } + + memset(m_bufMemory, 0, bufLen * 200); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + float x1 = (float)(x); + float y1 = (float)(y); + float x2 = (float)(x + 1); + float y2 = (float)(y + 1); + + pos *= 12; + + m_fVertexArray[pos + 0] = x1; + m_fVertexArray[pos + 1] = y1; + m_fVertexArray[pos + 2] = x2; + m_fVertexArray[pos + 3] = y1; + m_fVertexArray[pos + 4] = x1; + m_fVertexArray[pos + 5] = y2; + m_fVertexArray[pos + 6] = x2; + m_fVertexArray[pos + 7] = y1; + m_fVertexArray[pos + 8] = x1; + m_fVertexArray[pos + 9] = y2; + m_fVertexArray[pos + 10] = x2; + m_fVertexArray[pos + 11] = y2; + + m_uIndicesArray[pos + 0] = pos + 0; + m_uIndicesArray[pos + 1] = pos + 1; + m_uIndicesArray[pos + 2] = pos + 2; + m_uIndicesArray[pos + 3] = pos + 3; + m_uIndicesArray[pos + 4] = pos + 4; + m_uIndicesArray[pos + 5] = pos + 5; + m_uIndicesArray[pos + 6] = pos + 6; + m_uIndicesArray[pos + 7] = pos + 7; + m_uIndicesArray[pos + 8] = pos + 8; + m_uIndicesArray[pos + 9] = pos + 9; + m_uIndicesArray[pos + 10] = pos + 10; + m_uIndicesArray[pos + 11] = pos + 11; + } + + return 1; +} + +void olcConsoleGameEngineGLOOP::Draw(int x, int y, wchar_t c, short col) +{ + if (x >= 0 && x < m_nScreenWidth && y >= 0 && y < m_nScreenHeight) + { + m_bufScreen[y * m_nScreenWidth + x].Char.UnicodeChar = c; + m_bufScreen[y * m_nScreenWidth + x].Attributes = col; + } +} + +void olcConsoleGameEngineGLOOP::Fill(int x1, int y1, int x2, int y2, wchar_t c, short col) +{ + Clip(x1, y1); + Clip(x2, y2); + for (int x = x1; x < x2; x++) + for (int y = y1; y < y2; y++) + Draw(x, y, c, col); +} + +void olcConsoleGameEngineGLOOP::DrawString(int x, int y, wstring c, short col) +{ + for (size_t i = 0; i < c.size(); i++) + { + m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; + m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; + } +} + +void olcConsoleGameEngineGLOOP::DrawStringAlpha(int x, int y, wstring c, short col) +{ + for (size_t i = 0; i < c.size(); i++) + { + if (c[i] != L' ') + { + m_bufScreen[y * m_nScreenWidth + x + i].Char.UnicodeChar = c[i]; + m_bufScreen[y * m_nScreenWidth + x + i].Attributes = col; + } + } +} + +void olcConsoleGameEngineGLOOP::Clip(int &x, int &y) +{ + if (x < 0) x = 0; + if (x >= m_nScreenWidth) x = m_nScreenWidth; + if (y < 0) y = 0; + if (y >= m_nScreenHeight) y = m_nScreenHeight; +} + +void olcConsoleGameEngineGLOOP::DrawLine(int x1, int y1, int x2, int y2, wchar_t c, short col) +{ + int x, y, dx, dy, dx1, dy1, px, py, xe, ye, i; + dx = x2 - x1; + dy = y2 - y1; + dx1 = abs(dx); + dy1 = abs(dy); + px = 2 * dy1 - dx1; + py = 2 * dx1 - dy1; + if (dy1 <= dx1) + { + if (dx >= 0) + { + x = x1; + y = y1; + xe = x2; + } + else + { + x = x2; + y = y2; + xe = x1; + } + Draw(x, y, c, col); + for (i = 0; x0 && dy>0)) + y = y + 1; + else + y = y - 1; + px = px + 2 * (dy1 - dx1); + } + Draw(x, y, c, col); + } + } + else + { + if (dy >= 0) + { + x = x1; + y = y1; + ye = y2; + } + else + { + x = x2; + y = y2; + ye = y1; + } + Draw(x, y, c, col); + for (i = 0; y0 && dy>0)) + x = x + 1; + else + x = x - 1; + py = py + 2 * (dx1 - dy1); + } + Draw(x, y, c, col); + } + } +} + +void olcConsoleGameEngineGLOOP::DrawCircle(int xc, int yc, int r, wchar_t c, short col) +{ + int x = 0; + int y = r; + int p = 3 - 2 * r; + if (!r) return; + + while (y >= x) // only formulate 1/8 of circle + { + Draw(xc - x, yc - y, c, col);//upper left left + Draw(xc - y, yc - x, c, col);//upper upper left + Draw(xc + y, yc - x, c, col);//upper upper right + Draw(xc + x, yc - y, c, col);//upper right right + Draw(xc - x, yc + y, c, col);//lower left left + Draw(xc - y, yc + x, c, col);//lower lower left + Draw(xc + y, yc + x, c, col);//lower lower right + Draw(xc + x, yc + y, c, col);//lower right right + if (p < 0) p += 4 * x++ + 6; + else p += 4 * (x++ - y--) + 10; + } +} + +void olcConsoleGameEngineGLOOP::DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c, short col) +{ + DrawLine(x1, y1, x2, y2, c, col); + DrawLine(x2, y2, x3, y3, c, col); + DrawLine(x3, y3, x1, y1, c, col); +} + +// https://www.avrfreaks.net/sites/default/files/triangles.c +void olcConsoleGameEngineGLOOP::FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c, short col) +{ + auto SWAP = [](int &x, int &y) { int t = x; x = y; y = t; }; + auto drawline = [&](int sx, int ex, int ny) { for (int i = sx; i <= ex; i++) Draw(i, ny, c, col); }; + + int t1x, t2x, y, minx, maxx, t1xp, t2xp; + bool changed1 = false; + bool changed2 = false; + int signx1, signx2, dx1, dy1, dx2, dy2; + int e1, e2; + // Sort vertices + if (y1>y2) { SWAP(y1, y2); SWAP(x1, x2); } + if (y1>y3) { SWAP(y1, y3); SWAP(x1, x3); } + if (y2>y3) { SWAP(y2, y3); SWAP(x2, x3); } + + t1x = t2x = x1; y = y1; // Starting points + dx1 = (int)(x2 - x1); if (dx1<0) { dx1 = -dx1; signx1 = -1; } + else signx1 = 1; + dy1 = (int)(y2 - y1); + + dx2 = (int)(x3 - x1); if (dx2<0) { dx2 = -dx2; signx2 = -1; } + else signx2 = 1; + dy2 = (int)(y3 - y1); + + if (dy1 > dx1) { // swap values + SWAP(dx1, dy1); + changed1 = true; + } + if (dy2 > dx2) { // swap values + SWAP(dy2, dx2); + changed2 = true; + } + + e2 = (int)(dx2 >> 1); + // Flat top, just process the second half + if (y1 == y2) goto next; + e1 = (int)(dx1 >> 1); + + for (int i = 0; i < dx1;) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) t1xp = signx1;//t1x += signx1; + else goto next1; + } + if (changed1) break; + else t1x += signx1; + } + // Move line + next1: + // process second line until y value is about to change + while (1) { + e2 += dy2; + while (e2 >= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2;//t2x += signx2; + else goto next2; + } + if (changed2) break; + else t2x += signx2; + } + next2: + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxx dx1) { // swap values + SWAP(dy1, dx1); + changed1 = true; + } + else changed1 = false; + + e1 = (int)(dx1 >> 1); + + for (int i = 0; i <= dx1; i++) { + t1xp = 0; t2xp = 0; + if (t1x= dx1) { + e1 -= dx1; + if (changed1) { t1xp = signx1; break; }//t1x += signx1; + else goto next3; + } + if (changed1) break; + else t1x += signx1; + if (i= dx2) { + e2 -= dx2; + if (changed2) t2xp = signx2; + else goto next4; + } + if (changed2) break; + else t2x += signx2; + } + next4: + + if (minx>t1x) minx = t1x; if (minx>t2x) minx = t2x; + if (maxxy3) return; + } +} + +void olcConsoleGameEngineGLOOP::FillCircle(int xc, int yc, int r, wchar_t c, short col) +{ + // Taken from wikipedia + int x = 0; + int y = r; + int p = 3 - 2 * r; + if (!r) return; + + auto drawline = [&](int sx, int ex, int ny) + { + for (int i = sx; i <= ex; i++) + Draw(i, ny, c, col); + }; + + while (y >= x) + { + // Modified to draw scan-lines instead of edges + drawline(xc - x, xc + x, yc - y); + drawline(xc - y, xc + y, yc - x); + drawline(xc - x, xc + x, yc + y); + drawline(xc - y, xc + y, yc + x); + if (p < 0) p += 4 * x++ + 6; + else p += 4 * (x++ - y--) + 10; + } +}; + +void olcConsoleGameEngineGLOOP::DrawSprite(int x, int y, olcSprite *sprite) +{ + if (sprite == nullptr) + return; + + for (int i = 0; i < sprite->nWidth; i++) + { + for (int j = 0; j < sprite->nHeight; j++) + { + if (sprite->GetGlyph(i, j) != L' ') + Draw(x + i, y + j, sprite->GetGlyph(i, j), sprite->GetColour(i, j)); + } + } +} + +void olcConsoleGameEngineGLOOP::DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h) +{ + if (sprite == nullptr) + return; + + for (int i = 0; i < w; i++) + { + for (int j = 0; j < h; j++) + { + if (sprite->GetGlyph(i + ox, j + oy) != L' ') + Draw(x + i, y + j, sprite->GetGlyph(i + ox, j + oy), sprite->GetColour(i + ox, j + oy)); + } + } +} + +void olcConsoleGameEngineGLOOP::DrawWireFrameModel(const std::vector> &vecModelCoordinates, float x, float y, float r, float s, short col, short c) +{ + // pair.first = x coordinate + // pair.second = y coordinate + + // Create translated model vector of coordinate pairs + vector> vecTransformedCoordinates; + int verts = vecModelCoordinates.size(); + vecTransformedCoordinates.resize(verts); + + // Rotate + for (int i = 0; i < verts; i++) + { + vecTransformedCoordinates[i].first = vecModelCoordinates[i].first * cosf(r) - vecModelCoordinates[i].second * sinf(r); + vecTransformedCoordinates[i].second = vecModelCoordinates[i].first * sinf(r) + vecModelCoordinates[i].second * cosf(r); + } + + // Scale + for (int i = 0; i < verts; i++) + { + vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first * s; + vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second * s; + } + + // Translate + for (int i = 0; i < verts; i++) + { + vecTransformedCoordinates[i].first = vecTransformedCoordinates[i].first + x; + vecTransformedCoordinates[i].second = vecTransformedCoordinates[i].second + y; + } + + // Draw Closed Polygon + for (int i = 0; i < verts + 1; i++) + { + int j = (i + 1); + DrawLine((int)vecTransformedCoordinates[i % verts].first, (int)vecTransformedCoordinates[i % verts].second, + (int)vecTransformedCoordinates[j % verts].first, (int)vecTransformedCoordinates[j % verts].second, c, col); + } +} + +olcConsoleGameEngineGLOOP::~olcConsoleGameEngineGLOOP() +{ + if (m_bufMemory) VirtualFree(m_bufMemory, 0, MEM_RELEASE); + + m_bufMemory = nullptr; + + m_bufScreen = nullptr; + m_bufScreen_old = nullptr; + + m_fVertexArray = nullptr; + m_fTexCoordArray = nullptr; + m_uIndicesArray = nullptr; + + m_uForegroundColorArray = nullptr; + m_uBackgroundColorArray = nullptr; +} + +void olcConsoleGameEngineGLOOP::GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size) +{ + for (int y = 0; y < size; y++) + for (int x = 0; x < size; x++) + { + uint8_t *p0 = &tex_old[(y << 1) * (size << 1) + (x << 1)]; + uint8_t *p1 = &p0[(size << 1)]; + + tex_new[y * size + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + int char_size = size >> 5; + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) * char_size; + int posx = (i & 0x1F) * char_size; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + alpha += tex_new[(posy + y) * size + (posx + x)]; + } + + alpha /= char_size * char_size; + + float factor = (float)ref_alpha[i] / (float)alpha; + + for (int y = 0; y < char_size; y++) + for (int x = 0; x < char_size; x++) + { + int value = (int)((float)(tex_new[(posy + y) * size + (posx + x)]) * factor); + tex_new[(posy + y) * size + (posx + x)] = value > 255 ? 255 : value; + } + } + +} + + +void olcConsoleGameEngineGLOOP::Start() +{ + m_bAtomActive = true; + + m_hWnd = ConstructWindow(m_nWindowWidth, m_nWindowHeight); + if (!m_hWnd) + { + Error(L"Could not create GL window"); + return; + } + + glGenTextures(1, &m_uFontTexture); + glBindTexture(GL_TEXTURE_2D, m_uFontTexture); + + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST_MIPMAP_LINEAR); + + //mipmap generation + { + font_decode_custom_base64(); // fill pxplus_ibm_cga + + glTexImage2D(GL_TEXTURE_2D, 0, GL_INTENSITY, 256, 256, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, pxplus_ibm_cga); + + uint8_t *glyph_alpha = new uint8_t[1024]; // 32 * 32 + + for (int i = 0; i < 1024; i++) + { + int alpha = 0; + + int posy = (i >> 5) << 3; + int posx = (i & 0x1F) << 3; + + for (int y = 0; y < 8; y++) + for (int x = 0; x < 8; x++) + { + alpha += pxplus_ibm_cga[(posy + y) * 256 + (posx + x)]; + } + + glyph_alpha[i] = (uint8_t)(alpha >> 6); + } + + int texsize = 128; + uint8_t *texbuf = new uint8_t[texsize * texsize]; + + GenerateMipmapPow2(texbuf, pxplus_ibm_cga, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 1, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 64; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 2, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 32; + GenerateMipmapPow2(texbuf, texbuf, glyph_alpha, texsize); + glTexImage2D(GL_TEXTURE_2D, 3, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + + texsize = 16; + for (int level = 4; level < 9; level++) + { + for (int y = 0; y < texsize; y++) + for (int x = 0; x < texsize; x++) + { + uint8_t *p0 = &texbuf[(y << 1) * (texsize << 1) + (x << 1)]; + uint8_t *p1 = &p0[texsize << 1]; + + texbuf[y * texsize + x] = (p0[0] + p0[1] + p1[0] + p1[1]) >> 2; + } + + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + texsize >>= 1; + } + + texsize = 1; + for (int level = 9; level < 1000; level++) + { + glTexImage2D(GL_TEXTURE_2D, level, GL_INTENSITY, texsize, texsize, 0, GL_LUMINANCE, GL_UNSIGNED_BYTE, texbuf); + } + + delete[] texbuf; + delete[] glyph_alpha; + } + + wglSwapInterval = (wglSwapInterval_t*)wglGetProcAddress("wglSwapIntervalEXT"); + wglSwapInterval(0); + + glClearColor(0.1f, 0.1f, 0.1f, 0.0f); + glBlendFunc(GL_ONE, GL_ONE_MINUS_SRC_ALPHA); + wglMakeCurrent(NULL, NULL); + + // Star the thread + thread t = thread(&olcConsoleGameEngineGLOOP::GameThread, this); + + MSG msg; + while (GetMessage(&msg, NULL, 0, 0) > 0) + { + TranslateMessage(&msg); + DispatchMessage(&msg); + } + + // Wait for thread to be exited + t.join(); +} + +void olcConsoleGameEngineGLOOP::GameThread() +{ + wglMakeCurrent(m_hDevCtx, m_hRenCtx); + + // Create user resources as part of this thread + if (!OnUserCreate()) + m_bAtomActive = false; + + // Check if sound system should be enabled + if (m_bEnableSound) + { + if (!CreateAudio()) + { + m_bAtomActive = false; // Failed to create audio system + m_bEnableSound = false; + } + } + + glEnableClientState(GL_VERTEX_ARRAY); + glEnableClientState(GL_COLOR_ARRAY); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + + glVertexPointer(2, GL_FLOAT, 0, m_fVertexArray); + glTexCoordPointer(2, GL_FLOAT, 0, m_fTexCoordArray); + + LARGE_INTEGER timeFreq, timeNew, timeOld; + QueryPerformanceFrequency(&timeFreq); + QueryPerformanceCounter(&timeOld); + QueryPerformanceCounter(&timeNew); + + int nFrameCounter = 0; + float fFrameTimeAccum = 0; + + while (m_bAtomActive) + { + // Run as fast as possible + while (m_bAtomActive) + { + QueryPerformanceCounter(&timeNew); + float fElapsedTime = (float)((timeNew.QuadPart - timeOld.QuadPart) / (double)timeFreq.QuadPart); + timeOld = timeNew; + + for (int i = 0; i < 256; i++) + { + m_keyNewState[i] = GetAsyncKeyState(i) >> 15; + + m_keys[i].bPressed = false; + m_keys[i].bReleased = false; + + if (m_keyNewState[i] != m_keyOldState[i]) + { + if (m_keyNewState[i]) + { + m_keys[i].bPressed = true; + m_keys[i].bHeld = true; + } + else + { + m_keys[i].bReleased = true; + m_keys[i].bHeld = false; + } + } + + m_keyOldState[i] = m_keyNewState[i]; + } + + m_mouse[0x00] = m_keys[VK_LBUTTON]; + m_mouse[0x01] = m_keys[VK_RBUTTON]; + m_mouse[0x02] = m_keys[VK_MBUTTON]; + m_mouse[0x03] = m_keys[0x05]; // VK_XBUTTON1 + m_mouse[0x04] = m_keys[0x06]; // VK_XBUTTON2 + + if (m_keys[VK_MENU].bHeld && m_keys[VK_RETURN].bPressed) + { + SendMessage(m_hWnd, 0x8000, 0, 0); + } + + if (m_bDoWindowUpdate) + { + WindowUpdateScale(); + m_bDoWindowUpdate = false; + } + + glClear(GL_COLOR_BUFFER_BIT); + + // Handle Frame Update + if (!OnUserUpdate(fElapsedTime)) + { + m_bAtomActive = false; + break; + } + + // draw the things + glPushMatrix(); + glTranslatef(m_fDrawOffsetX, m_fDrawOffsetY, 0.0f); + glScalef(m_fDrawScale * m_nFontWidth, m_fDrawScale * m_nFontHeight, 1.0f); + + for (int y = 0; y < m_nScreenHeight; y++) + for (int x = 0; x < m_nScreenWidth; x++) + { + int pos = y * m_nScreenWidth + x; + + if ((m_bufScreen[pos].Char.UnicodeChar == m_bufScreen_old[pos].Char.UnicodeChar) && + (m_bufScreen[pos].Attributes == m_bufScreen_old[pos].Attributes)) + continue; + + m_bufScreen_old[pos] = m_bufScreen[pos]; + + WCHAR id = m_bufScreen[pos].Char.UnicodeChar; + WORD col = m_bufScreen[pos].Attributes; + + int u, v; + float u1, v1, u2, v2; + uint32_t fg, bg; + + if (id == L' ') + { + u1 = u2 = v1 = v2 = 0.0f; + fg = bg = 0; + } + else + { + GetFontCoords(id, &u, &v); + + u1 = (u) / 256.0f; + v1 = (v) / 256.0f; + u2 = (u + 8) / 256.0f; + v2 = (v + 8) / 256.0f; + + fg = m_ColourPalette[col & 0xF]; + bg = m_ColourPalette[(col >> 4) & 0xF]; + } + + pos *= 6; + + m_uForegroundColorArray[pos + 0] = fg; + m_uForegroundColorArray[pos + 1] = fg; + m_uForegroundColorArray[pos + 2] = fg; + m_uForegroundColorArray[pos + 3] = fg; + m_uForegroundColorArray[pos + 4] = fg; + m_uForegroundColorArray[pos + 5] = fg; + + m_uBackgroundColorArray[pos + 0] = bg; + m_uBackgroundColorArray[pos + 1] = bg; + m_uBackgroundColorArray[pos + 2] = bg; + m_uBackgroundColorArray[pos + 3] = bg; + m_uBackgroundColorArray[pos + 4] = bg; + m_uBackgroundColorArray[pos + 5] = bg; + + pos *= 2; + + m_fTexCoordArray[pos + 0] = u1; + m_fTexCoordArray[pos + 1] = v1; + m_fTexCoordArray[pos + 2] = u2; + m_fTexCoordArray[pos + 3] = v1; + m_fTexCoordArray[pos + 4] = u1; + m_fTexCoordArray[pos + 5] = v2; + m_fTexCoordArray[pos + 6] = u2; + m_fTexCoordArray[pos + 7] = v1; + m_fTexCoordArray[pos + 8] = u1; + m_fTexCoordArray[pos + 9] = v2; + m_fTexCoordArray[pos + 10] = u2; + m_fTexCoordArray[pos + 11] = v2; + } + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uBackgroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + //glDrawElements(GL_TRIANGLES, m_nScreenWidth * m_nScreenHeight * 6, GL_UNSIGNED_INT, m_uIndicesArray); + + glEnable(GL_TEXTURE_2D); + glEnable(GL_BLEND); + + glColorPointer(4, GL_UNSIGNED_BYTE, 0, m_uForegroundColorArray); + glDrawArrays(GL_TRIANGLES, 0, m_nScreenWidth * m_nScreenHeight * 6); + + glDisable(GL_BLEND); + glDisable(GL_TEXTURE_2D); + + glPopMatrix(); + + // Update Title & Present Screen Buffer + wchar_t sNewTitle[256]; + swprintf_s(sNewTitle, 256, L"OneLoneCoder.com - Console Game Engine (OGL) - %s - FPS: %3.2f", m_sAppName.c_str(), 1.0f / fElapsedTime); + SetWindowText(m_hWnd, sNewTitle); + + SwapBuffers(m_hDevCtx); + } + + if (m_bEnableSound) + { + // Close and Clean up audio system + } + + if (OnUserDestroy()) + { + // User has permitted destroy, so exit and clean up + m_cvGameFinished.notify_one(); + } + else + { + // User denied destroy for some reason, so continue running + m_bAtomActive = true; + } + } + + PostMessage(m_hWnd, WM_DESTROY, 0, 0); +} + + + +int olcConsoleGameEngineGLOOP::Error(const wchar_t *msg) +{ + wchar_t buff1[256]; + wchar_t buff2[256]; + FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, NULL, GetLastError(), 0, buff1, 256, NULL); + wsprintf(buff2, L"%s\n\n%s", msg, buff1); + MessageBox(NULL, buff2, L"ERROR", MB_ICONERROR | MB_OK); + return 0; +} + + + +// Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID +// number is returned if successful, otherwise -1 +unsigned int olcConsoleGameEngineGLOOP::LoadAudioSample(std::wstring sWavFile) +{ + if (!m_bEnableSound) + return -1; + + olcAudioSample a(sWavFile); + if (a.bSampleValid) + { + vecAudioSamples.push_back(a); + return vecAudioSamples.size(); + } + else + return -1; +} + +// Add sample 'id' to the mixers sounds to play list +void olcConsoleGameEngineGLOOP::PlaySample(int id, bool bLoop) +{ + listActiveSamples.push_back({ id, 0, false, bLoop }); +} + +void olcConsoleGameEngineGLOOP::StopSample(int id) +{ + +} + +// The audio system uses by default a specific wave format +bool olcConsoleGameEngineGLOOP::CreateAudio(unsigned int nSampleRate, unsigned int nChannels, + unsigned int nBlocks, unsigned int nBlockSamples) +{ + // Initialise Sound Engine + m_bAudioThreadActive = false; + m_nSampleRate = nSampleRate; + m_nChannels = nChannels; + m_nBlockCount = nBlocks; + m_nBlockSamples = nBlockSamples; + m_nBlockFree = m_nBlockCount; + m_nBlockCurrent = 0; + m_pBlockMemory = nullptr; + m_pWaveHeaders = nullptr; + + // Device is available + WAVEFORMATEX waveFormat; + waveFormat.wFormatTag = WAVE_FORMAT_PCM; + waveFormat.nSamplesPerSec = m_nSampleRate; + waveFormat.wBitsPerSample = sizeof(short) * 8; + waveFormat.nChannels = m_nChannels; + waveFormat.nBlockAlign = (waveFormat.wBitsPerSample / 8) * waveFormat.nChannels; + waveFormat.nAvgBytesPerSec = waveFormat.nSamplesPerSec * waveFormat.nBlockAlign; + waveFormat.cbSize = 0; + + // Open Device if valid + if (waveOutOpen(&m_hwDevice, WAVE_MAPPER, &waveFormat, (DWORD_PTR)waveOutProcWrap, (DWORD_PTR)this, CALLBACK_FUNCTION) != S_OK) + return DestroyAudio(); + + // Allocate Wave|Block Memory + m_pBlockMemory = new short[m_nBlockCount * m_nBlockSamples]; + if (m_pBlockMemory == nullptr) + return DestroyAudio(); + ZeroMemory(m_pBlockMemory, sizeof(short) * m_nBlockCount * m_nBlockSamples); + + m_pWaveHeaders = new WAVEHDR[m_nBlockCount]; + if (m_pWaveHeaders == nullptr) + return DestroyAudio(); + ZeroMemory(m_pWaveHeaders, sizeof(WAVEHDR) * m_nBlockCount); + + // Link headers to block memory + for (unsigned int n = 0; n < m_nBlockCount; n++) + { + m_pWaveHeaders[n].dwBufferLength = m_nBlockSamples * sizeof(short); + m_pWaveHeaders[n].lpData = (LPSTR)(m_pBlockMemory + (n * m_nBlockSamples)); + } + + m_bAudioThreadActive = true; + m_AudioThread = std::thread(&olcConsoleGameEngineGLOOP::AudioThread, this); + + // Start the ball rolling with the sound delivery thread + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); + return true; +} + +// Stop and clean up audio system +bool olcConsoleGameEngineGLOOP::DestroyAudio() +{ + m_bAudioThreadActive = false; + return false; +} + +// Handler for soundcard request for more data +void olcConsoleGameEngineGLOOP::waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2) +{ + if (uMsg != WOM_DONE) return; + m_nBlockFree++; + std::unique_lock lm(m_muxBlockNotZero); + m_cvBlockNotZero.notify_one(); +} + +// Static wrapper for sound card handler +void CALLBACK olcConsoleGameEngineGLOOP::waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2) +{ + ((olcConsoleGameEngineGLOOP*)dwInstance)->waveOutProc(hWaveOut, uMsg, dwParam1, dwParam2); +} + +// Audio thread. This loop responds to requests from the soundcard to fill 'blocks' +// with audio data. If no requests are available it goes dormant until the sound +// card is ready for more data. The block is fille by the "user" in some manner +// and then issued to the soundcard. +void olcConsoleGameEngineGLOOP::AudioThread() +{ + m_fGlobalTime = 0.0f; + float fTimeStep = 1.0f / (float)m_nSampleRate; + + // Goofy hack to get maximum integer for a type at run-time + short nMaxSample = (short)pow(2, (sizeof(short) * 8) - 1) - 1; + float fMaxSample = (float)nMaxSample; + short nPreviousSample = 0; + + while (m_bAudioThreadActive) + { + // Wait for block to become available + if (m_nBlockFree == 0) + { + std::unique_lock lm(m_muxBlockNotZero); + while (m_nBlockFree == 0) // sometimes, Windows signals incorrectly + m_cvBlockNotZero.wait(lm); + } + + // Block is here, so use it + m_nBlockFree--; + + // Prepare block for processing + if (m_pWaveHeaders[m_nBlockCurrent].dwFlags & WHDR_PREPARED) + waveOutUnprepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + + short nNewSample = 0; + int nCurrentBlock = m_nBlockCurrent * m_nBlockSamples; + + auto clip = [](float fSample, float fMax) + { + if (fSample >= 0.0) + return fmin(fSample, fMax); + else + return fmax(fSample, -fMax); + }; + + for (unsigned int n = 0; n < m_nBlockSamples; n += m_nChannels) + { + // User Process + for (unsigned int c = 0; c < m_nChannels; c++) + { + nNewSample = (short)(clip(GetMixerOutput(c, m_fGlobalTime, fTimeStep), 1.0) * fMaxSample); + m_pBlockMemory[nCurrentBlock + n + c] = nNewSample; + nPreviousSample = nNewSample; + } + + m_fGlobalTime = m_fGlobalTime + fTimeStep; + } + + // Send block to sound device + waveOutPrepareHeader(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + waveOutWrite(m_hwDevice, &m_pWaveHeaders[m_nBlockCurrent], sizeof(WAVEHDR)); + m_nBlockCurrent++; + m_nBlockCurrent %= m_nBlockCount; + } +} + +// Overridden by user if they want to generate sound in real-time +float olcConsoleGameEngineGLOOP::onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep) +{ + return 0.0f; +} + +// Overriden by user if they want to manipulate the sound before it is played +float olcConsoleGameEngineGLOOP::onUserSoundFilter(int nChannel, float fGlobalTime, float fSample) +{ + return fSample; +} + +// The Sound Mixer - If the user wants to play many sounds simultaneously, and +// perhaps the same sound overlapping itself, then you need a mixer, which +// takes input from all sound sources for that audio frame. This mixer maintains +// a list of sound locations for all concurrently playing audio samples. Instead +// of duplicating audio data, we simply store the fact that a sound sample is in +// use and an offset into its sample data. As time progresses we update this offset +// until it is beyound the length of the sound sample it is attached to. At this +// point we remove the playing souind from the list. +// +// Additionally, the users application may want to generate sound instead of just +// playing audio clips (think a synthesizer for example) in whcih case we also +// provide an "onUser..." event to allow the user to return a sound for that point +// in time. +// +// Finally, before the sound is issued to the operating system for performing, the +// user gets one final chance to "filter" the sound, perhaps changing the volume +// or adding funky effects +float olcConsoleGameEngineGLOOP::GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep) +{ + // Accumulate sample for this channel + float fMixerSample = 0.0f; + + for (auto &s : listActiveSamples) + { + // Calculate sample position + s.nSamplePosition += (long)((float)vecAudioSamples[s.nAudioSampleID - 1].wavHeader.nSamplesPerSec * fTimeStep); + + // If sample position is valid add to the mix + if (s.nSamplePosition < vecAudioSamples[s.nAudioSampleID - 1].nSamples) + fMixerSample += vecAudioSamples[s.nAudioSampleID - 1].fSample[(s.nSamplePosition * vecAudioSamples[s.nAudioSampleID - 1].nChannels) + nChannel]; + else + s.bFinished = true; // Else sound has completed + } + + // If sounds have completed then remove them + listActiveSamples.remove_if([](const sCurrentlyPlayingSample &s) {return s.bFinished; }); + + // The users application might be generating sound, so grab that if it exists + fMixerSample += onUserSoundSample(nChannel, fGlobalTime, fTimeStep); + + // Return the sample via an optional user override to filter the sound + return onUserSoundFilter(nChannel, fGlobalTime, fMixerSample); +} + + +atomic olcConsoleGameEngineGLOOP::m_bAtomActive(false); +condition_variable olcConsoleGameEngineGLOOP::m_cvGameFinished; +mutex olcConsoleGameEngineGLOOP::m_muxGame; \ No newline at end of file diff --git a/VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.h b/VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.h new file mode 100644 index 0000000..61d54e9 --- /dev/null +++ b/VS2018_BUILD_CGE/olcConsoleGameEngineGLOOP.h @@ -0,0 +1,597 @@ +/* +OneLoneCoder.com - Command Line Game Engine +"Who needs a frame buffer?" - @Javidx9 +License +~~~~~~~ +One Lone Coder Console Game Engine Copyright (C) 2018 Javidx9 +This program comes with ABSOLUTELY NO WARRANTY. +This is free software, and you are welcome to redistribute it +under certain conditions; See license for details. +Original works located at: +https://www.github.com/onelonecoder +https://www.onelonecoder.com +https://www.youtube.com/javidx9 +GNU GPLv3 +https://github.com/OneLoneCoder/videos/blob/master/LICENSE +From Javidx9 :) +~~~~~~~~~~~~~~~ +Hello! Ultimately I don't care what you use this for. It's intended to be +educational, and perhaps to the oddly minded - a little bit of fun. +Please hack this, change it and use it in any way you see fit. You acknowledge +that I am not responsible for anything bad that happens as a result of +your actions. However this code is protected by GNU GPLv3, see the license in the +github repo. This means you must attribute me if you use it. You can view this +license here: https://github.com/OneLoneCoder/videos/blob/master/LICENSE +Cheers! +Background +~~~~~~~~~~ +If you've seen any of my videos - I like to do things using the windows console. It's quick +and easy, and allows you to focus on just the code that matters - ideal when you're +experimenting. Thing is, I have to keep doing the same initialisation and display code +each time, so this class wraps that up. +Author +~~~~~~ +Twitter: @javidx9 http://twitter.com/javidx9 +Blog: http://www.onelonecoder.com +YouTube: http://www.youtube.com/javidx9 +Videos: +~~~~~~ +Original: https://youtu.be/cWc0hgYwZyc +Added mouse support: https://youtu.be/tdqc9hZhHxM +Beginners Guide: https://youtu.be/u5BhrA8ED0o +Shout Outs! +~~~~~~~~~~~ +Thanks to cool people who helped with testing, bug-finding and fixing! +wowLinh, JavaJack59, idkwid, kingtatgi, Return Null, CPP Guy +Last Updated: 18/03/2018 +Usage: +~~~~~~ +This class is abstract, so you must inherit from it. Override the OnUserCreate() function +with all the stuff you need for your application (for thready reasons it's best to do +this in this function and not your class constructor). Override the OnUserUpdate(float fElapsedTime) +function with the good stuff, it gives you the elapsed time since the last call so you +can modify your stuff dynamically. Both functions should return true, unless you need +the application to close. +int main() +{ +// Use olcConsoleGameEngine derived app +OneLoneCoder_Example game; +// Create a console with resolution 160x100 characters +// Each character occupies 8x8 pixels +game.ConstructConsole(160, 100, 8, 8); +// Start the engine! +game.Start(); +return 0; +} +Input is also handled for you - interrogate the m_keys[] array with the virtual +keycode you want to know about. bPressed is set for the frame the key is pressed down +in, bHeld is set if the key is held down, bReleased is set for the frame the key +is released in. The same applies to mouse! m_mousePosX and Y can be used to get +the current cursor position, and m_mouse[1..5] returns the mouse buttons. +The draw routines treat characters like pixels. By default they are set to white solid +blocks - but you can draw any unicode character, using any of the colours listed below. +There may be bugs! +See my other videos for examples! +http://www.youtube.com/javidx9 +Lots of programs to try: +http://www.github.com/OneLoneCoder/videos +Chat on the Discord server: +https://discord.gg/WhwHUMV +Be bored by Twitch: +http://www.twitch.tv/javidx9 +*/ + +#pragma once + +#ifndef UNICODE +#pragma message("Please enable UNICODE for your compiler! VS: Project Properties -> General -> \ +Character Set -> Use Unicode. Thanks! For now, I'll try enabling it for you - Javidx9") +#define UNICODE +#define _UNICODE +#endif + +#pragma comment(lib, "user32.lib") +#pragma comment(lib, "gdi32.lib") +#pragma comment(lib, "opengl32.lib") +#pragma comment(lib, "winmm.lib") + +#include +#include +#include +#include +#include +#include +#include +#include +#include +using namespace std; +#define GL_GENERATE_MIPMAP 0x8191 +#define GL_GENERATE_MIPMAP_HINT 0x8192 +typedef BOOL(WINAPI wglSwapInterval_t) (int interval); + + +enum COLOUR +{ + FG_BLACK = 0x0000, + FG_DARK_BLUE = 0x0001, + FG_DARK_GREEN = 0x0002, + FG_DARK_CYAN = 0x0003, + FG_DARK_RED = 0x0004, + FG_DARK_MAGENTA = 0x0005, + FG_DARK_YELLOW = 0x0006, + FG_GREY = 0x0007, // Thanks MS :-/ + FG_DARK_GREY = 0x0008, + FG_BLUE = 0x0009, + FG_GREEN = 0x000A, + FG_CYAN = 0x000B, + FG_RED = 0x000C, + FG_MAGENTA = 0x000D, + FG_YELLOW = 0x000E, + FG_WHITE = 0x000F, + BG_BLACK = 0x0000, + BG_DARK_BLUE = 0x0010, + BG_DARK_GREEN = 0x0020, + BG_DARK_CYAN = 0x0030, + BG_DARK_RED = 0x0040, + BG_DARK_MAGENTA = 0x0050, + BG_DARK_YELLOW = 0x0060, + BG_GREY = 0x0070, + BG_DARK_GREY = 0x0080, + BG_BLUE = 0x0090, + BG_GREEN = 0x00A0, + BG_CYAN = 0x00B0, + BG_RED = 0x00C0, + BG_MAGENTA = 0x00D0, + BG_YELLOW = 0x00E0, + BG_WHITE = 0x00F0, +}; +enum PIXEL_TYPE +{ + PIXEL_SOLID = 0x2588, + PIXEL_THREEQUARTERS = 0x2593, + PIXEL_HALF = 0x2592, + PIXEL_QUARTER = 0x2591, +}; + + +class olcSprite +{ +public: + olcSprite() + { + + } + + olcSprite(int w, int h) + { + Create(w, h); + } + + olcSprite(wstring sFile) + { + if (!Load(sFile)) + Create(8, 8); + } + + int nWidth = 0; + int nHeight = 0; + + //Yer touching private things king! + //private: + wchar_t *m_Glyphs = nullptr; + short *m_Colours = nullptr; + +private: + void Create(int w, int h) + { + nWidth = w; + nHeight = h; + m_Glyphs = new wchar_t[w*h]; + m_Colours = new short[w*h]; + for (int i = 0; i < w*h; i++) + { + m_Glyphs[i] = L' '; + m_Colours[i] = FG_BLACK; + } + } + +public: + void SetGlyph(int x, int y, wchar_t c) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return; + else + m_Glyphs[y * nWidth + x] = c; + } + + void SetColour(int x, int y, short c) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return; + else + m_Colours[y * nWidth + x] = c; + } + + wchar_t GetGlyph(int x, int y) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return L' '; + else + return m_Glyphs[y * nWidth + x]; + } + + short GetColour(int x, int y) + { + if (x <0 || x >= nWidth || y < 0 || y >= nHeight) + return FG_BLACK; + else + return m_Colours[y * nWidth + x]; + } + + wchar_t SampleGlyph(float x, float y) + { + int sx = (int)(x * (float)nWidth); + int sy = (int)(y * (float)nHeight - 1.0f); + if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) + return L' '; + else + return m_Glyphs[sy * nWidth + sx]; + } + + short SampleColour(float x, float y) + { + int sx = (int)(x * (float)nWidth); + int sy = (int)(y * (float)nHeight - 1.0f); + if (sx <0 || sx >= nWidth || sy < 0 || sy >= nHeight) + return FG_BLACK; + else + return m_Colours[sy * nWidth + sx]; + } + + bool Save(wstring sFile) + { + FILE *f = nullptr; + _wfopen_s(&f, sFile.c_str(), L"wb"); + if (f == nullptr) + return false; + + fwrite(&nWidth, sizeof(int), 1, f); + fwrite(&nHeight, sizeof(int), 1, f); + fwrite(m_Colours, sizeof(short), nWidth * nHeight, f); + fwrite(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); + + fclose(f); + + return true; + } + + bool Load(wstring sFile) + { + delete[] m_Glyphs; + delete[] m_Colours; + nWidth = 0; + nHeight = 0; + + FILE *f = nullptr; + _wfopen_s(&f, sFile.c_str(), L"rb"); + if (f == nullptr) + return false; + + fread(&nWidth, sizeof(int), 1, f); + fread(&nHeight, sizeof(int), 1, f); + + Create(nWidth, nHeight); + + fread(m_Colours, sizeof(short), nWidth * nHeight, f); + fread(m_Glyphs, sizeof(wchar_t), nWidth * nHeight, f); + + fclose(f); + return true; + } + +}; + +class olcConsoleGameEngineGLOOP +{ + // Mega Thanks to KrossX for completely converting the olcConsoleGameEngine to OpenGL! + uint32_t m_ColourPalette[16] = // 0xAABBGGRR + { + 0xFF000000, // BLACK + 0xFF800000, // DARK_BLUE + 0xFF008000, // DARK_GREEN + 0xFF808000, // DARK_CYAN + 0xFF000080, // DARK_RED + 0xFF800080, // DARK_MAGENTA + 0xFF008080, // DARK_YELLOW + 0xFFC0C0C0, // GREY + 0xFF808080, // DARK_GREY + 0xFFFF0000, // BLUE + 0xFF00FF00, // GREEN + 0xFFFFFF00, // CYAN + 0xFF0000FF, // RED + 0xFFFF00FF, // MAGENTA + 0xFF00FFFF, // YELLOW + 0xFFFFFFFF // WHITE + }; + + void UpdateMousePosition(int x, int y); + void ToggleFullscreen(HWND hWnd); + void WindowResize(void); + void WindowUpdateScale(void); + int SetPixelFormatGL(void); + static LRESULT CALLBACK olcWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam); + HWND ConstructWindow(int width, int height); + +public: + olcConsoleGameEngineGLOOP(); + int ConstructConsole(int width, int height, int fontw, int fonth); + virtual void Draw(int x, int y, wchar_t c = 0x2588, short col = 0x000F); + void Fill(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); + void DrawString(int x, int y, wstring c, short col = 0x000F); + void DrawStringAlpha(int x, int y, wstring c, short col = 0x000F); + void Clip(int &x, int &y); + void DrawLine(int x1, int y1, int x2, int y2, wchar_t c = 0x2588, short col = 0x000F); + void DrawCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); + void DrawTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F); + void FillTriangle(int x1, int y1, int x2, int y2, int x3, int y3, short c = 0x2588, short col = 0x000F); + void FillCircle(int xc, int yc, int r, wchar_t c = 0x2588, short col = 0x000F); + void DrawSprite(int x, int y, olcSprite *sprite); + void DrawPartialSprite(int x, int y, olcSprite *sprite, int ox, int oy, int w, int h); + void DrawWireFrameModel(const std::vector> &vecModelCoordinates, float x, float y, float r = 0.0f, float s = 1.0f, short col = FG_WHITE, short c = PIXEL_SOLID); + ~olcConsoleGameEngineGLOOP(); + + + void GenerateMipmapPow2(uint8_t *tex_new, uint8_t *tex_old, uint8_t *ref_alpha, int size); + +public: + void Start(); + int ScreenWidth(){ return m_nScreenWidth; } + int ScreenHeight(){ return m_nScreenHeight; } + +private: + void GameThread(); + + +public: + // User MUST OVERRIDE THESE!! + virtual bool OnUserCreate() = 0; + virtual bool OnUserUpdate(float fElapsedTime) = 0; + + // Optional for clean up + virtual bool OnUserDestroy() + { + return true; + } + + +protected: + struct sKeyState + { + bool bPressed; + bool bReleased; + bool bHeld; + } m_keys[256], m_mouse[5]; + + int m_mousePosX; + int m_mousePosY; + +public: + sKeyState GetKey(int nKeyID) { return m_keys[nKeyID]; } + int GetMouseX() { return m_mousePosX; } + int GetMouseY() { return m_mousePosY; } + sKeyState GetMouse(int nMouseButtonID) { return m_mouse[nMouseButtonID]; } + bool IsFocused() { return m_bConsoleInFocus; } + + +protected: + int Error(const wchar_t *msg); + + +protected: // Audio Engine ===================================================================== + + class olcAudioSample + { + public: + olcAudioSample() + { + + } + + olcAudioSample(std::wstring sWavFile) + { + // Load Wav file and convert to float format + FILE *f = nullptr; + _wfopen_s(&f, sWavFile.c_str(), L"rb"); + if (f == nullptr) + return; + + char dump[4]; + std::fread(&dump, sizeof(char), 4, f); // Read "RIFF" + if (strncmp(dump, "RIFF", 4) != 0) return; + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&dump, sizeof(char), 4, f); // Read "WAVE" + if (strncmp(dump, "WAVE", 4) != 0) return; + + // Read Wave description chunk + std::fread(&dump, sizeof(char), 4, f); // Read "fmt " + std::fread(&dump, sizeof(char), 4, f); // Not Interested + std::fread(&wavHeader, sizeof(WAVEFORMATEX) - 2, 1, f); // Read Wave Format Structure chunk + // Note the -2, because the structure has 2 bytes to indicate its own size + // which are not in the wav file + + // Just check if wave format is compatible with olcCGE + if (wavHeader.wBitsPerSample != 16 || wavHeader.nSamplesPerSec != 44100) + { + std::fclose(f); + return; + } + + // Search for audio data chunk + long nChunksize = 0; + std::fread(&dump, sizeof(char), 4, f); // Read chunk header + std::fread(&nChunksize, sizeof(long), 1, f); // Read chunk size + while (strncmp(dump, "data", 4) != 0) + { + // Not audio data, so just skip it + std::fseek(f, nChunksize, SEEK_CUR); + std::fread(&dump, sizeof(char), 4, f); + std::fread(&nChunksize, sizeof(long), 1, f); + } + + // Finally got to data, so read it all in and convert to float samples + nSamples = nChunksize / (wavHeader.nChannels * (wavHeader.wBitsPerSample >> 3)); + nChannels = wavHeader.nChannels; + + // Create floating point buffer to hold audio sample + fSample = new float[nSamples * nChannels]; + float *pSample = fSample; + + // Read in audio data and normalise + for (long i = 0; i < nSamples; i++) + { + for (int c = 0; c < nChannels; c++) + { + short s = 0; + std::fread(&s, sizeof(short), 1, f); + *pSample = (float)s / (float)(MAXSHORT); + pSample++; + } + } + + // All done, flag sound as valid + std::fclose(f); + bSampleValid = true; + } + + WAVEFORMATEX wavHeader; + float *fSample = nullptr; + long nSamples = 0; + int nChannels = 0; + bool bSampleValid = false; + }; + + void EnableSound(); + + // This vector holds all loaded sound samples in memory + std::vector vecAudioSamples; + + // This structure represents a sound that is currently playing. It only + // holds the sound ID and where this instance of it is up to for its + // current playback + struct sCurrentlyPlayingSample + { + int nAudioSampleID = 0; + long nSamplePosition = 0; + bool bFinished = false; + bool bLoop = false; + }; + std::list listActiveSamples; + + // Load a 16-bit WAVE file @ 44100Hz ONLY into memory. A sample ID + // number is returned if successful, otherwise -1 + unsigned int LoadAudioSample(std::wstring sWavFile); + + // Add sample 'id' to the mixers sounds to play list + void PlaySample(int id, bool bLoop = false); + void StopSample(int id); + + // The audio system uses by default a specific wave format + bool CreateAudio(unsigned int nSampleRate = 44100, unsigned int nChannels = 1, + unsigned int nBlocks = 8, unsigned int nBlockSamples = 512); + + // Stop and clean up audio system + bool DestroyAudio(); + + // Handler for soundcard request for more data + void waveOutProc(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwParam1, DWORD dwParam2); + + // Static wrapper for sound card handler + static void CALLBACK waveOutProcWrap(HWAVEOUT hWaveOut, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2); + + // Audio thread. This loop responds to requests from the soundcard to fill 'blocks' + // with audio data. If no requests are available it goes dormant until the sound + // card is ready for more data. The block is fille by the "user" in some manner + // and then issued to the soundcard. + void AudioThread(); + + // Overridden by user if they want to generate sound in real-time + virtual float onUserSoundSample(int nChannel, float fGlobalTime, float fTimeStep); + + + // Overriden by user if they want to manipulate the sound before it is played + virtual float onUserSoundFilter(int nChannel, float fGlobalTime, float fSample); + + + // The Sound Mixer - If the user wants to play many sounds simultaneously, and + // perhaps the same sound overlapping itself, then you need a mixer, which + // takes input from all sound sources for that audio frame. This mixer maintains + // a list of sound locations for all concurrently playing audio samples. Instead + // of duplicating audio data, we simply store the fact that a sound sample is in + // use and an offset into its sample data. As time progresses we update this offset + // until it is beyound the length of the sound sample it is attached to. At this + // point we remove the playing souind from the list. + // + // Additionally, the users application may want to generate sound instead of just + // playing audio clips (think a synthesizer for example) in whcih case we also + // provide an "onUser..." event to allow the user to return a sound for that point + // in time. + // + // Finally, before the sound is issued to the operating system for performing, the + // user gets one final chance to "filter" the sound, perhaps changing the volume + // or adding funky effects + float GetMixerOutput(int nChannel, float fGlobalTime, float fTimeStep); + + + unsigned int m_nSampleRate; + unsigned int m_nChannels; + unsigned int m_nBlockCount; + unsigned int m_nBlockSamples; + unsigned int m_nBlockCurrent; + + short* m_pBlockMemory = nullptr; + WAVEHDR *m_pWaveHeaders = nullptr; + HWAVEOUT m_hwDevice = nullptr; + + std::thread m_AudioThread; + std::atomic m_bAudioThreadActive = false; + std::atomic m_nBlockFree = 0; + std::condition_variable m_cvBlockNotZero; + std::mutex m_muxBlockNotZero; + std::atomic m_fGlobalTime = 0.0f; + bool m_bEnableSound = false; + +protected: + int m_nScreenWidth; + int m_nScreenHeight; + int m_nWindowWidth; + int m_nWindowHeight; + int m_nFontWidth; + int m_nFontHeight; + float m_fDrawScale; + float m_fDrawOffsetX; + float m_fDrawOffsetY; + float *m_fVertexArray; + uint32_t *m_uIndicesArray; + uint32_t *m_uForegroundColorArray; + uint32_t *m_uBackgroundColorArray; + float *m_fTexCoordArray; + CHAR_INFO *m_bufScreen; + CHAR_INFO *m_bufScreen_old; + uint8_t *m_bufMemory; + wstring m_sAppName; + SMALL_RECT m_rectWindow; + short m_keyOldState[256] = { 0 }; + short m_keyNewState[256] = { 0 }; + bool m_mouseOldState[5] = { 0 }; + bool m_mouseNewState[5] = { 0 }; + bool m_bConsoleInFocus = true; + bool m_bDoWindowUpdate = false; + HWND m_hConsole = nullptr; + HWND m_hWnd = nullptr; + HDC m_hDevCtx = nullptr; + HGLRC m_hRenCtx = nullptr; + GLuint m_uFontTexture; + static atomic m_bAtomActive; + static condition_variable m_cvGameFinished; + static mutex m_muxGame; +}; diff --git a/VS2018_BUILD_CGE/olcConsoleGameEngineOOP.cpp b/VS2018_BUILD_CGE/olcConsoleGameEngineOOP.cpp index a0d2eae..34494c7 100644 --- a/VS2018_BUILD_CGE/olcConsoleGameEngineOOP.cpp +++ b/VS2018_BUILD_CGE/olcConsoleGameEngineOOP.cpp @@ -93,8 +93,13 @@ int olcConsoleGameEngineOOP::ConstructConsole(int width, int height, int fontw, cfi.FontWeight = FW_NORMAL; //wcscpy_s(cfi.FaceName, L"Lucida Console"); //wcscpy_s(cfi.FaceName, L"Liberation Mono"); + +#ifdef RASTER_FONT + wcscpy_s(cfi.FaceName, L"Raster"); +#else wcscpy_s(cfi.FaceName, L"Consolas"); - //wcscpy_s(cfi.FaceName, L"Raster"); +#endif + if (!SetCurrentConsoleFontEx(m_hConsole, false, &cfi)) return Error(L"SetCurrentConsoleFontEx");