From 92d1bab65df8aa50df87d98d6fd6db1cfc4b88c1 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 12:00:35 +0100 Subject: [PATCH 01/16] small progress --- beta_v0.2.2.p8 | 177 ++++++++++++++++++++++++------------------------- 1 file changed, 87 insertions(+), 90 deletions(-) diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index 009f392..7413af6 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -437,10 +437,10 @@ function _init() } -- Decompress Logo - decompress_to_memory(map_data[5], 0x1C00) + -- decompress_to_memory(map_data[5], 0x1C00) -- -- Load map 1 for Intro - decompress_current_map() + -- decompress_current_map() SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| @@ -519,7 +519,7 @@ function init_intro() intro_counter, intro_page, x_cortex, x_protocol = 0, 1, -50, 128 intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) - controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ WEAPON MENU\n❎ ATTACK/USE", true) intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true @@ -725,7 +725,7 @@ end -- GAMEPLAY ---------------------- function init_gameplay() - decompress_current_map() + -- decompress_current_map() music(0) player_hud = player_hud.new() entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 @@ -764,7 +764,7 @@ function init_gameplay() end local tutorial_terminals = { - [[128,32,MOVE: ⬅️➡️⬆️⬇️|120,80,ATTACK: ❎|200,120,WEAPONS: 🅾️]], + [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48,ATTACK: ❎|40,-8,FRAGMENTS RESTORE HP|264,-2,WEAPONS MENU: 🅾️|368,-2,DEFEAT ENEMY]], "", -- No tutorials for mission 2 "", -- No tutorials for mission 3 "" -- No tutorials for mission 4 @@ -1745,7 +1745,7 @@ function terminal.new(x, y, target_door, tutorial_msg) -- Create panel at fixed screen position (will be adjusted with camera in draw) local msg = tutorial_msg or "❎ INTERACT" local panel_width = max(40, #msg * 4 + 12) -- Adjust width based on message length - t.panel = textpanel.new(64 - panel_width/2, 40, 10, panel_width, msg, true) + t.panel = textpanel.new(64 - panel_width/2, 114, 10, panel_width, msg, true) t.panel.selected = true return t @@ -1761,7 +1761,7 @@ function terminal:update() if self.tutorial_msg then -- Tutorial: simply show when close - self.interactive = dist < 24 + self.interactive = dist < 42 else -- Original terminal logic self.interactive = true @@ -2072,60 +2072,54 @@ e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee 5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d 53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d 3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd -5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 -a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b -0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 -14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f -d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 -6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 -7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c -3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd -1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 -2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 -821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf -6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf -a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b -6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 -2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 -8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 -2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 -7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 -73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 -55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf -6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 -081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 -0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b -8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 -e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 -5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 -c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 -41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 -462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 -2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b -188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 -e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 -260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a -f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 -d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 -d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 -aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 -1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 -1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 -e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c -df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d -e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 -76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 -40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 -64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a -174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 -7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 -d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc -4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a -4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 -01e3a0f160f038782c301e120f0a8f85c703e3a1f1e0f07848f01a4f388300019f6cee918b0e0109d08240c2306a40f6108a18c1bb783c83f14b20e170ff30ff -28641c4dd93c831441b1a4c07c9055382814cf1a4315b98740c1b1e411961097581d04d316c6054a8ff14af22e70fee03c28210cce7c0dcd83f1ca20e070efe3 -18ff0cff0ef705cb8886c954a4717ed09338cf0c5a12f71bf7854cc345e41358205ab8ff1c151a9d11f384fdc927637172c12ec81f6cff3e2607ff8701c1d52a -243b6174b8cec47806d20bf38f904369a8a33372f5b9f8ec700efc3f1a9f8dcf0fe731f3e3f1f9e8080000000000000000000000000000000000000000000000 +74b6060606060606060606060606c62636b606060606060606b6f6a7a7f6a7a7a7f6a7a740b70694740640c1c1a7f6a7a7a7a7a7a7a7a7f603e503e6e5f60696 +70707070707070177070707070b4050515701770656765450505050505053706f6a7a7f6b60606060606060606c6f6a7a7d6a7f6a7a7a7a7f6a7a7a7a7f60695 +b50404046466440404040404040404c5b5040404646644043606f6a7a7a7f6f6f6a7a7a740f606947406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7b7a7a7a7b006a5 +7017707070707070707070707075c606062565350607060606060606060606c6f6a7a7f6f6f6f6f6f6f6f6f6f6f6f6a7a7d6f6d6a7a7a7d4e4f6a7a7a7f606a6 +b405050505050515671505050505050505050505050505053706f6a7a7a7a7a7a7a7a7a7f64006947406f6a7a7a7a7a7a7a7a7a7a7a7d4e6e603f6a7b0b20694 +7070707070a27070a2707070707034f6f6f6f6f6f6f6f640f6f640f6f64003c0c0b2c0a7a7a7a7a7a7a7a7a7a7a7a7a7a7d6f6d5e6e2f2e5d5f6e6e6e6e60696 +74c60606060606060606060606060606060606060606060606c6f6a7a7a7a7a7a7a7a7a7a7f606947406f6a7a7a7a7a7a7a7a7a7a7a7d6a7a7f6a7b0b2b20694 +7070707070705161707070c2d0d0d0f6a7a7a7a7a7a740a7a7a7a740a74003b1b2b1c1a7a7a7a7a7b0c0a7a7a7a7a7a7d4d5f6a7a7e3f3a7a7f6a7a7a7400696 +7406f6b1b2b2c1f6d6f6f6f6f6f6f6d6f6f6f6f6f6f6f6f6f6f6f6f6a7a7a7a74040a7a7f6f606947406f6a7a7a7a7a7a7a7a7a7a7a7d6a7a7a7a7b140400695 +7070707070705262707070d1701700f6a7a7a7a7a7a7a7a7a740a7a7a7a74003c1a7a7f6f6f6f6b0b2b2c0f6f6f6d4e6e5a7f6a7a7d6a7a7a7f6a7a7a7400694 +7406f6b1b2b2c1a7d6a7a7a7a7a7a7d6a7a7a7a7f6f6f6f6f6f6f6f6f6f6f6f64040f6f6f6f606947406f6f6f6f6f6f6404040f6f6f6d6f6f6f6b0b2404006a5 +7070707070a27070a2d0d0d3707035f6f6f6f6f6f6f6f6f640f6f64040f6f603f6a787f6c60606060606060606b6e5a7a7a7a7f6e6e5a7a7f6a7a7a7a7f60695 +8406f6a7b1c1f6f6d6f6f6f6f6f6d4e5f6f6a7a7a7f6c6060606060706060606060606060606c69475b6b6f6f7c606060606060606060606060606060606c694 +7070707070707070707070707085b606060606060606060606060606060606b6f6a797f606260404040404043606f6a7a7a7d4e5f6f6f6f6a7a7d4e6e6e60696 +8416f6a7a7f6f6a7d5e4a7a7a7a7d6a7a7f6f6a7a7f60626046644040404040404040404040404c5176416f6f6062614646454040464664404045466044404c5 +70700000000000000000000000b5040404646444542444040404040404043606e797a7f706957070d17070707406f6a7a7a7d5e4a7a7a7a7a7a7d6a7a7f606a5 +7406f6a7f6f6a7a7a7d6a7a7a7a7d6a7a7a7f6f6a7f60694b4050555050505551570706567701770008606f6e7069400000000001700007017707070d11770a2 +a2000000000000000000000000b405050505050505c47000d170707000007416e7a7a7f624707070d17070177506f6a7a7a7a7d6a7a7a7a7a7a7d6a7a7f60696 +7506e6e6e6e6e4a7a7d5f6f6f6f6e5a7a7a7a7f6a7f6069474c6060606060606062535060735567077701640e7062715771770707070707070707070d1707070 +b405051567156567654505050537c60606060606b694700077a2707070007516f7a7a7e725707070a2a270707606f6f6a7a7a7d6a7a7a7a7a7a7d6a7f6f60696 +7406f6a7f6a7d6a7a7f6a7a7a7a7f6a7a7a7a7f6a7f606948406f6f6f6d6f6e7f7f6f640404006a57046c640f6b60606072567350607567070707000a2a27070 +74c6060606060607060606060606c6f640f6b7f606940000a2a2707070001786e78797f7a6701770a2a270708506f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f606a5 +7406f6a7f6a7d6a7f6d6a7a7a7a7a7f6a7a7a7f6a7f606947406e7b7a7d6a7a7a7d4e6e6e6e606a67034f6f6f6f6f6f7f6e7f6404040f47070707000a2a27070 +7406f6f6f6f6f6f6f6d6f6f6f6f6f6f6a74040f60694000000d1001770707047240000345700707070d1707074b606070606060606060706060606060606c694 +7406f6a7f6a7d6a7f6d5e6e2f2a7d4f6e6e6e6e6e6e606948406e6e5a7d6f6f6f6e5b0c0c0f616941785f687a7a7a7a7a7a7a7a797e770707017700070d17070 +7506f6a7a7a7a7a7a7d6a7a7a7a7a7a7a7a740f606940000a2a2000000700000000000000000707070d17070b5040404040404040404040404040404040404c5 +7406f6a7f6d4d5e6f6a7a7e3f3e6e5f6a7a7a7a740400694750640a7a7f6a7a7a7f6b1b2c1f706a47084408797a7a7a7a79797a7a7a7707070707000c2d37070 +8606e6e6e6e4f6f6f6e5b0c0a7d4e6e6e6e6e5f606947000a2a20000007070007070707070177070a2a27070b4051565656715701770656765051567150505c4 +7406e6e6e6e5a7a7f6a7a7d6a7a7a7f6a7a7a7f640400694860640a7f6d6a703a7a7f6b1c1f606a670864040f6f6f6f7f6f6e7f6e7f6c77070707000d1707070 +8616f6a7a7f6a7a7b0f6b1c0a7d6a7a7b0c0a7f606967070c3d0d2007070707070177070707070c2a2a2707075c606060606062565350607060606060606b694 +7506f6a7f6a7a7a7a7f6e6e5a7a7f6a7a7a7a7f6a7f606948616f7a7f6d5039703d4e6e6e6e616967047246466346664340706060606577070707000a2a27070 +7606f6a7f6d6c003b1c0f6c1a7d6a7b0b2b2b2f6f470707070a2a2707017707070707070707070d1707070707034f6f7e7f6404040f6e74040f640f6e7f60694 +76064040f6a7a7a7d4e5f6f6f6f6a7a7d4e6e6e6e6e606948606f6a7f6a7a70397e5f6a7a7f606967070177070707070706664666464001770707000a2a27070 +7606f6a7f6d503a703e6e6e6e6e5b0b2b2b2c1a77070177070a2a2707070707070707070707070d1707070700000e7a7a7a7a7a787a7a7a7a7a7a740b7f60695 +86064040f6f6a7a7d5e4a7a7a7a7a7a7d6a7f6f6b0c006948506f6a7a7f6e6e5a7e7a7a7a7f7f470707070707070707070707070700000707070707070d17070 +8506f6a7f6a7a703a7a7f6a7a7a7b1c1b1c1a7a77070000000d100000046256535560000001700c3a2a200000000f687a7a7a7a7a7a7a7a7a7a7a7a7d5e60694 +76064040a7f6f6a7a7d6a7a7a7a7a7a7d6f6f6b0b2c106948406f6a7d4e5f6f6f687a7a787977070177070701770707070707017700000707070170070d17070 +7406f6c0a7f6e6e5a7f6a7a7a7a7a7a7a7a7a7a77070000000c3d2000086e7e7f7a6000000000000a2a200001700f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70695 +8506f6a7a7a7f6f6f6d6f6f6f6f6f6f6d6f6a7b0c1f606948416c0a7d6a7a7a7a7a7a7a7a7a770707070707070000000000000707070a2707070000070d17070 +7506f6b2d4e5f6f6f6a7a7b0b0c0a7f6f6f6f6f6c77070707070d1707076f7b7f6a670007070707070d170700000f6c0a787a7a7a7a7a7a7a7a7a7a7a7a70696 +7406f6b7a7a7d4e6e6e5a7b0b0c0a7a7d5e4b0c1b7f606947406b2c0d6a797a7a7a7a7a7a79770707070707070c2a2d0d0d0d2707070d17070a2a27070d17070 +7606f6c1d6a7a7a7a7a7b0c1b1b1c0f6a740404006a57070a2a2d3177086f7e7e72570007070a2a270d170707000e7b1c0a7a7b7e4a7a7a7a7a7a740a7f606a5 +7406f6f6f6f6d6f6f6f6b0b2b2b2c0f6f6d6b1f6f6f606947506b2c1d6f6e7f6f6f6f7e7f7e7c7707070a2d0d0d370707070c3a2d0d0d3d0d0a2a2d0d0777070 +8506f6f6d6f6f6f6f6b0c1f6f6f6f6f64040404006947070a2a2707070472417345770007070a277d0d370707035f7f6e7f7f6f6d6f6e7f6f6f6e7f640f60694 +74b60606060606066606060606060606060606060606c69470470606060706060606060706065770707070707070177070707070707017707070707070707070 +74b6060606060606060606060606060606060606c69470707070707070707070707070707070d1707070707085b606060606060606060606060606060606c694 +b504040404040414174414666664644454244404040404c5177034246464666466641464666470a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2 +b50404040404040404646444542444040404040404c5a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2b5040404646444542444040404040404040404c5 __label__ 00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 @@ -2260,35 +2254,38 @@ __gff__ 0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __map__ -0381c55000f808281c0e533b1d8a2073881cac762b150a05028c02c80f00728009154a2562b1da00c500c32612e034f0145808b4080a060c4c82fe0009703b381c9c0f4a080701e203c001800000ec029200aa0732180c07030180a45629c018e016a768089c0352006e6b2491cd901a6808dc0c24d64982fa1c40105d280190 -a8af01c43841114e101e783fb0f01a0d150b61204710043d88be04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e1436880000160f600a607219f808783fc1648da7e379fcfc6f3012a2ce47187fbc424c2a161ec428a22667e3c1e8f4793d1e -0f27a3d06c39086280394004e0285008236c030a0bc404301648e60379ee01eb01333dc1fd0de7b0201205a707f33481c0e7783fa0740110430381cfe7982891e09a5e3d1e8fc7b3798623c477068345b07f684490944d18a38c99c1c48f10f1383bac1c0e2187080583fa43b4cd257304080cbb128282894250a0fe909e281c -29bcc05a068347b1da238c998a1fe853379e21a6478369e0f0793c9fcfc608cf2c7e121fe82a068f45512f290991f4fd12638bc2460ca004700c2021bc160c837a13a20a905e23441fd214886f269b8dc6e83fb1be20046f3d45fc89315e38bc1c1fd4b0613f43298f40b3c9e8ff17f483fd0b22f8528a38c911d248ae423311 -88c6621c9d9a464505a48202174dc60251a0c26f2693a1e067a269ba2a64048bfa17626843083e9134dc60394c34e60c60793f91c40047304720cdf1c023786e2bc517f39b1143fe25f6d209483b1910804020caf8c0e07221048c66331108a40319863b471942379e8360c936a1b8bc6d37974dc2e1797a20a707f590d90c0f -430371bcbc6f35cc9de0fe80083f945fca13a87a379e635a44a9df94f6ce7b09004f80a7d020ce716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f4936495d24bce6a8769c7f8e622920023012652286f02480d274a910e23012 -45907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925 -088e71c462319820fe672ade956fc89942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5 -155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b30988804123110804520170b70b032311888452111216e767f22dc039e1 -7c85c9502d8f10ff22e235dd39628a5726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132ca697c24a0da42d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0 -fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715493ee7bf8402c562bc26acc7ea9ba549d68401dd78c7983 -c299fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09d6b8e61fa58ba09e9ce201ed4a38c2f16ad48e9aa13d4be530d00957c3a64f6af2484000010e87e525f491225e48e8feb30fb9ef94c76665ed4 -a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e99a09832efe7baf7d9d90345bae73801b502ccd58ee2cc39cc94b3fcdb083dc59c0a1c0e2a96f9e2f8ac6499bd4c07fef57381d18072b5816b78f86adc39432148ec53291db93a453c1396df08ca5783ff41 -ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06fcaa16fdc8f07a36f0b8c0807160360feda632cfe174ad3a9adcff6c16a0c35747a123d1 -71657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683 -ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c00239acc1000a36188c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f30 -1a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d -8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f65 -04a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae -6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8a -a5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1 -919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d08 -4000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a758 -95ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc43331188 -76db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa -264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc57 -6514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac -3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0 -ae703d9b6f200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505050504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c +072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060606060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49 +2a07070700000007070700003c0d0d2a2d072a0771000771075f046f6f7e6f6f6f7f6f1b2b0404046f6f6f7e6f6f6f6f6f6f6f6f6f6f6d6f6f6f6f046f7b604947606e4e6f6f6d6f6f6f6d6f6f6f6d6f60494760046f046f6f6f6f6f04046f605907077107000000002c3d0000000007437e04787f6f6f7e6d7e6f7f7e6f604a +2a07077100000007070700000707070715163d070700070707077e787a7a797a78797a7a1b1c6f6f7f797a7a7a7a7a7a787a7a7a7a7a6d7a7a7a6f040404605947606f7b7a7a6d7a7a7a6d7a7a7a7b6f604947606f7b04047a7a7a7a04046f606907077707000000001d007100000007077f79797a7a784d5e7a7a7e7b6f6149 +2a07770d0d2d07070707000007710707252607070700070707077f797a786f6f6f6f7a7a7a7a7a7a7a7a7a6f6f6f7f6f7a787a7a7a7a6d7a7a7a6f6f6d6f606957606f7a7a7a5d6f6f6f5e7a7a7a7a6f604947606f6d7a7a6f6f6f7a7a7a6f605a0d0d3d07000071072a2a0000000007536f787a7879786d787879797f7e6049 +2a070707071d0707070700000707072a0d3d2a7107000707077d7e7f6f6f7e04046f6f6f6f7f6f6f7f6f6f7e0404046f0b0c6f7a7a7a5d4e7a7a7a7a6d6f605a68606f7a7a7a6f7a7a7a7a6f4d6e6e6e604947606f6d7a6f047a7a6f7a046f604907710707000000072a2a0000000058617e7a7a7a7a6f6f6f7a0b797a7f604a +2a070707071d07070707000000002a3c2d070707070071072a7460606060607060604266464666437060606060606070606b6f047a7a6f6f6f6f7a0b5d6e604a68616f4d4e6f6d7a7a7a4d6e5e7a7a04604947606f5d6f7a7a307a046f4d6e60725050515654504c07071d0000710047607f797a7a6f7a6d7a6f1b7a786f6149 +2a070771071d070707070000002a072a1d0707070700072a0707664646664440404107070771070744414666664445406361047a7a7a6f7a7a6f7a1b0c6f604947606e5e6d6f5d6e2e2f5e7a6f7a7a04604947606f7a6f04307a306e6f5e6f6b6060606060606b4907071d0000000047606f787a6f7a7a307a7a6f797a6f604a +4b505050517651505050505050505050505050505050505050505050505050505050505050505050505050507656545073606f7a7a7a6f7a7a6f6f6f6f6f605947606f7a5d6f7a7a3e3f7a7a6f7a7a6f604947606f7a6f6e6e3004046f7a0404046f6f6f6f6f6049072a2a0000070748617e797a6f7a3079304e7a79786f6049 +476c60606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a +57606f6f6d6f6f7a0b2b046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f6d6f6f1b1c6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f6d7a307a4d606a47606f7a7a7a6f6e5e7a7a6f7a6d7a6f604968606f7a7a046f6d6f7a7a7a6f6c60606b047a6f60493d07077100000047607f787a7a6f7a5d4e7e7a797a6f6059 +68606f7b5e7a7a7a0b2b1c047a7a7a04047a7a0b0c7a7a7a7a7a7a4d5e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f5d307a305e605a47606f7a7a4d5e6f6f6f6f7a4d5e7a6f604968616f7a7a7a7a6d7a7a7a7a6f60626360047a6f60725107070707544c47606f797a7a7a6f6f6f787a797a6f6069 +68616f7a7a7a7a0b2b1c7a7a7a7a6f04046f0b2b0c6f6f7a7a6f6f6d6f6f6f6f6f6f6f0b0c6f6f7a7a6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f7a7a307a7a604947606f6f7a6d7a7a7a7a7a7a6d7a7a6f604947606f6f7a4d6e5e7a7a046f6f604947606f7a6f6b6060520707536b49486079797a7a7a7a7a6d7a7a7a7978605a +68606f7a7a7a7a1b1c7a7a7a7a6f6c6060606060606b6f7a7a6f6c606060606060606060606b6f7a7a6f6c6060606060606b6f6f7a6f6f04046f6e5e7e7e604a47606f6f6f6d6f6f6f6f6f6f6d6f6f6f604947606f6f6f6d6f6f6f6f6f6f6f604947606f7a6f6f04047e6f7f6f605947607878797a78797a6d787a787979614a +58606f7b4e7a7a7a7a7a7a047a6f60624040404063606f7a7a6f6062404040404040404063606f7a7a6f606240404040636b6b6f7a6f6c606060606060606c49476b60606060606060606060606b6f6f6049476b60606060606060606060606c4947606f7a7a7a04047a787b6f606948606f78046f6f6f7e6d7f7e6f6f7f604a +48606f6f6d6f6f6f6f6f6f6f046f60494b50505073606f7a7a6f60725050504c4b50505073606f7a7a6f60725050504c5b63606f7a0b6162404146444045405c5b404040404646444542444063606f6f60495b404646444542444040404040405c47606f7a7a7a7a7a7a7a7a7f605a476b6070606060606b6f6f6c6060606c49 +476b6060606060606060606060606c49476c6060606c6f7a7a6f6b6060606b49476c6060606c6f7a7a6f6b6060606b494b73616f7a1b6072505051565450504c00000007070707070707545073606f6f607250505050505050505050505050505073606e6e6e6e4e7a7a04046f60495b4040404040456360046f60624040405c +5b40404046664440404040404040405c47600404046f6f7a7a6f6d6f0404604947600404046f6f7a7a6f046f6f6f6049476c6c6f7a6f6b606060606060606b49077700076452565653537060606c6f6f6b60606060606060606060606060606060606c6f7a7a7a6d7a7a7a046f6072515656765050507360046f60725050504c +4b50505050505050505050517651505073600404046f7a7a7a7a5d6e0404604948606f7a7a7a7a7a7a7a7a047b6f604947606f6f7a6f6f6f6f042b2b04046049001d0007436f6f6f6f6f6f6f6f6f6f6f6f6f1b1c6f6f6f0404046f6f6f6f6f6f307a7a7a7a7a7a6d7a7a7a7a6f6b6060606060606060606c6f6f6b6060606b49 +476c6060606060606060606060606060606c6f6f6f6f7a7a7a7a7a6f0b0b604948616f7a7a7a7a7a7a7a7a7a5d6e604957606f7a7a7a7a7a7a7a1b2b2b0c6059071d0007077a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f0404046f7a7a7a7a04307a7a7a7a7a7a6d7a7a7a7a6f6f6f6f6f0404046f6f6f6f6f6f6f6f6f6f6149 +47606f6f6f6f6f04046f6d6f6f6f6f6f6f6f6f7a7a7a7a7a7a7a7a0b2b2b604947606f7b4e7a7a7a7a7a7a047a6f604968600c7a7a7a7a7a7a7a7a1b2b6f6049073c2d07077a7a7a7a7a7a047a047a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a30047a7a7a7a7a045d4e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b6f614a +47606f7b7a7a7a7a7a7a6d7a7a6f6f6f6f6f6f6f6f6f6f6f6f6f6f0b2b2b604957606f6f6d6f6f6f6f6f6f6f046f604968612b0c6f6f6f6f6f6f6f7a1b1c604907711d07536f6f6f6f6f0404046f6f6f6f6f6f6f6f0b0c6f6f6f6f6f6f6f6f306f7a7a6f6f6f04046d6f6f6f6f6f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604a +47606e6e6e6e6f6f6f6f5e7a7a6f6c60606b6060606060606060606060606c49586b6060607060606060606060606c49586b606060606060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49 +48606f7a7a307a7a7a7a307a7a6f60624040404046664440404040404040405c5b40406644404040404040404040405c5b4040404040404063600c7a7a6f605907001d0707664644404040404040404040404040404040406361046f605947606f7a7a6f6062404040464644454244404040404040404040410707710044405c +48606f7a6f6d7a7a7a7a7a6f7a6f60494b505050505176515050505050505050505050505050504c4b5050505051077107567656545050507360040c7a6f606a07071d07070707073c0d0d0d2d000000000000000000000073606f6f6a004760047a7a6f604900072a2a0000000000000000710000000000000000000000072a +48616f7a6f5d6e6f6f7a4d6e6e6e6049476c60606060606060606060606060606060606060606b49476c6060606052565360706060606060606c1c7a7a6f606907001d0707070707070707071d6452765352765656765360606c046f60655760047a7a6f604900072a2a0000000071000000000000000071000000000000072a +47606f7a6f7a7a6f6f6e5e6f7a6f604947600404046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604947606f6f04046f6f6d6f6f6f0404046f6f6f6f7a7a0b606907711d0707070707070707071d430404047e6f7f6f7e6f6d7f6f7a04044207436f7a7a6f60490007071d00004b5051565676505051765150505050505050504c +57606f7a6f7a7a6d7a7a7a6f7a6f6072736004047a7a7a7a7a306f6f7a7a7a7a7a7a7a7a6f6f604947606f7a0b0c7a7a6d7a7a7a7a7a7a7a7a7a7a7a7a1b604907001d0707070771070707071d686f7a79787a7a04787a6d797a7a7a04072c0d6f7a7a6f60490071071d0000476c607060606060606070606060606070606b49 +68616f7a7a306e5e7a7a307a7a6f6b60606c6f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a0b6f604947606f7a7a1b0c6f5e7a7a7a7a7a7a7a7a4d6e6e6e6e60490d0d3d0707070707070707071d437f7a7b7a7a0479787a5d6e6e7b7a6f0d3d076f7a046f60490007772a000047600404046f6d6f6f6f6f6f6f6d6f6f6f6f6059 +68606f7a4d5e6f6f6f6f7a7a7a6f6f6f6f6f6f7a7a7a7a7a7a7a7a7a7a7a6f6f6f7a7a0b2b6f604947606f7a6f301b306f0c7a7a7a7a7a7a7a6d7a7a7a6f60490707070707070707070707071d677e786d7a0404787a7a797a7a78797e5207536f7a7a6f6049002c2a2a000047600404047a6d7a7a7a7a7a4d5e7a7a6f6f6049 +58606f7b6d7a7a7a7a7a7a7a7a6f6f6f6f6f6f7a7a7a7a7a7a306f7a7a6f7a7a7a6f7a0b2b1c604947606e6e6e6e7b0b2b1c7a7a7a7a7a7a7a6d7a7a7a6f60490707070707070707077107073c537f6f6d6f6f6f7f7e6f6f7f7e6f7f7f6a58606f7a7a6f6049001d0707000047606f7a7a7a5d4e7a7a7a7a6d7a7a7a7a6f6049 +48606f6f6d6f6f6f6f6f6f6f04046c60606b6f6f6f6f6f6f6f6f6f7a7a6f7a6f7a6f7a1b1c0460494760041b0c6f306f1c7a7a7a7a7a7a7a6f6d6f7a4d6e606907070707070707070707070707744246436646434246466643426646437547606f7a7a6f607250505050505073606e6e6e4e7a5d6f6f6f6f5e7a7a7a7a6f6049 __sfx__ 151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 From 5492e82af9d47865b96e485f7db787e89bbdb038 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 14:57:00 +0100 Subject: [PATCH 02/16] small progress --- beta_v0.2.2.p8 | 2 +- beta_v0.2.2_copy.p8 | 2335 +++++++++++++++++++++++++++++++++++ compression_utilities.ipynb | 18 +- 3 files changed, 2345 insertions(+), 10 deletions(-) create mode 100644 beta_v0.2.2_copy.p8 diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index 7413af6..1004df5 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -2276,7 +2276,7 @@ __map__ 476c6060606060606060606060606060606c6f6f6f6f7a7a7a7a7a6f0b0b604948616f7a7a7a7a7a7a7a7a7a5d6e604957606f7a7a7a7a7a7a7a1b2b2b0c6059071d0007077a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f0404046f7a7a7a7a04307a7a7a7a7a7a6d7a7a7a7a6f6f6f6f6f0404046f6f6f6f6f6f6f6f6f6f6149 47606f6f6f6f6f04046f6d6f6f6f6f6f6f6f6f7a7a7a7a7a7a7a7a0b2b2b604947606f7b4e7a7a7a7a7a7a047a6f604968600c7a7a7a7a7a7a7a7a1b2b6f6049073c2d07077a7a7a7a7a7a047a047a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a30047a7a7a7a7a045d4e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b6f614a 47606f7b7a7a7a7a7a7a6d7a7a6f6f6f6f6f6f6f6f6f6f6f6f6f6f0b2b2b604957606f6f6d6f6f6f6f6f6f6f046f604968612b0c6f6f6f6f6f6f6f7a1b1c604907711d07536f6f6f6f6f0404046f6f6f6f6f6f6f6f0b0c6f6f6f6f6f6f6f6f306f7a7a6f6f6f04046d6f6f6f6f6f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604a -47606e6e6e6e6f6f6f6f5e7a7a6f6c60606b6060606060606060606060606c49586b6060607060606060606060606c49586b606060606060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49 +47606e6e6e6e6f6f6f6f5e7a7a6f6c6060606060606060606060606060606c49586b6060607060606060606060606c49586b606060606060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49 48606f7a7a307a7a7a7a307a7a6f60624040404046664440404040404040405c5b40406644404040404040404040405c5b4040404040404063600c7a7a6f605907001d0707664644404040404040404040404040404040406361046f605947606f7a7a6f6062404040464644454244404040404040404040410707710044405c 48606f7a6f6d7a7a7a7a7a6f7a6f60494b505050505176515050505050505050505050505050504c4b5050505051077107567656545050507360040c7a6f606a07071d07070707073c0d0d0d2d000000000000000000000073606f6f6a004760047a7a6f604900072a2a0000000000000000710000000000000000000000072a 48616f7a6f5d6e6f6f7a4d6e6e6e6049476c60606060606060606060606060606060606060606b49476c6060606052565360706060606060606c1c7a7a6f606907001d0707070707070707071d6452765352765656765360606c046f60655760047a7a6f604900072a2a0000000071000000000000000071000000000000072a diff --git a/beta_v0.2.2_copy.p8 b/beta_v0.2.2_copy.p8 new file mode 100644 index 0000000..18d461b --- /dev/null +++ b/beta_v0.2.2_copy.p8 @@ -0,0 +1,2335 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + +--[[ +CORTEX PROTOCOL + by Emanuele Bonura + itch: https://izzy88izzy.itch.io/ + github: https://github.com/EBonura/CortexProtocol + instagram: https://www.instagram.com/izzy88izzy/ + minified with: https://thisismypassport.github.io/shrinko8/ + +How to Play: +* Use arrow keys to move +* Press X to use your selected ability +* Press O to open the ability menu and switch between abilities +* Interact with terminals using X when prompted + +Main Objective: +* Activate all terminals and reach the extraction point + +Optional Objectives: +* Collect data fragments to restore health and earn credits +* Eliminate all enemy units +]] + +-- MAP COMPRESSION +---------------------- +function decompress_to_memory(compressed_data, dest_address) + local byte_index, bit_index, dest_index = 1, 0, dest_address + local function read_bits(num_bits) + local value = 0 + for _ = 1, num_bits or 1 do + if byte_index > #compressed_data then return end + value = bor(shl(value, 1), band(shr(compressed_data[byte_index], 7 - bit_index), 1)) + bit_index += 1 + if bit_index == 8 then + bit_index = 0 + byte_index += 1 + end + end + return value + end + + while true do + if read_bits() == 0 then + local byte = read_bits(8) + if not byte then return end + poke(dest_index, byte) + dest_index += 1 + else + local distance, length = dest_index - read_bits(12) - 1, read_bits(4) + 1 + for _ = 1, length do + poke(dest_index, peek(distance)) + distance, dest_index = distance + 1, dest_index + 1 + end + end + end +end + +function decompress_current_map() + local i = current_mission > 2 and 3 or 1 + decompress_to_memory(map_data[i], 0x2000) + decompress_to_memory(map_data[i + 1], 0x1000) +end + + +-- HELPER FUNCTIONS +---------------------- +function reset_pal(_cls) + pal() + palt(0) + palt(14,true) + if _cls then cls() end +end + +function check_tile_flag(x, y, flag) + return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) +end + +function stringToTable(str) + local tbl = {} + for pair in all(split(str, "|", false)) do + add(tbl, split(pair, ",", true)) + end + return tbl +end + +function dist_trig(dx, dy) + local ang = atan2(dx, dy) + return dx * cos(ang) + dy * sin(ang) +end + +function draw_shadow(circle_x, circle_y, radius, swap_palette) + local swapped_palette = {} + for i = 0, 15 do + for j = 0, 15 do + local index = (i << 4) | j + swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] + end + end + + -- Pre-compute squared radius + local radius_squared = radius * radius + + -- calculate the top and bottom y of the circle + local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) + + -- Function to swap palette for a line + local function swap_line(y, start_x, end_x) + local line_start_addr = 0x6000 + y * 64 + for i = 0, start_x >> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y) + spr(224, x_protocol, y + 12, 9, 2) + spr(233, x_cortex, y, 7, 2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + c += cond(i) and 0 or 1 + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + + +-- CAMERA +---------------------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active, self.t, self.closing = true, 0, true +end + +function transition:update() + if not self.active then return false end + + self.t += self.closing and 1 or -1 + + if self.closing and self.t == self.duration then + self.closing = false + return true + elseif not self.closing and self.t == 0 then + self.active = false + end + + return false +end + +function transition:draw() + if not self.active then return end + local size = max(1, flr(16 * self.t/self.duration)) + for x = 0, 127, size do + for y = 0, 127, size do + rectfill(x, y, x+size-1, y+size-1, pget(x, y)) + end + end +end + +-- TEXT PANEL +---------------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx = cam.x + self.x + self.x_offset - self.expand_counter + local dy = cam.y + self.y + local w = self.width + self.expand_counter * 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx + w - 2, dy - 1, dx + w + 1, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + local line_x = dx + (self.line_offset % (w + 1)) + line(line_x, dy, line_x, dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, dx + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter += self.selected and (self.expand_counter < 3 and 1 or 0) or (self.expand_counter > 0 and -1 or 0) + + self.x_offset += self.move_direction * self.max_offset / 5 + if self.x_offset <= -self.max_offset or self.x_offset >= 0 then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +---------------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y = self.owner.x+self.owner.width/2, self.owner.y+self.owner.height/2 + local x1,y1 = t.x+t.width/2, t.y+t.height/2 + local dx,dy = x1-x, y1-y + local step = max(abs(dx), abs(dy)) + + for i=0,step do + if check_tile_flag(x+dx*i/step, y+dy*i/step) then return false end + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +---------------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new(37, 30 + (i - 1) * 16, 10, 54, a.name) + p.ability_index = i + add(self.panels, p) + end + + add(self.panels, textpanel.new(13, 94, 20, 102, "")) + + self.active = true + if #self.panels > 1 then + self.panels[self.last_selected_ability].selected = true + end +end + +function ability_menu:update() + if not self.active then return end + + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.panels[self.last_selected_ability].selected = false + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + local current_panel = self.panels[self.last_selected_ability] + current_panel.selected = true + player.selected_ability = current_panel.ability_index + sfx(19) + end + + foreach(self.panels, function(p) p:update() end) + + self.panels[#self.panels].textline = + "dATA SHARDS LEFT: " .. count_remaining_fragments() .. + "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + p.text_color = ability.remaining_uses > 0 and (p.selected and 11 or 5) or 2 + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- MAIN +---------------------- +function _init() + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS = { + "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", + "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", + "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", + "FACILITY 800a:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" + } + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 + + -- Load compressed map from map data + map_data = { + -- 4 Levels + pack(peek(0x2000, 2005)), + pack(peek(0x2000 + 2005, 1585)), + pack(peek(0x1000, 1788)), + pack(peek(0x1000 + 1788, 1402)), + -- Logo + pack(peek(0x1000 + 1788 + 1402, 243)) + } + + -- Decompress Logo + decompress_to_memory(map_data[5], 0x1C00) + + -- -- Load map 1 for Intro + decompress_current_map() + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + change_state("loadout_select", true) +end + + +function _update() + if trans.active then + if trans:update() then + current_state = next_state + current_state.init() + next_state = nil + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, skip_transition) + local new_state = states[new_state_name] + + if skip_transition ~= true and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + music(05) + intro_counter, intro_page, x_cortex, x_protocol = 0, 1, -50, 128 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ WEAPON MENU\n❎ ATTACK/USE", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + intro_counter += 1 + x_cortex, x_protocol = min(15, x_cortex + 2), max(45, x_protocol - 2) + + if intro_counter == 30 then sfx(20) end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select") + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(t()) < .9 then circfill(63,64, 3, 2) end + + local y_logos = intro_text_panel.active and 0 or 30 + display_logo(x_cortex, x_protocol, y_logos) + + intro_text_panel:draw() + controls_text_panel:draw() + if intro_counter > 60 then print("PRESS ❎ TO CONTINUE", 24, 118, 11) end +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + music(0) + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,69,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]] + ) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + info_panel.char_count = 0 + sfx(19) + elseif btnp(⬅️) or btnp(➡️) then + show_briefing = not show_briefing + info_panel.char_count = 0 + sfx(19) + elseif btnp(❎) then + change_state("loadout_select") + elseif btnp(🅾️) then + change_state("intro") + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select") + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay") + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + else + p.active = has_weapon + end + p:update() + end + + foreach(count_panels, function(p) p:update() end) +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + foreach(loadout_panels, function(p) p:draw() end) + foreach(count_panels, function(p) p:draw() end) + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + decompress_current_map() + music(0) + player_hud = player_hud.new() + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 + + local mission_entities = { + [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], + [[0,0,bot,player|528,144,bot,dervish|624,160,bot,vanguard|688,288,bot,dervish|616,48,preacher,cyberseer|824,136,bot,warden|680,96,bot,dervish|920,32,bot,dervish|984,96,bot,warden|896,160,bot,vanguard|904,312,preacher,quantumcleric|976,248,bot,vanguard|800,376,bot,warden|728,336,bot,vanguard|816,320,bot,vanguard|608,360,bot,warden|968,1200,bot,vanguard]], + [[0,0,bot,player|240,416,bot,warden|88,336,bot,dervish|160,368,bot,vanguard|24,416,bot,warden|216,104,bot,vanguard|256,40,bot,dervish|296,72,bot,dervish|136,80,preacher,cyberseer|32,88,bot,dervish|32,32,bot,dervish|40,160,bot,warden|344,344,bot,vanguard|456,336,preacher,quantumcleric|368,416,bot,dervish|416,128,bot,vanguard|344,136,bot,vanguard|424,96,preacher,quantumcleric|352,240,bot,vanguard|432,264,bot,vanguard|496,152,bot,warden]], + [[0,0,bot,player|880,412,bot,dervish|760,408,bot,dervish|696,424,bot,vanguard|600,360,bot,warden|552,400,bot,warden|592,256,bot,vanguard|528,280,preacher,cyberseer|528,208,bot,vanguard|560,168,bot,vanguard|688,296,bot,dervish|688,360,bot,dervish|760,304,bot,warden|912,344,preacher,quantumcleric|848,344,bot,dervish|712,192,bot,warden|776,200,bot,warden|888,192,bot,warden|984,184,preacher,cyberseer|992,256,bot,vanguard|640,32,bot,vanguard|632,104,bot,vanguard|664,32,bot,dervish|664,104,bot,dervish|704,32,bot,dervish|704,104,bot,dervish|896,40,preacher,quantumcleric|968,96,preacher,cyberseer]] + } + + for e in all(stringToTable(mission_entities[current_mission])) do + if e[4] != "player" then + add(entities, entity.new(unpack(e))) + end + end + + boundaries = stringToTable("0,0,0,0,64,56|64,0,0,0,128,56|0,0,0,0,64,56|64,0,0,0,128,56")[current_mission] + + for map_y = boundaries[2], boundaries[6] do + for map_x = boundaries[1],boundaries[5] do + local tile, tile_x, tile_y = mget(map_x,map_y), map_x*8, map_y*8 + if fget(tile, 6) then + add(barrels, barrel.new(tile_x, tile_y)) + elseif fget(tile, 5) then + add(data_fragments, data_fragment.new(tile_x, tile_y)) + elseif fget(tile, 4) then + add(terminals, terminal.new(tile_x+4, tile_y-4)) + elseif fget(tile, 7) then + player_spawn_x, player_spawn_y = tile_x, tile_y + player.x, player.y = tile_x, tile_y + player.health = player.max_health + add(entities, player) + end + end + end + + local tutorial_terminals = { + [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48,ATTACK: ❎|40,-8,FRAGMENTS RESTORE HP|264,-2,WEAPONS MENU: 🅾️|368,-2,DEFEAT ENEMY]], + "", -- No tutorials for mission 2 + "", -- No tutorials for mission 3 + "" -- No tutorials for mission 4 + } + + if tutorial_terminals[current_mission] != "" then + for args in all(stringToTable(tutorial_terminals[current_mission])) do + add(terminals, terminal.new(args[1], args[2], nil, args[3])) + end + end + + local door_terminals = { + [[444,130,472,80,red|354,66,248,368,green]], + [[808,252,712,48,green|824,252,952,56,red|840,252,568,376,blue]], + [[184,2,160,392,green|392,282,144,224,red|360,170,320,408,blue]], + [[620,2,552,304,green|652,2,904,280,red|684,2,984,272,blue]] + } + for args in all(stringToTable(door_terminals[current_mission])) do + create_door_terminal_pair(unpack(args)) + end + + game_ability_menu = ability_menu.new() + game_minigame = minigame.new() +end + +function update_gameplay() + if game_minigame.active then + game_minigame:update() + else + if btn(🅾️) and not game_ability_menu.active then + game_ability_menu:open() + elseif not btn(🅾️) and game_ability_menu.active then + game_ability_menu:close() + end + + if game_ability_menu.active then + game_ability_menu:update() + else + foreach(entities, function(e) e:update() end) + foreach(terminals, function(t) t:update() end) + foreach(barrels, function(b) b:update() end) + + for i = #particles, 1, -1 do + local p = particles[i] + p:update() + if p.lifespan < 0 then del(particles, p) end + end + + cam:update() + player_hud:update() + end + end +end + +function draw_gameplay() + reset_pal(true) + map(0,0,0,0,128,56) + + for group in all({terminals, data_fragments, entities, particles, barrels}) do + foreach(group, function(e) e:draw() end) + end + + foreach(doors, function(d) d:draw() end) + + if player.health > 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + + for t in all(terminals) do + if t.interactive and not game_minigame.active then + t.panel:draw() + end + end + + -- check mission status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select") + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + add(particles, particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9)) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(28) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + obj:take_damage(self.explosion_damage * (1 - dist/self.explosion_radius)) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + add(particles, particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 10 + rnd(5), 1, 6)) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = "15,100,rIFLE bURST,rifle_burst,20|30,200,mACHINE gUN,machine_gun,25|45,50,mISSILE hAIL,missile_hail,50|60,25,pLASMA cANNON,plasma_cannon,75" + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + action = new_entity[a[4]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[5] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,400,400,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + -- Common updates for all entities + self:apply_physics() + self.targeting:update() + + -- Update cooldowns and poison in one pass + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer = (self.poison_timer + 1) % 6 + if self.poison_timer == 5 then self:take_damage(1) end + else + self.poison_timer = 0 + end + + -- Handle plasma charging + if self.plasma_timer and self.plasma_timer > 0 then + self.plasma_timer -= 1 + if self.plasma_timer == 0 then + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + add(particles, particle:new(sx, sy, dx * 5, dy * 5, 120, 4, 12, "plasma", self)) + sfx(10) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + self.plasma_timer = nil + end + end + + -- Entity-specific behavior + if self.subclass == "player" then + self.state = "idle" -- Player is always in idle state for terminal interaction + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive and not t.tutorial_msg then -- Skip tutorials + game_minigame:start(t) + return + end + end + self:activate_ability(self.selected_ability) + end + + -- Fragment collection + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 25, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end + else + -- Enemy AI consolidated + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" -- Set attack state + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" -- Set alert state + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + self.state = "alert" -- Set alert state when moving to last known position + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + self.vx, self.vy = dist > 1 and dx / dist or 0, dist > 1 and dy / dist or 0 + else + self.state = "idle" -- Set idle state + -- Idle behavior + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer, angle, speed = 30, rnd(), rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.last_seen_player_pos.x, self.last_seen_player_pos.y = nil, nil + end + end +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) + sfx(30) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + if dist_trig(player.x - self.x, player.y - self.y) <= self.attack_range and self.targeting:has_line_of_sight(player) then + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + ability.current_cooldown = self.subclass == "player" and ability.cooldown or ability.cooldown * 3 + if self.subclass == "player" then ability.remaining_uses -= 1 end + else + -- Auto-switch to next weapon with ammo + for i = 1, #self.abilities do + local next_index = (index + i - 1) % #self.abilities + 1 + if self.abilities[next_index].remaining_uses > 0 then + self.selected_ability = next_index + game_ability_menu.last_selected_ability = next_index + self:activate_ability(next_index) + return + end + end + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + local decel = self.subclass == "player" and 5.5 or 1 + self.vx -= dx * decel + self.vy -= dy * decel + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local bullet = particle:new( + self.x + self.width/2 + cos(angle) * self.width/2, + self.y + self.height/2 + sin(angle) * self.height/2, + cos(angle) * 4, + sin(angle) * 4, + 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + + local bullet = particle:new( + self.x + self.width/2 + cos(angle) * self.width/2, + self.y + self.height/2 + sin(angle) * self.height/2, + cos(angle) * 6, + sin(angle) * 6, + 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_timer = 20 +end + + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx, dy = target.x - self.x, target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + + if ix == 0 and iy == 0 then + self.target_x += (self.x - self.target_x) * 0.3 + self.target_y += (self.y - self.target_y) * 0.3 + return + end + + self.target_x += ix * 6 + self.target_y += iy * 6 + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > 32 then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * 32 + self.target_y = self.y + sin(angle) * 32 + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.01 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.01 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + local w, h = self.width - 1, self.height - 1 + return check_tile_flag(x, y) or check_tile_flag(x + w, y) or check_tile_flag(x, y + h) or check_tile_flag(x + w, y + h) +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.plasma_timer and self.plasma_timer > 0 then + circ(x + w/2, y + h/2, 32 * (self.plasma_timer / 20), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + -- printh(normalized_dist) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door, tutorial_msg) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} + + local t = setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false, + tutorial_msg = tutorial_msg + }, {__index = terminal}) + + -- Create panel at fixed screen position (will be adjusted with camera in draw) + local msg = tutorial_msg or "❎ INTERACT" + local panel_width = max(40, #msg * 4 + 12) -- Adjust width based on message length + t.panel = textpanel.new(64 - panel_width/2, 114, 10, panel_width, msg, true) + t.panel.selected = true + + return t +end + +function terminal:update() + if self.completed then + self.interactive = false + return + end + + local dist = dist_trig(player.x-self.x, player.y-self.y) + + if self.tutorial_msg then + -- Tutorial: simply show when close + self.interactive = dist < 42 + else + -- Original terminal logic + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + end + + -- Update panel animation when interactive + if self.interactive then + self.panel:update() + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + sfx(15) + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + else + sfx(29) + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, + +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y, health_percent = cam.x, cam.y, player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent, bar_width = entity.health / entity.max_health, flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + if ending_sequence_timer == 1000 then + music(7) + elseif ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + ending_sequence_timer -= 1 + end +end + +function draw_bar(x, y, w, h, bg, fill, pct) + rectfill(x, y, x + w - 1, y + h - 1, bg) + if pct > 0 then rectfill(x, y, x + max(1, flr(w * pct)) - 1, y + h - 1, fill) end + rect(x, y, x + w - 1, y + h - 1, 0) +end + + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 +a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b +0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 +14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f +d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 +6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 +7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c +3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd +1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 +2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 +821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf +6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf +a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b +6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 +2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 +8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 +2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 +7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 +73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 +55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf +6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 +081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 +0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b +8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 +e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 +5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 +c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 +41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 +462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 +2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b +188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 +e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 +260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a +f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 +d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 +d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 +aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 +1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 +1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 +e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c +df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d +e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 +76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 +40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 +64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a +174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 +7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 +d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc +4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a +4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 +01e3a0f160f038782c301e120f0a8f85c703e3a1f1e0f07848f01a4f388300019f6cee918b0e0109d08240c2306a40f6108a18c1bb783c83f14b20e170ff30ff +28641c4dd93c831441b1a4c07c9055382814cf1a4315b98740c1b1e411961097581d04d316c6054a8ff14af22e70fee03c28210cce7c0dcd83f1ca20e070efe3 +18ff0cff0ef705cb8886c954a4717ed09338cf0c5a12f71bf7854cc345e41358205ab8ff1c151a9d11f384fdc927637172c12ec81f6cff3e2607ff8701c1d52a +243b6174b8cec47806d20bf38f904369a8a33372f5b9f8ec700efc3f1a9f8dcf0fe731f3e3f1f9e8080000000000000000000000000000000000000000000000 +__label__ +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00002000000020000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000001011000000000000000000000000000000000 +00002000200002000222222002222220001001100222222002222220022222200222222002222220000000000011001002222220022222200000000000000000 +00000000000000000200000002000000001101000200000002000000020000000200000002000000001111110101011002000000020000000000000000000000 +00000000000000000200000002000000001001100200000002000000020000000200000002000000001010101111001002000000020000000000000000000000 +00000002000000000200000002000000001101000200000002000000020000000200000002000000001100000000011002000000020000000000000000000000 +00000000000000000200000002000000001001100200000002000000020000000200000002000000001001111010101002000000020000000000000000000000 +00000000000000000200000002000000001101000200000002000000020000000200000002000000001101011111111002000000020000000000000000000000 +00000000000000000000000000000000001001100000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 +00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 +00000000020000000200000000000000001101011111111000000000000000000000000000000000001101000000000000000000020000000200000000000000 +00000000020000000200000000000000001001111010101000000000000000000000000000000000001001100000000000000000020000000200000000000000 +00000000020000000200000000000000001100000000011000000000000000000000000000000000001101000000000000000000020000000200000000000000 +00000000020000000200000000000000001010101111001000000000000000000000000000000000001001100000000000000000020000000200000000000000 +00000000020000000200000000000000001111110101011000000000000000000000000000000000001101000000000000000000020000000200000000000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000000000000000000 +02222220022222200000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000222222002222220 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000010011002222220022232200222222002222220001100100000000000000000000000000000000002222220 +01010101010101033333333033333333033333333033333333033333333033000233300002000000010101100000000000000000000000000000000002000000 +11111111111111133333333033333333033333333033333333033333333033300333000002000000111100100000000000000000000000000000000002000000 +00000000000000033000011033000033000000033011033002000000020003333330000002000000000001100000000000000000000000000000000002000000 +10101010101010133111001033000033033333333010133002033333333000333300000002000000101010100000000000000000000000000000000002000000 +11111111111111133101011033000033033333333011133102033333333000333300000002000000111111100000000000000000000000000000000002000000 +00000000000000033011001033000033033033300000033000000000000003333330000000000000000000000000000000000000000000000000000000000000 +00000000000000033333333033333333033003330000033000033333333033300333000000000000000000000000000000000000000000000000000000000000 +02222220000000033333333033333333033000333222233000033333333333000033000000000000022222200000000000000000000000000000000002222220 +02000000000000000011010000000000000000033200000000000000000000000003000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000003200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200033333333033000333033333333033333333033333333033033333333033333333000000000002000000 +00000000000000000010011000000000000000000000033333333033003330033333333033333333033333333033033333333033333333000000000000000000 +00000000000000000011010000000000000000000011033000033033033300000000000000000033000000033033033000033000000000000000000000000000 +02222220000000000010011000000000022222200010033000033033333000033333333033333333033333333233233000033033333333000000000002222220 +02000000000000000011010000000000020000000011033000033033330000033333333033333333033333333233033000333033333333000000000002000000 +02000000000000000010011000000000020000000010033000033033300000000000000033033300033033300233033003330000000000000000000002000000 +02000000000000000011010000000000020000000011033333333033000000033333333033003330033003330233033033300033333333000000000002000000 +02000000000000000010011000000000020000000010033333333030000000033333333033000333033000333233033333000033333333000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000033000000033200033330000000000000000000000002000000 +00000000000000000010011000000000000000000010011000000000000000000000000000000003000000003000033300000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000033000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222232000000000000000000000000000000000 +02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 +02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 +02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 +02000000000000000010011000000000020000000010101010101010201100000000000000000000001001110200000010101010101010101010101010101010 +02000000000000000011010000000000020000000011111111111111201000011000010000000000001101010200000011111111111111111111111111111111 +00000000000000000010011000000000000000000000000000000000201000100000010000000000001001100000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000201000100100000000000000000101100000000000000000000000000000000000000000 +02222220000000000010011000000000022222200000000000000000201000011000010000000000001100100222222000000000000000000000000000000000 +02000000001111110011010101010101020000000000000000000000201000000000010001010101010101100200000000000000000000000000000000000000 +02000000001010100010011111111111020000000000000000000000200000000000110011111111111100100200000000000000000000000000000000000000 +02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 +02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 +02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 +00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 +00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 +01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 +02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 +02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 +02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 +02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 +02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 +02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 +02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 +02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000020000200 +00000000000000000011111101010101010101010101011000000000000000000000000000000000000000000000000000110101111111100000000000000000 +00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 +00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 +00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 +00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140aa50800113096502814604c4072a40516022d020280aa9409905bc0012e0767039381e94100e03c4078411400c80e000007600c607802a81cc860301c0c060290a8002a29c020e016500c680059ac92473640990ed011 +c8119c0554d64982da1c40105d280190a8af01c4384134e0be701e580e141fd0780d068a85b0902388000e710397c086f379f8de6f379fcde1b158100900c2806a40098db00a3021bcf6602491cc06e27403d6006b0113024068a04080437980b307c28301800583d802981c867e021e0ff0592369f8de7f3f1bcc04a8b39427 +0e1fe910530a8587b10a288799f8f07a3d1e4f4783c9e8f41b0e4218a00e5001380a140208db00c282f1010c05923980de7b807ac04ccf707f4379ec08048169c1fccd207039de0fe81d00419d4fe7982891e09a5e3d1e8fc7b3798623c477068345b17f587a901c4a268c51c63ce0e24788789c1dd60e0710c38402c1fd21da +6692b98204065d89414144a128507f484f140e14de602d0341a3d8ed11c64cc50ff4299bcf10d323c1b4f0783c9e4fe7e30467963f091844150347a2a8979484c8fa7e8931c5e12306500238061010de0b0641bd09d105482f11a20fe90a4437934dc6e3741fd8df1002379ea2fe44986a1c780e0fea58309fa194c7a059e4f4 +7f8bfa41fe243e43c8852c2b88e92457211988c463310e462b232382d241010ba6e30128d061379349d0f033d134dd15320245fd0bb13421841f489a6e301ca61a7306303c9fc8e2002398239066f8e011bc3715e28bf9c9aca0fef38828852cc54e0ec64420100832be30381c884123198cc4422900c6618ed1c6508de7a0d8 +324da86e2f1b4de5d370b85e5e8829c1fd64364303d0c0dc6f2f1bcd7327783fa0020fe517f284ea1e8de798d6912a77e53c039e59c00fe027e50a031140e716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f5936495d24bce6a +8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003 +f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e +755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b309888041231 +10804520170b70b032311888452111216e767f22dc039e17c85c9502d8f10ff22e235dd25228a7726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8 +522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715 +493ee7bf8402c562bc26acc7ea9ba549d68401dd78ee62733f7c3aa52d30610125025ec1699fd87f29df9e2fca87af56cc83fa567da63f06830d278eb0ea1b0e5f78c3b853286c752c4e2fcd6a95020121bb15e1ca1f9ca78f3b275c13b4679b09258ba09dff1a01f14a38c2f16ad48e9a9c3811c52ce00957c3a64f6af64840 +00010e87e525f491225e48e8feb30fb9ef94c76665ed4a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e2e0624d0541977f3dd7becec81a2dd739c00da81666ac771661ce64a59fe6d841ee2ce050e07154b7cf17c56324cdea603ff7ab8522b7031300e96b +02d6f1f0d5b872864291d8a6523b7274b84a46cdbe1194af07fe83dc421180e2ac96a6b6ccdc5ec13a51fda44ff6e74e029efe4f1aad7b42daf149fca0ff043e141599ccfc6f36eca480804215068a1fed9fe325f46132df84cd9f9e2f9ef4e747f399caede66e8cf8183bb535009a4feba215e9c91a0df9542dfb91e0f46de1 +718100e2c06c1fdb4c659fc2e95a7471b9fed82d4186ae8f4247a2e2cafa61e34179d56d389e24e36d87b6d2def1c3fe4fe7ae46101210645d371b8f67a37efe8b708a00039df7fc55db5bd6563fd717cf8637c72dc59528ce7ad541806c606fa3f3f40937bc5000a0e99cfc7836f3d4ba709d3923f14b8dc527f5cff2596d31 +58dd003d4b9dffce2a714ffba1ca716933345de74e2e91ef24ed07ffdd1a8f0a67ff0ca5f693bbe47f356174a5febeda2dbe272ee7a5ad7ffc0268b8f4cf529a3a731dc1d77de43037c1f9f4be7c5e2eef144068e8422310ccc462190b9607cb62219d67fed7936bc5213bb589dad8239acc1000a36188c700f7359bcf47a801 +1400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8 +de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75 +c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a57 +69084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d298767505343 +5834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b14 +68e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8 +b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870f +e602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3 +a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c2914921809 +32922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74 +c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4a +c2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881 +c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + diff --git a/compression_utilities.ipynb b/compression_utilities.ipynb index fb661ef..eaba5c0 100644 --- a/compression_utilities.ipynb +++ b/compression_utilities.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 12, + "execution_count": 1, "metadata": {}, "outputs": [ { @@ -251,20 +251,20 @@ }, { "cell_type": "code", - "execution_count": 15, + "execution_count": null, "metadata": {}, "outputs": [], "source": [ "# Example usage\n", "map_1_2_top = \"\"\"\n", - "07072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107710756765654505051765107075450505050505551565676505055505050504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c\n", - "072a070707070707070700001d07070707071d070707070707646060706060525653607060606060706060527660706060706060606060606060606060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49\n", - "2a07070707070707070700003c0d0d2a2d072a0771000771075f046f6f7e6f6f6f7f6f1b2b0404046f6f6f7e6f6f6f6f6f6f6f6f6f6f6d6f6f6f6f046f7b604947606e4e6f6f6d6f6f6f6d6f6f6f6d6f60494760046f046f6f6f6f6f04046f605907077107000000002c3d0000000007437e04787f6f6f7e6d7e6f7f7e6f604a\n", - "2a07077107070707070700000707070715163d070700070707077e787a7a797a78797a7a1b1c6f6f7f797a7a7a7a7a7a787a7a7a7a7a6d7a7a7a6f040404605947606f7b7a7a6d7a7a7a6d7a7a7a7b6f604947606f7b04047a7a7a7a04046f606907077707000000001d007100000007077f79797a7a784d5e7a7a7e7b6f6149\n", + "07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505050504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c\n", + "072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060606060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49\n", + "2a07070700000007070700003c0d0d2a2d072a0771000771075f046f6f7e6f6f6f7f6f1b2b0404046f6f6f7e6f6f6f6f6f6f6f6f6f6f6d6f6f6f6f046f7b604947606e4e6f6f6d6f6f6f6d6f6f6f6d6f60494760046f046f6f6f6f6f04046f605907077107000000002c3d0000000007437e04787f6f6f7e6d7e6f7f7e6f604a\n", + "2a07077100000007070700000707070715163d070700070707077e787a7a797a78797a7a1b1c6f6f7f797a7a7a7a7a7a787a7a7a7a7a6d7a7a7a6f040404605947606f7b7a7a6d7a7a7a6d7a7a7a7b6f604947606f7b04047a7a7a7a04046f606907077707000000001d007100000007077f79797a7a784d5e7a7a7e7b6f6149\n", "2a07770d0d2d07070707000007710707252607070700070707077f797a786f6f6f6f7a7a7a7a7a7a7a7a7a6f6f6f7f6f7a787a7a7a7a6d7a7a7a6f6f6d6f606957606f7a7a7a5d6f6f6f5e7a7a7a7a6f604947606f6d7a7a6f6f6f7a7a7a6f605a0d0d3d07000071072a2a0000000007536f787a7879786d787879797f7e6049\n", "2a070707071d0707070700000707072a0d3d2a7107000707077d7e7f6f6f7e04046f6f6f6f7f6f6f7f6f6f7e0404046f0b0c6f7a7a7a5d4e7a7a7a7a6d6f605a68606f7a7a7a6f7a7a7a7a6f4d6e6e6e604947606f6d7a6f047a7a6f7a046f604907710707000000072a2a0000000058617e7a7a7a7a6f6f6f7a0b797a7f604a\n", - "2a070707071d07070707000007072c3d0707070707007107077460606060607060604266464666437060606060606070606b6f047a7a6f6f6f6f7a0b5d6e604a68616f4d4e6f6d7a7a7a4d6e5e7a7a04604947606f5d6f7a7a307a046f4d6e60725050515654504c07071d0000710047607f797a7a6f7a6d7a6f1b7a786f6149\n", - "2a070771071d07070707000007071d0707070707070007070707664646664440404107070771070744414666664445406361047a7a7a6f7a7a6f7a1b0c6f604947606e5e6d6f5d6e2e2f5e7a6f7a7a04604947606f7a6f04307a306e6f5e6f6b6060606060606b4907071d0000000047606f787a6f7a7a307a7a6f797a6f604a\n", + "2a070707071d07070707000000002a3c2d070707070071072a7460606060607060604266464666437060606060606070606b6f047a7a6f6f6f6f7a0b5d6e604a68616f4d4e6f6d7a7a7a4d6e5e7a7a04604947606f5d6f7a7a307a046f4d6e60725050515654504c07071d0000710047607f797a7a6f7a6d7a6f1b7a786f6149\n", + "2a070771071d070707070000002a072a1d0707070700072a0707664646664440404107070771070744414666664445406361047a7a7a6f7a7a6f7a1b0c6f604947606e5e6d6f5d6e2e2f5e7a6f7a7a04604947606f7a6f04307a306e6f5e6f6b6060606060606b4907071d0000000047606f787a6f7a7a307a7a6f797a6f604a\n", "4b505050517651505050505050505050505050505050505050505050505050505050505050505050505050507656545073606f7a7a7a6f7a7a6f6f6f6f6f605947606f7a5d6f7a7a3e3f7a7a6f7a7a6f604947606f7a6f6e6e3004046f7a0404046f6f6f6f6f6049072a2a0000070748617e797a6f7a3079304e7a79786f6049\n", "476c60606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a\n", "57606f6f6d6f6f7a0b2b046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f6d6f6f1b1c6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f6d7a307a4d606a47606f7a7a7a6f6e5e7a7a6f7a6d7a6f604968606f7a7a046f6d6f7a7a7a6f6c60606b047a6f60493d07077100000047607f787a7a6f7a5d4e7e7a797a6f6059\n", @@ -452,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 16, + "execution_count": 11, "metadata": {}, "outputs": [ { From 21a99d4ba71e6ef8065c803b5ea9c4c88fb18bfd Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 15:16:55 +0100 Subject: [PATCH 03/16] got map working --- beta_v0.2.2.p8 | 10 ++--- beta_v0.2.2_copy.p8 | 83 +++++++++++++++++++------------------ compression_utilities.ipynb | 80 +++++++++++++++++------------------ 3 files changed, 87 insertions(+), 86 deletions(-) diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index 1004df5..a1de7d6 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -2254,8 +2254,8 @@ __gff__ 0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __map__ -07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505050504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c -072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060606060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49 +07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505055504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c +072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060707060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49 2a07070700000007070700003c0d0d2a2d072a0771000771075f046f6f7e6f6f6f7f6f1b2b0404046f6f6f7e6f6f6f6f6f6f6f6f6f6f6d6f6f6f6f046f7b604947606e4e6f6f6d6f6f6f6d6f6f6f6d6f60494760046f046f6f6f6f6f04046f605907077107000000002c3d0000000007437e04787f6f6f7e6d7e6f7f7e6f604a 2a07077100000007070700000707070715163d070700070707077e787a7a797a78797a7a1b1c6f6f7f797a7a7a7a7a7a787a7a7a7a7a6d7a7a7a6f040404605947606f7b7a7a6d7a7a7a6d7a7a7a7b6f604947606f7b04047a7a7a7a04046f606907077707000000001d007100000007077f79797a7a784d5e7a7a7e7b6f6149 2a07770d0d2d07070707000007710707252607070700070707077f797a786f6f6f6f7a7a7a7a7a7a7a7a7a6f6f6f7f6f7a787a7a7a7a6d7a7a7a6f6f6d6f606957606f7a7a7a5d6f6f6f5e7a7a7a7a6f604947606f6d7a7a6f6f6f7a7a7a6f605a0d0d3d07000071072a2a0000000007536f787a7879786d787879797f7e6049 @@ -2263,11 +2263,11 @@ __map__ 2a070707071d07070707000000002a3c2d070707070071072a7460606060607060604266464666437060606060606070606b6f047a7a6f6f6f6f7a0b5d6e604a68616f4d4e6f6d7a7a7a4d6e5e7a7a04604947606f5d6f7a7a307a046f4d6e60725050515654504c07071d0000710047607f797a7a6f7a6d7a6f1b7a786f6149 2a070771071d070707070000002a072a1d0707070700072a0707664646664440404107070771070744414666664445406361047a7a7a6f7a7a6f7a1b0c6f604947606e5e6d6f5d6e2e2f5e7a6f7a7a04604947606f7a6f04307a306e6f5e6f6b6060606060606b4907071d0000000047606f787a6f7a7a307a7a6f797a6f604a 4b505050517651505050505050505050505050505050505050505050505050505050505050505050505050507656545073606f7a7a7a6f7a7a6f6f6f6f6f605947606f7a5d6f7a7a3e3f7a7a6f7a7a6f604947606f7a6f6e6e3004046f7a0404046f6f6f6f6f6049072a2a0000070748617e797a6f7a3079304e7a79786f6049 -476c60606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a +476c60606060606060606060606060606060606060606060606060706060606060606070606060606060706060606070606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a 57606f6f6d6f6f7a0b2b046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f6d6f6f1b1c6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f6d7a307a4d606a47606f7a7a7a6f6e5e7a7a6f7a6d7a6f604968606f7a7a046f6d6f7a7a7a6f6c60606b047a6f60493d07077100000047607f787a7a6f7a5d4e7e7a797a6f6059 68606f7b5e7a7a7a0b2b1c047a7a7a04047a7a0b0c7a7a7a7a7a7a4d5e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f5d307a305e605a47606f7a7a4d5e6f6f6f6f7a4d5e7a6f604968616f7a7a7a7a6d7a7a7a7a6f60626360047a6f60725107070707544c47606f797a7a7a6f6f6f787a797a6f6069 68616f7a7a7a7a0b2b1c7a7a7a7a6f04046f0b2b0c6f6f7a7a6f6f6d6f6f6f6f6f6f6f0b0c6f6f7a7a6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f7a7a307a7a604947606f6f7a6d7a7a7a7a7a7a6d7a7a6f604947606f6f7a4d6e5e7a7a046f6f604947606f7a6f6b6060520707536b49486079797a7a7a7a7a6d7a7a7a7978605a -68606f7a7a7a7a1b1c7a7a7a7a6f6c6060606060606b6f7a7a6f6c606060606060606060606b6f7a7a6f6c6060606060606b6f6f7a6f6f04046f6e5e7e7e604a47606f6f6f6d6f6f6f6f6f6f6d6f6f6f604947606f6f6f6d6f6f6f6f6f6f6f604947606f7a6f6f04047e6f7f6f605947607878797a78797a6d787a787979614a +68606f7a7a7a7a1b1c7a7a7a7a6f6c6060606060606b6f7a7a6f6c606060606070606060606b6f7a7a6f6c6060606060606b6f6f7a6f6f04046f6e5e7e7e604a47606f6f6f6d6f6f6f6f6f6f6d6f6f6f604947606f6f6f6d6f6f6f6f6f6f6f604947606f7a6f6f04047e6f7f6f605947607878797a78797a6d787a787979614a 58606f7b4e7a7a7a7a7a7a047a6f60624040404063606f7a7a6f6062404040404040404063606f7a7a6f606240404040636b6b6f7a6f6c606060606060606c49476b60606060606060606060606b6f6f6049476b60606060606060606060606c4947606f7a7a7a04047a787b6f606948606f78046f6f6f7e6d7f7e6f6f7f604a 48606f6f6d6f6f6f6f6f6f6f046f60494b50505073606f7a7a6f60725050504c4b50505073606f7a7a6f60725050504c5b63606f7a0b6162404146444045405c5b404040404646444542444063606f6f60495b404646444542444040404040405c47606f7a7a7a7a7a7a7a7a7f605a476b6070606060606b6f6f6c6060606c49 476b6060606060606060606060606c49476c6060606c6f7a7a6f6b6060606b49476c6060606c6f7a7a6f6b6060606b494b73616f7a1b6072505051565450504c00000007070707070707545073606f6f607250505050505050505050505050505073606e6e6e6e4e7a7a04046f60495b4040404040456360046f60624040405c @@ -2276,7 +2276,7 @@ __map__ 476c6060606060606060606060606060606c6f6f6f6f7a7a7a7a7a6f0b0b604948616f7a7a7a7a7a7a7a7a7a5d6e604957606f7a7a7a7a7a7a7a1b2b2b0c6059071d0007077a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f0404046f7a7a7a7a04307a7a7a7a7a7a6d7a7a7a7a6f6f6f6f6f0404046f6f6f6f6f6f6f6f6f6f6149 47606f6f6f6f6f04046f6d6f6f6f6f6f6f6f6f7a7a7a7a7a7a7a7a0b2b2b604947606f7b4e7a7a7a7a7a7a047a6f604968600c7a7a7a7a7a7a7a7a1b2b6f6049073c2d07077a7a7a7a7a7a047a047a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a30047a7a7a7a7a045d4e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b6f614a 47606f7b7a7a7a7a7a7a6d7a7a6f6f6f6f6f6f6f6f6f6f6f6f6f6f0b2b2b604957606f6f6d6f6f6f6f6f6f6f046f604968612b0c6f6f6f6f6f6f6f7a1b1c604907711d07536f6f6f6f6f0404046f6f6f6f6f6f6f6f0b0c6f6f6f6f6f6f6f6f306f7a7a6f6f6f04046d6f6f6f6f6f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604a -47606e6e6e6e6f6f6f6f5e7a7a6f6c6060606060606060606060606060606c49586b6060607060606060606060606c49586b606060606060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49 +47606e6e6e6e6f6f6f6f5e7a7a6f6c6060606060606060606060606060606c49586b6060607060606060706060606c49586b606060706060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49 48606f7a7a307a7a7a7a307a7a6f60624040404046664440404040404040405c5b40406644404040404040404040405c5b4040404040404063600c7a7a6f605907001d0707664644404040404040404040404040404040406361046f605947606f7a7a6f6062404040464644454244404040404040404040410707710044405c 48606f7a6f6d7a7a7a7a7a6f7a6f60494b505050505176515050505050505050505050505050504c4b5050505051077107567656545050507360040c7a6f606a07071d07070707073c0d0d0d2d000000000000000000000073606f6f6a004760047a7a6f604900072a2a0000000000000000710000000000000000000000072a 48616f7a6f5d6e6f6f7a4d6e6e6e6049476c60606060606060606060606060606060606060606b49476c6060606052565360706060606060606c1c7a7a6f606907001d0707070707070707071d6452765352765656765360606c046f60655760047a7a6f604900072a2a0000000071000000000000000071000000000000072a diff --git a/beta_v0.2.2_copy.p8 b/beta_v0.2.2_copy.p8 index 18d461b..d79e3a1 100644 --- a/beta_v0.2.2_copy.p8 +++ b/beta_v0.2.2_copy.p8 @@ -424,12 +424,13 @@ function _init() } mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 - + + v = 2040 -- Load compressed map from map data map_data = { -- 4 Levels - pack(peek(0x2000, 2005)), - pack(peek(0x2000 + 2005, 1585)), + pack(peek(0x2000, v)), + pack(peek(0x2000 + v, 1585)), pack(peek(0x1000, 1788)), pack(peek(0x1000 + 1788, 1402)), -- Logo @@ -1477,15 +1478,15 @@ function entity:apply_physics() local new_x, new_y = self.x + self.vx, self.y + self.vy -- Check tile collision - if self:check_tile_collision(new_x, new_y) then - if not self:check_tile_collision(new_x, self.y) then - new_y = self.y - elseif not self:check_tile_collision(self.x, new_y) then - new_x = self.x - else - new_x, new_y = self.x, self.y - end - end + -- if self:check_tile_collision(new_x, new_y) then + -- if not self:check_tile_collision(new_x, self.y) then + -- new_y = self.y + -- elseif not self:check_tile_collision(self.x, new_y) then + -- new_x = self.x + -- else + -- new_x, new_y = self.x, self.y + -- end + -- end -- Check laser door collision for door in all(doors) do @@ -2260,35 +2261,35 @@ __gff__ 0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __map__ -0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140aa50800113096502814604c4072a40516022d020280aa9409905bc0012e0767039381e94100e03c4078411400c80e000007600c607802a81cc860301c0c060290a8002a29c020e016500c680059ac92473640990ed011 -c8119c0554d64982da1c40105d280190a8af01c4384134e0be701e580e141fd0780d068a85b0902388000e710397c086f379f8de6f379fcde1b158100900c2806a40098db00a3021bcf6602491cc06e27403d6006b0113024068a04080437980b307c28301800583d802981c867e021e0ff0592369f8de7f3f1bcc04a8b39427 -0e1fe910530a8587b10a288799f8f07a3d1e4f4783c9e8f41b0e4218a00e5001380a140208db00c282f1010c05923980de7b807ac04ccf707f4379ec08048169c1fccd207039de0fe81d00419d4fe7982891e09a5e3d1e8fc7b3798623c477068345b17f587a901c4a268c51c63ce0e24788789c1dd60e0710c38402c1fd21da -6692b98204065d89414144a128507f484f140e14de602d0341a3d8ed11c64cc50ff4299bcf10d323c1b4f0783c9e4fe7e30467963f091844150347a2a8979484c8fa7e8931c5e12306500238061010de0b0641bd09d105482f11a20fe90a4437934dc6e3741fd8df1002379ea2fe44986a1c780e0fea58309fa194c7a059e4f4 -7f8bfa41fe243e43c8852c2b88e92457211988c463310e462b232382d241010ba6e30128d061379349d0f033d134dd15320245fd0bb13421841f489a6e301ca61a7306303c9fc8e2002398239066f8e011bc3715e28bf9c9aca0fef38828852cc54e0ec64420100832be30381c884123198cc4422900c6618ed1c6508de7a0d8 -324da86e2f1b4de5d370b85e5e8829c1fd64364303d0c0dc6f2f1bcd7327783fa0020fe517f284ea1e8de798d6912a77e53c039e59c00fe027e50a031140e716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f5936495d24bce6a -8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003 -f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e -755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b309888041231 -10804520170b70b032311888452111216e767f22dc039e17c85c9502d8f10ff22e235dd25228a7726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8 -522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715 -493ee7bf8402c562bc26acc7ea9ba549d68401dd78ee62733f7c3aa52d30610125025ec1699fd87f29df9e2fca87af56cc83fa567da63f06830d278eb0ea1b0e5f78c3b853286c752c4e2fcd6a95020121bb15e1ca1f9ca78f3b275c13b4679b09258ba09dff1a01f14a38c2f16ad48e9a9c3811c52ce00957c3a64f6af64840 -00010e87e525f491225e48e8feb30fb9ef94c76665ed4a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e2e0624d0541977f3dd7becec81a2dd739c00da81666ac771661ce64a59fe6d841ee2ce050e07154b7cf17c56324cdea603ff7ab8522b7031300e96b -02d6f1f0d5b872864291d8a6523b7274b84a46cdbe1194af07fe83dc421180e2ac96a6b6ccdc5ec13a51fda44ff6e74e029efe4f1aad7b42daf149fca0ff043e141599ccfc6f36eca480804215068a1fed9fe325f46132df84cd9f9e2f9ef4e747f399caede66e8cf8183bb535009a4feba215e9c91a0df9542dfb91e0f46de1 -718100e2c06c1fdb4c659fc2e95a7471b9fed82d4186ae8f4247a2e2cafa61e34179d56d389e24e36d87b6d2def1c3fe4fe7ae46101210645d371b8f67a37efe8b708a00039df7fc55db5bd6563fd717cf8637c72dc59528ce7ad541806c606fa3f3f40937bc5000a0e99cfc7836f3d4ba709d3923f14b8dc527f5cff2596d31 -58dd003d4b9dffce2a714ffba1ca716933345de74e2e91ef24ed07ffdd1a8f0a67ff0ca5f693bbe47f356174a5febeda2dbe272ee7a5ad7ffc0268b8f4cf529a3a731dc1d77de43037c1f9f4be7c5e2eef144068e8422310ccc462190b9607cb62219d67fed7936bc5213bb589dad8239acc1000a36188c700f7359bcf47a801 -1400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8 -de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75 -c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a57 -69084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d298767505343 -5834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b14 -68e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8 -b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870f -e602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3 -a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c2914921809 -32922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74 -c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4a -c2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881 -c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140ab00232612ca05028c09880e5480a2942022b020280aa9409905bc0012e0767039281eac0b480f0682801a01c00000ec018c0f005503990c06038180c0521500054538041c02ca018d00073840788d649239b204c8768 +0b3c045203c90154359260b6871004174a00642a2bc07120747022480f2c070a0fe83c06834542d84811c400073881cbe04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e14180c002c1ec014c0e433f010f07f82c91b4fc6f3f9f8de6025459 +ca13870ff488298542c3d8851443ccfc783d1e8f27a3c1e4f47a0d87210c500728009c050a01046d80614178808602c91cc06f3dc03d602667b83fa1bcf6040240b4e0fe6690381cef07f40e8020cea7f3cc1448f04d2f1e8f47e3d9bcc311e23b8341a2d8bfac3d480e25134628e31e707123c43c4e0eeb07038861c20160fe +90ed33495cc102032ec4a0a0a25094283fa4278a070a6f301681a0d1ec7688e3266287fa14cde7886991e0da783c1e4f27f3f18233cb1f848c220a81a3d1544bca42647d3f4498e2f0918328011c0308086f058320de84e882a41788d107f485221bc9a6e371ba0fec6f88011bcf517f224c350e3c0707f52c184fd0ca63d02c +f27a3fc5fd20ff121f21e4429615c474922b908cc46231988723159191c169208085d371809468309bc9a4e87819e89a6e8a990122fe85d89a10c20fa44d37180e530d3983181e4fe4710011cc11c8337c7008de1b8af145fce4d6507f79c414429662a7076322100804195f181c0e4420918cc6622114806330c768e32846f3 +d06c1926d437178da6f2e9b85c2f2f4414e0feb21b2181e8606e37978de6b98abc1fd00107f28bf942750f46f3cc6b48953bf29e01cee4e007f013f2850188a0738b7bcc3124fe91fd23d1e87c3f84aac1fd8dc6e184ccca502530958c3147fcc0e0724184fd07623d0c0f230271e8f2789969ceb56009f3c16803dcecd0e060 +36495d24bce6a8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160 +c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be75 +c68807e4dfca5bc555a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c2853b980d84923cd85a136f6112808dc080aa40959023c56708d2489bf9592d0db5fd237d1fca02051688a571ce3ce6c691b32ba6b403f0b70 +9c305984c44020918884022900b85b85819188c4422908890b73b3f916e01cf0be42e4a816c7887f91711ae2a2f16b485ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e +9ae6770001cc8522b158a653c1691b0df01988c155b04ee60778d23962958e707f38afc53f1aa0c90192a5c1db4ccdd07f38d815c01c0975d693f9dff48578162301270ac40721d08f286494438ab414e36645730a96c56c42bae8c500b05c1fca79ab7fc4b3795375701481b158ac1985e283f901e84ff81b4bdc2581ca0ff5 +4ddcc66998665ab8aa49f73dfc20162b15e135663f54dd2a4eb4200eebc7731399fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09da33cd8492c5d04ecf4d00fca51a6178b56a474d4e1dab5e81720 +4abe1d2e6add1a42000008743f292fa48912f24747f5987dcf7ca63b332f6a51ad894b4bc7b35333502ca001d8272bceb5355a82974e42c2b11c40044201725be78be4ca21d1fdb021f3278e07170312682a0cbbf9eebdf67640d16eb9ce006d40b33563b8b30e73252cff36c20f71670287038aa5be78be2b19266f5301ffbd +5c2915b818957a4b5816b78f86adc39432148ec53291db93a5c252366df08ca5783ff41ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06f +caa16fdc8f07a36f0b8c0807160360feda632cfe174ad32b53cff3c16a0c35747a123d171657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c +6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c0239acc1000a36188 +c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c +21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c +8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d +291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4 +f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513 +bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3 +dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573 +051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72 +d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab69 +52540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50 +771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e20734 +1fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c4884 +1334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __sfx__ 151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 diff --git a/compression_utilities.ipynb b/compression_utilities.ipynb index eaba5c0..4878778 100644 --- a/compression_utilities.ipynb +++ b/compression_utilities.ipynb @@ -2,7 +2,7 @@ "cells": [ { "cell_type": "code", - "execution_count": 1, + "execution_count": 28, "metadata": {}, "outputs": [ { @@ -257,8 +257,8 @@ "source": [ "# Example usage\n", "map_1_2_top = \"\"\"\n", - "07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505050504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c\n", - "072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060606060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49\n", + "07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505055504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c\n", + "072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060707060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49\n", "2a07070700000007070700003c0d0d2a2d072a0771000771075f046f6f7e6f6f6f7f6f1b2b0404046f6f6f7e6f6f6f6f6f6f6f6f6f6f6d6f6f6f6f046f7b604947606e4e6f6f6d6f6f6f6d6f6f6f6d6f60494760046f046f6f6f6f6f04046f605907077107000000002c3d0000000007437e04787f6f6f7e6d7e6f7f7e6f604a\n", "2a07077100000007070700000707070715163d070700070707077e787a7a797a78797a7a1b1c6f6f7f797a7a7a7a7a7a787a7a7a7a7a6d7a7a7a6f040404605947606f7b7a7a6d7a7a7a6d7a7a7a7b6f604947606f7b04047a7a7a7a04046f606907077707000000001d007100000007077f79797a7a784d5e7a7a7e7b6f6149\n", "2a07770d0d2d07070707000007710707252607070700070707077f797a786f6f6f6f7a7a7a7a7a7a7a7a7a6f6f6f7f6f7a787a7a7a7a6d7a7a7a6f6f6d6f606957606f7a7a7a5d6f6f6f5e7a7a7a7a6f604947606f6d7a7a6f6f6f7a7a7a6f605a0d0d3d07000071072a2a0000000007536f787a7879786d787879797f7e6049\n", @@ -266,11 +266,11 @@ "2a070707071d07070707000000002a3c2d070707070071072a7460606060607060604266464666437060606060606070606b6f047a7a6f6f6f6f7a0b5d6e604a68616f4d4e6f6d7a7a7a4d6e5e7a7a04604947606f5d6f7a7a307a046f4d6e60725050515654504c07071d0000710047607f797a7a6f7a6d7a6f1b7a786f6149\n", "2a070771071d070707070000002a072a1d0707070700072a0707664646664440404107070771070744414666664445406361047a7a7a6f7a7a6f7a1b0c6f604947606e5e6d6f5d6e2e2f5e7a6f7a7a04604947606f7a6f04307a306e6f5e6f6b6060606060606b4907071d0000000047606f787a6f7a7a307a7a6f797a6f604a\n", "4b505050517651505050505050505050505050505050505050505050505050505050505050505050505050507656545073606f7a7a7a6f7a7a6f6f6f6f6f605947606f7a5d6f7a7a3e3f7a7a6f7a7a6f604947606f7a6f6e6e3004046f7a0404046f6f6f6f6f6049072a2a0000070748617e797a6f7a3079304e7a79786f6049\n", - "476c60606060606060606060606060606060606060606060606060606060606060606060606060606060606060606060606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a\n", + "476c60606060606060606060606060606060606060606060606060706060606060606070606060606060706060606070606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a\n", "57606f6f6d6f6f7a0b2b046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f6d6f6f1b1c6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f6d7a307a4d606a47606f7a7a7a6f6e5e7a7a6f7a6d7a6f604968606f7a7a046f6d6f7a7a7a6f6c60606b047a6f60493d07077100000047607f787a7a6f7a5d4e7e7a797a6f6059\n", "68606f7b5e7a7a7a0b2b1c047a7a7a04047a7a0b0c7a7a7a7a7a7a4d5e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f5d307a305e605a47606f7a7a4d5e6f6f6f6f7a4d5e7a6f604968616f7a7a7a7a6d7a7a7a7a6f60626360047a6f60725107070707544c47606f797a7a7a6f6f6f787a797a6f6069\n", "68616f7a7a7a7a0b2b1c7a7a7a7a6f04046f0b2b0c6f6f7a7a6f6f6d6f6f6f6f6f6f6f0b0c6f6f7a7a6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f7a7a307a7a604947606f6f7a6d7a7a7a7a7a7a6d7a7a6f604947606f6f7a4d6e5e7a7a046f6f604947606f7a6f6b6060520707536b49486079797a7a7a7a7a6d7a7a7a7978605a\n", - "68606f7a7a7a7a1b1c7a7a7a7a6f6c6060606060606b6f7a7a6f6c606060606060606060606b6f7a7a6f6c6060606060606b6f6f7a6f6f04046f6e5e7e7e604a47606f6f6f6d6f6f6f6f6f6f6d6f6f6f604947606f6f6f6d6f6f6f6f6f6f6f604947606f7a6f6f04047e6f7f6f605947607878797a78797a6d787a787979614a\n", + "68606f7a7a7a7a1b1c7a7a7a7a6f6c6060606060606b6f7a7a6f6c606060606070606060606b6f7a7a6f6c6060606060606b6f6f7a6f6f04046f6e5e7e7e604a47606f6f6f6d6f6f6f6f6f6f6d6f6f6f604947606f6f6f6d6f6f6f6f6f6f6f604947606f7a6f6f04047e6f7f6f605947607878797a78797a6d787a787979614a\n", "58606f7b4e7a7a7a7a7a7a047a6f60624040404063606f7a7a6f6062404040404040404063606f7a7a6f606240404040636b6b6f7a6f6c606060606060606c49476b60606060606060606060606b6f6f6049476b60606060606060606060606c4947606f7a7a7a04047a787b6f606948606f78046f6f6f7e6d7f7e6f6f7f604a\n", "48606f6f6d6f6f6f6f6f6f6f046f60494b50505073606f7a7a6f60725050504c4b50505073606f7a7a6f60725050504c5b63606f7a0b6162404146444045405c5b404040404646444542444063606f6f60495b404646444542444040404040405c47606f7a7a7a7a7a7a7a7a7f605a476b6070606060606b6f6f6c6060606c49\n", "476b6060606060606060606060606c49476c6060606c6f7a7a6f6b6060606b49476c6060606c6f7a7a6f6b6060606b494b73616f7a1b6072505051565450504c00000007070707070707545073606f6f607250505050505050505050505050505073606e6e6e6e4e7a7a04046f60495b4040404040456360046f60624040405c\n", @@ -279,7 +279,7 @@ "476c6060606060606060606060606060606c6f6f6f6f7a7a7a7a7a6f0b0b604948616f7a7a7a7a7a7a7a7a7a5d6e604957606f7a7a7a7a7a7a7a1b2b2b0c6059071d0007077a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f0404046f7a7a7a7a04307a7a7a7a7a7a6d7a7a7a7a6f6f6f6f6f0404046f6f6f6f6f6f6f6f6f6f6149\n", "47606f6f6f6f6f04046f6d6f6f6f6f6f6f6f6f7a7a7a7a7a7a7a7a0b2b2b604947606f7b4e7a7a7a7a7a7a047a6f604968600c7a7a7a7a7a7a7a7a1b2b6f6049073c2d07077a7a7a7a7a7a047a047a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a30047a7a7a7a7a045d4e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b6f614a\n", "47606f7b7a7a7a7a7a7a6d7a7a6f6f6f6f6f6f6f6f6f6f6f6f6f6f0b2b2b604957606f6f6d6f6f6f6f6f6f6f046f604968612b0c6f6f6f6f6f6f6f7a1b1c604907711d07536f6f6f6f6f0404046f6f6f6f6f6f6f6f0b0c6f6f6f6f6f6f6f6f306f7a7a6f6f6f04046d6f6f6f6f6f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604a\n", - "47606e6e6e6e6f6f6f6f5e7a7a6f6c60606b6060606060606060606060606c49586b6060607060606060606060606c49586b606060606060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49\n", + "47606e6e6e6e6f6f6f6f5e7a7a6f6c6060606060606060606060606060606c49586b6060607060606060706060606c49586b606060706060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49\n", "48606f7a7a307a7a7a7a307a7a6f60624040404046664440404040404040405c5b40406644404040404040404040405c5b4040404040404063600c7a7a6f605907001d0707664644404040404040404040404040404040406361046f605947606f7a7a6f6062404040464644454244404040404040404040410707710044405c\n", "48606f7a6f6d7a7a7a7a7a6f7a6f60494b505050505176515050505050505050505050505050504c4b5050505051077107567656545050507360040c7a6f606a07071d07070707073c0d0d0d2d000000000000000000000073606f6f6a004760047a7a6f604900072a2a0000000000000000710000000000000000000000072a\n", "48616f7a6f5d6e6f6f7a4d6e6e6e6049476c60606060606060606060606060606060606060606b49476c6060606052565360706060606060606c1c7a7a6f606907001d0707070707070707071d6452765352765656765360606c046f60655760047a7a6f604900072a2a0000000071000000000000000071000000000000072a\n", @@ -452,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": 30, "metadata": {}, "outputs": [ { @@ -462,8 +462,8 @@ "Processing Map 1_2:\n", "Map piece 1:\n", "Original length: 4096\n", - "Compressed length: 2005\n", - "Compression ratio: 0.49\n", + "Compressed length: 2031\n", + "Compression ratio: 0.50\n", "Matches original: True\n", "\n", "Map piece 2:\n", @@ -473,39 +473,39 @@ "Matches original: True\n", "\n", "Lengths of compressed pieces:\n", - "[2005, 1585]\n", - "Total compressed length: 3590\n", + "[2031, 1585]\n", + "Total compressed length: 3616\n", "\n", "Compressed data:\n", - "0381c55000f808281c0e533b1d8a2073881cac762b150a05028c02c80f00728009154a2562b1da00c500c32612e034f0145808b4080a060c4c82fe0009703b381c9c0f4a080701e203c001800000ec029200aa0732180c07030180a45629c018e016a768089c0352006e6b2491cd901a6808dc0c24d64982fa1c40105d280190\n", - "a8af01c43841114e101e783fb0f01a0d150b61204710043d88be04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e1436880000160f600a607219f808783fc1648da7e379fcfc6f3012a2ce47187fbc424c2a161ec428a22667e3c1e8f4793d1e\n", - "0f27a3d06c39086280394004e0285008236c030a0bc404301648e60379ee01eb01333dc1fd0de7b0201205a707f33481c0e7783fa0740110430381cfe7982891e09a5e3d1e8fc7b3798623c477068345b07f684490944d18a38c99c1c48f10f1383bac1c0e2187080583fa43b4cd257304080cbb128282894250a0fe909e281c\n", - "29bcc05a068347b1da238c998a1fe853379e21a6478369e0f0793c9fcfc608cf2c7e121fe82a068f45512f290991f4fd12638bc2460ca004700c2021bc160c837a13a20a905e23441fd214886f269b8dc6e83fb1be20046f3d45fc89315e38bc1c1fd4b0613f43298f40b3c9e8ff17f483fd0b22f8528a38c911d248ae423311\n", - "88c6621c9d9a464505a48202174dc60251a0c26f2693a1e067a269ba2a64048bfa17626843083e9134dc60394c34e60c60793f91c40047304720cdf1c023786e2bc517f39b1143fe25f6d209483b1910804020caf8c0e07221048c66331108a40319863b471942379e8360c936a1b8bc6d37974dc2e1797a20a707f590d90c0f\n", - "430371bcbc6f35cc9de0fe80083f945fca13a87a379e635a44a9df94f6ce7b09004f80a7d020ce716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f4936495d24bce6a8769c7f8e622920023012652286f02480d274a910e23012\n", - "45907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925\n", - "088e71c462319820fe672ade956fc89942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5\n", - "155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b30988804123110804520170b70b032311888452111216e767f22dc039e1\n", - "7c85c9502d8f10ff22e235dd39628a5726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132ca697c24a0da42d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0\n", - "fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715493ee7bf8402c562bc26acc7ea9ba549d68401dd78c7983\n", - "c299fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09d6b8e61fa58ba09e9ce201ed4a38c2f16ad48e9aa13d4be530d00957c3a64f6af2484000010e87e525f491225e48e8feb30fb9ef94c76665ed4\n", - "a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e99a09832efe7baf7d9d90345bae73801b502ccd58ee2cc39cc94b3fcdb083dc59c0a1c0e2a96f9e2f8ac6499bd4c07fef57381d18072b5816b78f86adc39432148ec53291db93a453c1396df08ca5783ff41\n", - "ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06fcaa16fdc8f07a36f0b8c0807160360feda632cfe174ad3a9adcff6c16a0c35747a123d1\n", - "71657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683\n", - "ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c00239acc1000a36188c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f30\n", - "1a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d\n", - "8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f65\n", - "04a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae\n", - "6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8a\n", - "a5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1\n", - "919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d08\n", - "4000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a758\n", - "95ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc43331188\n", - "76db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa\n", - "264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc57\n", - "6514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac\n", - "3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0\n", - "ae703d9b6f200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n", + "0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140aa50800113096502814604c4072a40516022d020280aa9409905bc0012e0767039381e94100e03c4078411400c80e000007600c607802a81cc860301c0c060290a8002a29c020e016500c680059ac92473640990ed011\n", + "c8119c0554d64982da1c40105d280190a8af01c4384134e0be701e580e141fd0780d068a85b0902388000e710397c086f379f8de6f379fcde1b158100900c2806a40098db00a3021bcf6602491cc06e27403d6006b0113024068a04080437980b307c28301800583d802981c867e021e0ff0592369f8de7f3f1bcc04a8b39427\n", + "0e1fe910530a8587b10a288799f8f07a3d1e4f4783c9e8f41b0e4218a00e5001380a140208db00c282f1010c05923980de7b807ac04ccf707f4379ec08048169c1fccd207039de0fe81d00419d4fe7982891e09a5e3d1e8fc7b3798623c477068345b17f587a901c4a268c51c63ce0e24788789c1dd60e0710c38402c1fd21da\n", + "6692b98204065d89414144a128507f484f140e14de602d0341a3d8ed11c64cc50ff4299bcf10d323c1b4f0783c9e4fe7e30467963f091844150347a2a8979484c8fa7e8931c5e12306500238061010de0b0641bd09d105482f11a20fe90a4437934dc6e3741fd8df1002379ea2fe44986a1c780e0fea58309fa194c7a059e4f4\n", + "7f8bfa41fe243e43c8852c2b88e92457211988c463310e462b232382d241010ba6e30128d061379349d0f033d134dd15320245fd0bb13421841f489a6e301ca61a7306303c9fc8e2002398239066f8e011bc3715e28bf9c9aca0fef38828852cc54e0ec64420100832be30381c884123198cc4422900c6618ed1c6508de7a0d8\n", + "324da86e2f1b4de5d370b85e5e8829c1fd64364303d0c0dc6f2f1bcd7327783fa0020fe517f284ea1e8de798d6912a77e53c039e59c00fe027e50a031140e716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f5936495d24bce6a\n", + "8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003\n", + "f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e\n", + "755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b309888041231\n", + "10804520170b70b032311888452111216e767f22dc039e17c85c9502d8f10ff22e235dd25228a7726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8\n", + "522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715\n", + "493ee7bf8402c562bc26acc7ea9ba549d68401dd78ee62733f7c3aa52d30610125025ec1699fd87f29df9e2fca87af56cc83fa567da63f06830d278eb0ea1b0e5f78c3b853286c752c4e2fcd6a95020121bb15e1ca1f9ca78f3b275c13b4679b09258ba09dff1a01f14a38c2f16ad48e9a9c3811c52ce00957c3a64f6af64840\n", + "00010e87e525f491225e48e8feb30fb9ef94c76665ed4a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e2e0624d0541977f3dd7becec81a2dd739c00da81666ac771661ce64a59fe6d841ee2ce050e07154b7cf17c56324cdea603ff7ab8522b7031300e96b\n", + "02d6f1f0d5b872864291d8a6523b7274b84a46cdbe1194af07fe83dc421180e2ac96a6b6ccdc5ec13a51fda44ff6e74e029efe4f1aad7b42daf149fca0ff043e141599ccfc6f36eca480804215068a1fed9fe325f46132df84cd9f9e2f9ef4e747f399caede66e8cf8183bb535009a4feba215e9c91a0df9542dfb91e0f46de1\n", + "718100e2c06c1fdb4c659fc2e95a7471b9fed82d4186ae8f4247a2e2cafa61e34179d56d389e24e36d87b6d2def1c3fe4fe7ae46101210645d371b8f67a37efe8b708a00039df7fc55db5bd6563fd717cf8637c72dc59528ce7ad541806c606fa3f3f40937bc5000a0e99cfc7836f3d4ba709d3923f14b8dc527f5cff2596d31\n", + "58dd003d4b9dffce2a714ffba1ca716933345de74e2e91ef24ed07ffdd1a8f0a67ff0ca5f693bbe47f356174a5febeda2dbe272ee7a5ad7ffc0268b8f4cf529a3a731dc1d77de43037c1f9f4be7c5e2eef144068e8422310ccc462190b9607cb62219d67fed7936bc5213bb589dad8239acc1000a36188c700f7359bcf47a801\n", + "1400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8\n", + "de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75\n", + "c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a57\n", + "69084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d298767505343\n", + "5834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b14\n", + "68e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8\n", + "b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870f\n", + "e602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3\n", + "a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c2914921809\n", + "32922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74\n", + "c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4a\n", + "c2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881\n", + "c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n", "\n", "Processing Map 3_4:\n", "Map piece 1:\n", From 5d25994a309aa655ecfc48921cc39c5a3d3d576e Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 16:31:18 +0100 Subject: [PATCH 04/16] got map working --- beta_v0.2.2_copy.p8 | 109 +++++++++++++++++++++----------------------- 1 file changed, 51 insertions(+), 58 deletions(-) diff --git a/beta_v0.2.2_copy.p8 b/beta_v0.2.2_copy.p8 index d79e3a1..f0e063f 100644 --- a/beta_v0.2.2_copy.p8 +++ b/beta_v0.2.2_copy.p8 @@ -49,10 +49,12 @@ function decompress_to_memory(compressed_data, dest_address) poke(dest_index, byte) dest_index += 1 else - local distance, length = dest_index - read_bits(12) - 1, read_bits(4) + 1 + local distance = dest_index - read_bits(12) - 1 + local length = read_bits(4) + 1 for _ = 1, length do poke(dest_index, peek(distance)) - distance, dest_index = distance + 1, dest_index + 1 + distance += 1 + dest_index += 1 end end end @@ -252,19 +254,19 @@ function textpanel:draw() local dx = cam.x + self.x + self.x_offset - self.expand_counter local dy = cam.y + self.y local w = self.width + self.expand_counter * 2 + local dy2 = dy + self.height - rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) - rectfill(dx + w - 2, dy - 1, dx + w + 1, dy + self.height + 1, 3) - rectfill(dx, dy, dx + w, dy + self.height, 0) + rectfill(dx - 1, dy - 1, dx + 2, dy2 + 1, 3) + rectfill(dx + w - 2, dy - 1, dx + w + 1, dy2 + 1, 3) + rectfill(dx, dy, dx + w, dy2, 0) if self.selected then local line_x = dx + (self.line_offset % (w + 1)) - line(line_x, dy, line_x, dy + self.height, 2) + line(line_x, dy, line_x, dy2, 2) end local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline - local color = self.text_color or (self.selected and 11 or 5) - print(display_text, dx + 2, dy + 2, color) + print(display_text, dx + 2, dy + 2, self.text_color or (self.selected and 11 or 5)) end function textpanel:update() @@ -396,13 +398,14 @@ function ability_menu:update() end function ability_menu:draw() - if not self.active then return end - for p in all(self.panels) do - local ability = player.abilities[p.ability_index] - if ability then - p.text_color = ability.remaining_uses > 0 and (p.selected and 11 or 5) or 2 + if self.active then + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + p.text_color = ability.remaining_uses > 0 and (p.selected and 11 or 5) or 2 + end + p:draw() end - p:draw() end end @@ -558,20 +561,20 @@ function update_intro() controls_text_panel:update() end -function draw_intro() - reset_pal(true) - map(unpack(INTRO_MAP_ARGS)) - draw_shadow(128,128,0, SWAP_PALETTE_DARK) +-- function draw_intro() +-- reset_pal(true) +-- map(unpack(INTRO_MAP_ARGS)) +-- draw_shadow(128,128,0, SWAP_PALETTE_DARK) - if sin(t()) < .9 then circfill(63,64, 3, 2) end +-- if sin(t()) < .9 then circfill(63,64, 3, 2) end - local y_logos = intro_text_panel.active and 0 or 30 - display_logo(x_cortex, x_protocol, y_logos) +-- local y_logos = intro_text_panel.active and 0 or 30 +-- display_logo(x_cortex, x_protocol, y_logos) - intro_text_panel:draw() - controls_text_panel:draw() - if intro_counter > 60 then print("PRESS ❎ TO CONTINUE", 24, 118, 11) end -end +-- intro_text_panel:draw() +-- controls_text_panel:draw() +-- if intro_counter > 60 then print("PRESS ❎ TO CONTINUE", 24, 118, 11) end +-- end -- MISSION SELECT ---------------------- @@ -765,7 +768,7 @@ function init_gameplay() end local tutorial_terminals = { - [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48,ATTACK: ❎|40,-8,FRAGMENTS RESTORE HP|264,-2,WEAPONS MENU: 🅾️|368,-2,DEFEAT ENEMY]], + [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48, ATTACK: ❎|40,-8, FRAGMENTS RESTORE HP|264,-2, WEAPONS MENU: 🅾️|368,-2, DEFEAT ENEMY]], "", -- No tutorials for mission 2 "", -- No tutorials for mission 3 "" -- No tutorials for mission 4 @@ -1189,7 +1192,7 @@ function entity:update() for fragment in all(data_fragments) do if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then self.health = min(self.health + 25, self.max_health) - player_hud:add_credits(50) + player_hud.credit_add_timer += 50 fragment.collected = true sfx(7) end @@ -1246,7 +1249,7 @@ function entity:take_damage(amount) end function entity:on_death() - player_hud:add_credits(self.kill_value) + player_hud.credit_add_timer += self.kill_value self:spawn_death_particles() del(entities, self) sfx(30) @@ -1478,15 +1481,15 @@ function entity:apply_physics() local new_x, new_y = self.x + self.vx, self.y + self.vy -- Check tile collision - -- if self:check_tile_collision(new_x, new_y) then - -- if not self:check_tile_collision(new_x, self.y) then - -- new_y = self.y - -- elseif not self:check_tile_collision(self.x, new_y) then - -- new_x = self.x - -- else - -- new_x, new_y = self.x, self.y - -- end - -- end + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end -- Check laser door collision for door in all(doors) do @@ -1689,17 +1692,11 @@ function laser_door:draw() end end -function laser_door:check_collision(ex, ey, ew, eh) - if self.is_open then return false end - - for beam in all(self.laser_beams) do - if (ey + eh > beam.start_y and ey < beam.end_y) and - (ex < beam.start_x and ex + ew > beam.start_x) then - return true - end +function laser_door:check_collision(ex,ey,ew,eh) + if self.is_open then return end + for b in all(self.laser_beams) do + if ey+eh>b.start_y and eyb.start_x then return true end end - - return false end -- DATA FRAGMENT @@ -1744,7 +1741,7 @@ function terminal.new(x, y, target_door, tutorial_msg) }, {__index = terminal}) -- Create panel at fixed screen position (will be adjusted with camera in draw) - local msg = tutorial_msg or "❎ INTERACT" + local msg = tutorial_msg or " INTERACT: ❎" local panel_width = max(40, #msg * 4 + 12) -- Adjust width based on message length t.panel = textpanel.new(64 - panel_width/2, 114, 10, panel_width, msg, true) t.panel.selected = true @@ -1829,7 +1826,6 @@ function minigame:start(terminal) timer = time_limit current_input = {} current_terminal = terminal - end function minigame:update() @@ -1885,7 +1881,6 @@ function minigame:draw() rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) - -- Calculate total width of sequence local seq_width = #self.sequence * 12 - 4 local seq_start_x = center_x - seq_width / 2 @@ -1894,7 +1889,6 @@ function minigame:draw() seq_start_x += 12 end - -- Reset seq_start_x for current input seq_start_x = center_x - seq_width / 2 for i, dir in pairs(self.current_input) do @@ -1903,10 +1897,8 @@ function minigame:draw() seq_start_x += 12 end - -- Center the timer text local timer_text = "time: "..flr(self.timer / 30) - local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide - print(timer_text, center_x - timer_width / 2, center_y + 10, 8) + print(timer_text, center_x - #timer_text * 2, center_y + 10, 8) end @@ -1989,6 +1981,11 @@ function player_hud:draw() end ending_sequence_timer -= 1 end + + local mx,my,px,py=cam.x+112,cam.y+112,flr(player.x/8),flr(player.y/8) + for i=0,255 do local tx,ty=i%16-8,flr(i/16)-8 + pset(mx+tx+8,my+ty+8,fget(mget(px+tx,py+ty),0)and 1 or 5)end + pset(mx+8,my+8,7) end function draw_bar(x, y, w, h, bg, fill, pct) @@ -2003,10 +2000,6 @@ function print_shadow(text, x, y, color) print(text, x, y, color or 7) end -function player_hud:add_credits(amount) - self.credit_add_timer += amount -end - __gfx__ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee From 66d16a8b956a5cc1a60395451ac7c101603699f4 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 16:56:17 +0100 Subject: [PATCH 05/16] got map working --- beta_v0.2.2_copy.p8 | 101 ++++++++++++++++++-------------------------- 1 file changed, 41 insertions(+), 60 deletions(-) diff --git a/beta_v0.2.2_copy.p8 b/beta_v0.2.2_copy.p8 index f0e063f..70ec74f 100644 --- a/beta_v0.2.2_copy.p8 +++ b/beta_v0.2.2_copy.p8 @@ -166,14 +166,14 @@ function gamecam.new() return setmetatable({ x = 0, y = 0, - lerpfactor = 0.2 }, gamecam) end function gamecam:update() - self.x += (player.x - self.x - 64) * self.lerpfactor - self.y += (player.y - self.y - 64) * self.lerpfactor + self.x += (player.x - self.x - 64) * 0.2 + self.y += (player.y - self.y - 64) * 0.2 + -- shaking effect if count_remaining_terminals() == 0 then self.x += rnd(4) - 2 self.y += rnd(4) - 2 @@ -200,26 +200,25 @@ function transition:start() end function transition:update() - if not self.active then return false end - - self.t += self.closing and 1 or -1 - - if self.closing and self.t == self.duration then - self.closing = false - return true - elseif not self.closing and self.t == 0 then - self.active = false + if self.active then + self.t += self.closing and 1 or -1 + + if self.closing and self.t == self.duration then + self.closing = false + return true + elseif not self.closing and self.t == 0 then + self.active = false + end end - - return false end function transition:draw() - if not self.active then return end - local size = max(1, flr(16 * self.t/self.duration)) - for x = 0, 127, size do - for y = 0, 127, size do - rectfill(x, y, x+size-1, y+size-1, pget(x, y)) + if self.active then + local size = max(1, flr(16 * self.t/self.duration)) + for x = 0, 127, size do + for y = 0, 127, size do + rectfill(x, y, x+size-1, y+size-1, pget(x, y)) + end end end end @@ -273,15 +272,11 @@ function textpanel:update() self.expand_counter += self.selected and (self.expand_counter < 3 and 1 or 0) or (self.expand_counter > 0 and -1 or 0) self.x_offset += self.move_direction * self.max_offset / 5 - if self.x_offset <= -self.max_offset or self.x_offset >= 0 then - self.move_direction *= -1 - end + if self.x_offset <= -self.max_offset or self.x_offset >= 0 then self.move_direction *= -1 end self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 - if self.reveal and self.char_count < #self.textline then - self.char_count += 2 - end + if self.reveal and self.char_count < #self.textline then self.char_count += 2 end end -- TARGETING @@ -302,12 +297,10 @@ end function targeting:update() local closest_dist, closest_target = self.owner.attack_range, nil for e in all(entities) do - if e != self.owner then - if self.owner.subclass == "player" != (e.subclass == "player") then - local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) - if dist < closest_dist and self:has_line_of_sight(e) then - closest_dist, closest_target = dist, e - end + if e != self.owner and self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e end end end @@ -335,7 +328,7 @@ function targeting:has_line_of_sight(t) local step = max(abs(dx), abs(dy)) for i=0,step do - if check_tile_flag(x+dx*i/step, y+dy*i/step) then return false end + if check_tile_flag(x+dx*i/step, y+dy*i/step) then return end end return true end @@ -346,9 +339,7 @@ function targeting:draw() for i = 0, 3 do local angle = self.rotation + i * 0.25 - local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) - line(x + cos1 * half_size, y + sin1 * half_size, - x + cos2 * half_size, y + sin2 * half_size, 3) + line(x + cos(angle) * half_size, y + sin(angle) * half_size, x + cos(angle + 0.25) * half_size, y + sin(angle + 0.25) * half_size, 3) end end @@ -561,20 +552,20 @@ function update_intro() controls_text_panel:update() end --- function draw_intro() --- reset_pal(true) --- map(unpack(INTRO_MAP_ARGS)) --- draw_shadow(128,128,0, SWAP_PALETTE_DARK) +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) --- if sin(t()) < .9 then circfill(63,64, 3, 2) end + if sin(t()) < .9 then circfill(63,64, 3, 2) end --- local y_logos = intro_text_panel.active and 0 or 30 --- display_logo(x_cortex, x_protocol, y_logos) + local y_logos = intro_text_panel.active and 0 or 30 + display_logo(x_cortex, x_protocol, y_logos) --- intro_text_panel:draw() --- controls_text_panel:draw() --- if intro_counter > 60 then print("PRESS ❎ TO CONTINUE", 24, 118, 11) end --- end + intro_text_panel:draw() + controls_text_panel:draw() + if intro_counter > 60 then print("PRESS ❎ TO CONTINUE", 24, 118, 11) end +end -- MISSION SELECT ---------------------- @@ -939,8 +930,6 @@ function particle:check_collision_and_damage() return true end end - - return false end function particle:update() @@ -1272,31 +1261,24 @@ end function entity:can_see_player() local player = self:find_player() - if not player then return false end + if not player then return end if dist_trig(player.x - self.x, player.y - self.y) <= self.attack_range and self.targeting:has_line_of_sight(player) then self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y return true end - - return false end function entity:find_ability(ability_name) for i, ability in ipairs(self.abilities) do - if ability.name == ability_name then - return i - end + if ability.name == ability_name then return i end end - return nil end function entity:find_player() for e in all(entities) do - if e.subclass == "player" then - return e - end + if e.subclass == "player" then return e end end end @@ -1892,8 +1874,7 @@ function minigame:draw() seq_start_x = center_x - seq_width / 2 for i, dir in pairs(self.current_input) do - local color = dir == self.sequence[i] and 11 or 8 - print(dir, seq_start_x, center_y, color) + print(dir, seq_start_x, center_y, dir == self.sequence[i] and 11 or 8) seq_start_x += 12 end From 33c1aa22edff7ca53c442886f5354aa477438cae Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 17:08:19 +0100 Subject: [PATCH 06/16] got map working --- beta_v0.2.2.p8 | 517 +++++----- beta_v0.2.2_copy.p8 | 2310 ------------------------------------------- 2 files changed, 248 insertions(+), 2579 deletions(-) delete mode 100644 beta_v0.2.2_copy.p8 diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index a1de7d6..f33c505 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -24,6 +24,109 @@ Optional Objectives: * Eliminate all enemy units ]] +-- MAIN +---------------------- +function _init() + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS = { + "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", + "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", + "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", + "FACILITY 800a:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" + } + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 + + v = 2040 + -- Load compressed map from map data + map_data = { + -- 4 Levels + pack(peek(0x2000, v)), + pack(peek(0x2000 + v, 1585)), + pack(peek(0x1000, 1788)), + pack(peek(0x1000 + 1788, 1402)), + -- Logo + pack(peek(0x1000 + 1788 + 1402, 243)) + } + + -- Decompress Logo + decompress_to_memory(map_data[5], 0x1C00) + + -- -- Load map 1 for Intro + decompress_current_map() + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + change_state("intro", true) +end + +function _update() + if trans.active then + if trans:update() then + current_state = next_state + current_state.init() + next_state = nil + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, skip_transition) + local new_state = states[new_state_name] + + if skip_transition ~= true and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + + -- MAP COMPRESSION ---------------------- function decompress_to_memory(compressed_data, dest_address) @@ -49,10 +152,12 @@ function decompress_to_memory(compressed_data, dest_address) poke(dest_index, byte) dest_index += 1 else - local distance, length = dest_index - read_bits(12) - 1, read_bits(4) + 1 + local distance = dest_index - read_bits(12) - 1 + local length = read_bits(4) + 1 for _ = 1, length do poke(dest_index, peek(distance)) - distance, dest_index = distance + 1, dest_index + 1 + distance += 1 + dest_index += 1 end end end @@ -164,14 +269,14 @@ function gamecam.new() return setmetatable({ x = 0, y = 0, - lerpfactor = 0.2 }, gamecam) end function gamecam:update() - self.x += (player.x - self.x - 64) * self.lerpfactor - self.y += (player.y - self.y - 64) * self.lerpfactor + self.x += (player.x - self.x - 64) * 0.2 + self.y += (player.y - self.y - 64) * 0.2 + -- shaking effect if count_remaining_terminals() == 0 then self.x += rnd(4) - 2 self.y += rnd(4) - 2 @@ -198,26 +303,25 @@ function transition:start() end function transition:update() - if not self.active then return false end - - self.t += self.closing and 1 or -1 - - if self.closing and self.t == self.duration then - self.closing = false - return true - elseif not self.closing and self.t == 0 then - self.active = false + if self.active then + self.t += self.closing and 1 or -1 + + if self.closing and self.t == self.duration then + self.closing = false + return true + elseif not self.closing and self.t == 0 then + self.active = false + end end - - return false end function transition:draw() - if not self.active then return end - local size = max(1, flr(16 * self.t/self.duration)) - for x = 0, 127, size do - for y = 0, 127, size do - rectfill(x, y, x+size-1, y+size-1, pget(x, y)) + if self.active then + local size = max(1, flr(16 * self.t/self.duration)) + for x = 0, 127, size do + for y = 0, 127, size do + rectfill(x, y, x+size-1, y+size-1, pget(x, y)) + end end end end @@ -252,34 +356,30 @@ function textpanel:draw() local dx = cam.x + self.x + self.x_offset - self.expand_counter local dy = cam.y + self.y local w = self.width + self.expand_counter * 2 + local dy2 = dy + self.height - rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) - rectfill(dx + w - 2, dy - 1, dx + w + 1, dy + self.height + 1, 3) - rectfill(dx, dy, dx + w, dy + self.height, 0) + rectfill(dx - 1, dy - 1, dx + 2, dy2 + 1, 3) + rectfill(dx + w - 2, dy - 1, dx + w + 1, dy2 + 1, 3) + rectfill(dx, dy, dx + w, dy2, 0) if self.selected then local line_x = dx + (self.line_offset % (w + 1)) - line(line_x, dy, line_x, dy + self.height, 2) + line(line_x, dy, line_x, dy2, 2) end local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline - local color = self.text_color or (self.selected and 11 or 5) - print(display_text, dx + 2, dy + 2, color) + print(display_text, dx + 2, dy + 2, self.text_color or (self.selected and 11 or 5)) end function textpanel:update() self.expand_counter += self.selected and (self.expand_counter < 3 and 1 or 0) or (self.expand_counter > 0 and -1 or 0) self.x_offset += self.move_direction * self.max_offset / 5 - if self.x_offset <= -self.max_offset or self.x_offset >= 0 then - self.move_direction *= -1 - end + if self.x_offset <= -self.max_offset or self.x_offset >= 0 then self.move_direction *= -1 end self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 - if self.reveal and self.char_count < #self.textline then - self.char_count += 2 - end + if self.reveal and self.char_count < #self.textline then self.char_count += 2 end end -- TARGETING @@ -300,12 +400,10 @@ end function targeting:update() local closest_dist, closest_target = self.owner.attack_range, nil for e in all(entities) do - if e != self.owner then - if self.owner.subclass == "player" != (e.subclass == "player") then - local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) - if dist < closest_dist and self:has_line_of_sight(e) then - closest_dist, closest_target = dist, e - end + if e != self.owner and self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e end end end @@ -333,7 +431,7 @@ function targeting:has_line_of_sight(t) local step = max(abs(dx), abs(dy)) for i=0,step do - if check_tile_flag(x+dx*i/step, y+dy*i/step) then return false end + if check_tile_flag(x+dx*i/step, y+dy*i/step) then return end end return true end @@ -344,9 +442,7 @@ function targeting:draw() for i = 0, 3 do local angle = self.rotation + i * 0.25 - local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) - line(x + cos1 * half_size, y + sin1 * half_size, - x + cos2 * half_size, y + sin2 * half_size, 3) + line(x + cos(angle) * half_size, y + sin(angle) * half_size, x + cos(angle + 0.25) * half_size, y + sin(angle + 0.25) * half_size, 3) end end @@ -396,13 +492,14 @@ function ability_menu:update() end function ability_menu:draw() - if not self.active then return end - for p in all(self.panels) do - local ability = player.abilities[p.ability_index] - if ability then - p.text_color = ability.remaining_uses > 0 and (p.selected and 11 or 5) or 2 + if self.active then + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + p.text_color = ability.remaining_uses > 0 and (p.selected and 11 or 5) or 2 + end + p:draw() end - p:draw() end end @@ -410,107 +507,6 @@ ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) ability_menu.close = function(self) self.active = false end --- MAIN ----------------------- -function _init() - cam = gamecam.new() - - -- Missions - MISSION_BRIEFINGS = { - "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", - "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", - "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", - "FACILITY 800a:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" - } - - mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 - - -- Load compressed map from map data - map_data = { - -- 4 Levels - pack(peek(0x2000, 2005)), - pack(peek(0x2000 + 2005, 1585)), - pack(peek(0x1000, 1788)), - pack(peek(0x1000 + 1788, 1402)), - -- Logo - pack(peek(0x1000 + 1788 + 1402, 243)) - } - - -- Decompress Logo - -- decompress_to_memory(map_data[5], 0x1C00) - - -- -- Load map 1 for Intro - -- decompress_current_map() - - SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ - 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| - 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| - 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| - 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) - - entity_abilities = { - dervish = {"mACHINE gUN"}, - vanguard = {"rIFLE bURST"}, - warden = {"mISSILE hAIL"}, - cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, - quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} - } - - entity_colors = { - dervish = 15, - vanguard = 13, - warden = 1, - player = 7, - preacher = 11, - cyberseer = 6, - quantumcleric = 1 - } - - states = {} - for name in all(STATE_NAMES) do - states[name] = { - init = _ENV["init_" .. name], - update = _ENV["update_" .. name], - draw = _ENV["draw_" .. name] - } - end - - trans = transition.new() - player = entity.new(0, 0, "bot", "player") - change_state("loadout_select", true) -end - - -function _update() - if trans.active then - if trans:update() then - current_state = next_state - current_state.init() - next_state = nil - end - else - current_state.update() - end -end - -function _draw() - current_state.draw() - trans:draw() - printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) -end - -function change_state(new_state_name, skip_transition) - local new_state = states[new_state_name] - - if skip_transition ~= true and not trans.active then - sfx(20) - next_state = new_state - trans:start() - else - current_state = new_state - current_state.init() - end -end -- INTRO ---------------------- @@ -725,7 +721,7 @@ end -- GAMEPLAY ---------------------- function init_gameplay() - -- decompress_current_map() + decompress_current_map() music(0) player_hud = player_hud.new() entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 @@ -764,7 +760,7 @@ function init_gameplay() end local tutorial_terminals = { - [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48,ATTACK: ❎|40,-8,FRAGMENTS RESTORE HP|264,-2,WEAPONS MENU: 🅾️|368,-2,DEFEAT ENEMY]], + [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48, ATTACK: ❎|40,-8, FRAGMENTS RESTORE HP|264,-2, WEAPONS MENU: 🅾️|368,-2, DEFEAT ENEMY]], "", -- No tutorials for mission 2 "", -- No tutorials for mission 3 "" -- No tutorials for mission 4 @@ -935,8 +931,6 @@ function particle:check_collision_and_damage() return true end end - - return false end function particle:update() @@ -1188,7 +1182,7 @@ function entity:update() for fragment in all(data_fragments) do if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then self.health = min(self.health + 25, self.max_health) - player_hud:add_credits(50) + player_hud.credit_add_timer += 50 fragment.collected = true sfx(7) end @@ -1245,7 +1239,7 @@ function entity:take_damage(amount) end function entity:on_death() - player_hud:add_credits(self.kill_value) + player_hud.credit_add_timer += self.kill_value self:spawn_death_particles() del(entities, self) sfx(30) @@ -1268,31 +1262,24 @@ end function entity:can_see_player() local player = self:find_player() - if not player then return false end + if not player then return end if dist_trig(player.x - self.x, player.y - self.y) <= self.attack_range and self.targeting:has_line_of_sight(player) then self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y return true end - - return false end function entity:find_ability(ability_name) for i, ability in ipairs(self.abilities) do - if ability.name == ability_name then - return i - end + if ability.name == ability_name then return i end end - return nil end function entity:find_player() for e in all(entities) do - if e.subclass == "player" then - return e - end + if e.subclass == "player" then return e end end end @@ -1688,17 +1675,11 @@ function laser_door:draw() end end -function laser_door:check_collision(ex, ey, ew, eh) - if self.is_open then return false end - - for beam in all(self.laser_beams) do - if (ey + eh > beam.start_y and ey < beam.end_y) and - (ex < beam.start_x and ex + ew > beam.start_x) then - return true - end +function laser_door:check_collision(ex,ey,ew,eh) + if self.is_open then return end + for b in all(self.laser_beams) do + if ey+eh>b.start_y and eyb.start_x then return true end end - - return false end -- DATA FRAGMENT @@ -1743,7 +1724,7 @@ function terminal.new(x, y, target_door, tutorial_msg) }, {__index = terminal}) -- Create panel at fixed screen position (will be adjusted with camera in draw) - local msg = tutorial_msg or "❎ INTERACT" + local msg = tutorial_msg or " INTERACT: ❎" local panel_width = max(40, #msg * 4 + 12) -- Adjust width based on message length t.panel = textpanel.new(64 - panel_width/2, 114, 10, panel_width, msg, true) t.panel.selected = true @@ -1828,7 +1809,6 @@ function minigame:start(terminal) timer = time_limit current_input = {} current_terminal = terminal - end function minigame:update() @@ -1884,7 +1864,6 @@ function minigame:draw() rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) - -- Calculate total width of sequence local seq_width = #self.sequence * 12 - 4 local seq_start_x = center_x - seq_width / 2 @@ -1893,19 +1872,15 @@ function minigame:draw() seq_start_x += 12 end - -- Reset seq_start_x for current input seq_start_x = center_x - seq_width / 2 for i, dir in pairs(self.current_input) do - local color = dir == self.sequence[i] and 11 or 8 - print(dir, seq_start_x, center_y, color) + print(dir, seq_start_x, center_y, dir == self.sequence[i] and 11 or 8) seq_start_x += 12 end - -- Center the timer text local timer_text = "time: "..flr(self.timer / 30) - local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide - print(timer_text, center_x - timer_width / 2, center_y + 10, 8) + print(timer_text, center_x - #timer_text * 2, center_y + 10, 8) end @@ -1988,6 +1963,11 @@ function player_hud:draw() end ending_sequence_timer -= 1 end + + local mx,my,px,py=cam.x+112,cam.y+112,flr(player.x/8),flr(player.y/8) + for i=0,255 do local tx,ty=i%16-8,flr(i/16)-8 + pset(mx+tx+8,my+ty+8,fget(mget(px+tx,py+ty),0)and 3 or 11) end + pset(mx+8,my+8,7) end function draw_bar(x, y, w, h, bg, fill, pct) @@ -2002,10 +1982,6 @@ function print_shadow(text, x, y, color) print(text, x, y, color or 7) end -function player_hud:add_credits(amount) - self.credit_add_timer += amount -end - __gfx__ eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee @@ -2072,54 +2048,60 @@ e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee 5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d 53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d 3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd -74b6060606060606060606060606c62636b606060606060606b6f6a7a7f6a7a7a7f6a7a740b70694740640c1c1a7f6a7a7a7a7a7a7a7a7f603e503e6e5f60696 -70707070707070177070707070b4050515701770656765450505050505053706f6a7a7f6b60606060606060606c6f6a7a7d6a7f6a7a7a7a7f6a7a7a7a7f60695 -b50404046466440404040404040404c5b5040404646644043606f6a7a7a7f6f6f6a7a7a740f606947406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7b7a7a7a7b006a5 -7017707070707070707070707075c606062565350607060606060606060606c6f6a7a7f6f6f6f6f6f6f6f6f6f6f6f6a7a7d6f6d6a7a7a7d4e4f6a7a7a7f606a6 -b405050505050515671505050505050505050505050505053706f6a7a7a7a7a7a7a7a7a7f64006947406f6a7a7a7a7a7a7a7a7a7a7a7d4e6e603f6a7b0b20694 -7070707070a27070a2707070707034f6f6f6f6f6f6f6f640f6f640f6f64003c0c0b2c0a7a7a7a7a7a7a7a7a7a7a7a7a7a7d6f6d5e6e2f2e5d5f6e6e6e6e60696 -74c60606060606060606060606060606060606060606060606c6f6a7a7a7a7a7a7a7a7a7a7f606947406f6a7a7a7a7a7a7a7a7a7a7a7d6a7a7f6a7b0b2b20694 -7070707070705161707070c2d0d0d0f6a7a7a7a7a7a740a7a7a7a740a74003b1b2b1c1a7a7a7a7a7b0c0a7a7a7a7a7a7d4d5f6a7a7e3f3a7a7f6a7a7a7400696 -7406f6b1b2b2c1f6d6f6f6f6f6f6f6d6f6f6f6f6f6f6f6f6f6f6f6f6a7a7a7a74040a7a7f6f606947406f6a7a7a7a7a7a7a7a7a7a7a7d6a7a7a7a7b140400695 -7070707070705262707070d1701700f6a7a7a7a7a7a7a7a7a740a7a7a7a74003c1a7a7f6f6f6f6b0b2b2c0f6f6f6d4e6e5a7f6a7a7d6a7a7a7f6a7a7a7400694 -7406f6b1b2b2c1a7d6a7a7a7a7a7a7d6a7a7a7a7f6f6f6f6f6f6f6f6f6f6f6f64040f6f6f6f606947406f6f6f6f6f6f6404040f6f6f6d6f6f6f6b0b2404006a5 -7070707070a27070a2d0d0d3707035f6f6f6f6f6f6f6f6f640f6f64040f6f603f6a787f6c60606060606060606b6e5a7a7a7a7f6e6e5a7a7f6a7a7a7a7f60695 -8406f6a7b1c1f6f6d6f6f6f6f6f6d4e5f6f6a7a7a7f6c6060606060706060606060606060606c69475b6b6f6f7c606060606060606060606060606060606c694 -7070707070707070707070707085b606060606060606060606060606060606b6f6a797f606260404040404043606f6a7a7a7d4e5f6f6f6f6a7a7d4e6e6e60696 -8416f6a7a7f6f6a7d5e4a7a7a7a7d6a7a7f6f6a7a7f60626046644040404040404040404040404c5176416f6f6062614646454040464664404045466044404c5 -70700000000000000000000000b5040404646444542444040404040404043606e797a7f706957070d17070707406f6a7a7a7d5e4a7a7a7a7a7a7d6a7a7f606a5 -7406f6a7f6f6a7a7a7d6a7a7a7a7d6a7a7a7f6f6a7f60694b4050555050505551570706567701770008606f6e7069400000000001700007017707070d11770a2 -a2000000000000000000000000b405050505050505c47000d170707000007416e7a7a7f624707070d17070177506f6a7a7a7a7d6a7a7a7a7a7a7d6a7a7f60696 -7506e6e6e6e6e4a7a7d5f6f6f6f6e5a7a7a7a7f6a7f6069474c6060606060606062535060735567077701640e7062715771770707070707070707070d1707070 -b405051567156567654505050537c60606060606b694700077a2707070007516f7a7a7e725707070a2a270707606f6f6a7a7a7d6a7a7a7a7a7a7d6a7f6f60696 -7406f6a7f6a7d6a7a7f6a7a7a7a7f6a7a7a7a7f6a7f606948406f6f6f6d6f6e7f7f6f640404006a57046c640f6b60606072567350607567070707000a2a27070 -74c6060606060607060606060606c6f640f6b7f606940000a2a2707070001786e78797f7a6701770a2a270708506f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f606a5 -7406f6a7f6a7d6a7f6d6a7a7a7a7a7f6a7a7a7f6a7f606947406e7b7a7d6a7a7a7d4e6e6e6e606a67034f6f6f6f6f6f7f6e7f6404040f47070707000a2a27070 -7406f6f6f6f6f6f6f6d6f6f6f6f6f6f6a74040f60694000000d1001770707047240000345700707070d1707074b606070606060606060706060606060606c694 -7406f6a7f6a7d6a7f6d5e6e2f2a7d4f6e6e6e6e6e6e606948406e6e5a7d6f6f6f6e5b0c0c0f616941785f687a7a7a7a7a7a7a7a797e770707017700070d17070 -7506f6a7a7a7a7a7a7d6a7a7a7a7a7a7a7a740f606940000a2a2000000700000000000000000707070d17070b5040404040404040404040404040404040404c5 -7406f6a7f6d4d5e6f6a7a7e3f3e6e5f6a7a7a7a740400694750640a7a7f6a7a7a7f6b1b2c1f706a47084408797a7a7a7a79797a7a7a7707070707000c2d37070 -8606e6e6e6e4f6f6f6e5b0c0a7d4e6e6e6e6e5f606947000a2a20000007070007070707070177070a2a27070b4051565656715701770656765051567150505c4 -7406e6e6e6e5a7a7f6a7a7d6a7a7a7f6a7a7a7f640400694860640a7f6d6a703a7a7f6b1c1f606a670864040f6f6f6f7f6f6e7f6e7f6c77070707000d1707070 -8616f6a7a7f6a7a7b0f6b1c0a7d6a7a7b0c0a7f606967070c3d0d2007070707070177070707070c2a2a2707075c606060606062565350607060606060606b694 -7506f6a7f6a7a7a7a7f6e6e5a7a7f6a7a7a7a7f6a7f606948616f7a7f6d5039703d4e6e6e6e616967047246466346664340706060606577070707000a2a27070 -7606f6a7f6d6c003b1c0f6c1a7d6a7b0b2b2b2f6f470707070a2a2707017707070707070707070d1707070707034f6f7e7f6404040f6e74040f640f6e7f60694 -76064040f6a7a7a7d4e5f6f6f6f6a7a7d4e6e6e6e6e606948606f6a7f6a7a70397e5f6a7a7f606967070177070707070706664666464001770707000a2a27070 -7606f6a7f6d503a703e6e6e6e6e5b0b2b2b2c1a77070177070a2a2707070707070707070707070d1707070700000e7a7a7a7a7a787a7a7a7a7a7a740b7f60695 -86064040f6f6a7a7d5e4a7a7a7a7a7a7d6a7f6f6b0c006948506f6a7a7f6e6e5a7e7a7a7a7f7f470707070707070707070707070700000707070707070d17070 -8506f6a7f6a7a703a7a7f6a7a7a7b1c1b1c1a7a77070000000d100000046256535560000001700c3a2a200000000f687a7a7a7a7a7a7a7a7a7a7a7a7d5e60694 -76064040a7f6f6a7a7d6a7a7a7a7a7a7d6f6f6b0b2c106948406f6a7d4e5f6f6f687a7a787977070177070701770707070707017700000707070170070d17070 -7406f6c0a7f6e6e5a7f6a7a7a7a7a7a7a7a7a7a77070000000c3d2000086e7e7f7a6000000000000a2a200001700f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70695 -8506f6a7a7a7f6f6f6d6f6f6f6f6f6f6d6f6a7b0c1f606948416c0a7d6a7a7a7a7a7a7a7a7a770707070707070000000000000707070a2707070000070d17070 -7506f6b2d4e5f6f6f6a7a7b0b0c0a7f6f6f6f6f6c77070707070d1707076f7b7f6a670007070707070d170700000f6c0a787a7a7a7a7a7a7a7a7a7a7a7a70696 -7406f6b7a7a7d4e6e6e5a7b0b0c0a7a7d5e4b0c1b7f606947406b2c0d6a797a7a7a7a7a7a79770707070707070c2a2d0d0d0d2707070d17070a2a27070d17070 -7606f6c1d6a7a7a7a7a7b0c1b1b1c0f6a740404006a57070a2a2d3177086f7e7e72570007070a2a270d170707000e7b1c0a7a7b7e4a7a7a7a7a7a740a7f606a5 -7406f6f6f6f6d6f6f6f6b0b2b2b2c0f6f6d6b1f6f6f606947506b2c1d6f6e7f6f6f6f7e7f7e7c7707070a2d0d0d370707070c3a2d0d0d3d0d0a2a2d0d0777070 -8506f6f6d6f6f6f6f6b0c1f6f6f6f6f64040404006947070a2a2707070472417345770007070a277d0d370707035f7f6e7f7f6f6d6f6e7f6f6f6e7f640f60694 -74b60606060606066606060606060606060606060606c69470470606060706060606060706065770707070707070177070707070707017707070707070707070 -74b6060606060606060606060606060606060606c69470707070707070707070707070707070d1707070707085b606060606060606060606060606060606c694 -b504040404040414174414666664644454244404040404c5177034246464666466641464666470a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2 -b50404040404040404646444542444040404040404c5a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2b5040404646444542444040404040404040404c5 +5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 +a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b +0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 +14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f +d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 +6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 +7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c +3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd +1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 +2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 +821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf +6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf +a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b +6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 +2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 +8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 +2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 +7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 +73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 +55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf +6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 +081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 +0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b +8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 +e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 +5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 +c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 +41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 +462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 +2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b +188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 +e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 +260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a +f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 +d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 +d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 +aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 +1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 +1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 +e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c +df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d +e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 +76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 +40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 +64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a +174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 +7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 +d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc +4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a +4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 +01e3a0f160f038782c301e120f0a8f85c703e3a1f1e0f07848f01a4f388300019f6cee918b0e0109d08240c2306a40f6108a18c1bb783c83f14b20e170ff30ff +28641c4dd93c831441b1a4c07c9055382814cf1a4315b98740c1b1e411961097581d04d316c6054a8ff14af22e70fee03c28210cce7c0dcd83f1ca20e070efe3 +18ff0cff0ef705cb8886c954a4717ed09338cf0c5a12f71bf7854cc345e41358205ab8ff1c151a9d11f384fdc927637172c12ec81f6cff3e2607ff8701c1d52a +243b6174b8cec47806d20bf38f904369a8a33372f5b9f8ec700efc3f1a9f8dcf0fe731f3e3f1f9e8080000000000000000000000000000000000000000000000 __label__ 00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 @@ -2254,38 +2236,35 @@ __gff__ 0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __map__ -07072a2a2a072a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a07075376765107712a0007567656545176505107712a000756505551565676505055505055504c4b505051765107075451565676505050504c4b505051565676517651070754504c072a2a2a2a2a2a2a2a2a2a2a2a004b5156567655505050555050555050504c -072a0707072a0707070700001d07070707071d070707070707646060706060522a002a536070606070606060522a002a53606060606060606060707060606b49476c60706060527660606060606060606b49476c60606060607060605276606b492a07070707710000001d0000002a576c606060607070606060606070606b49 -2a07070700000007070700003c0d0d2a2d072a0771000771075f046f6f7e6f6f6f7f6f1b2b0404046f6f6f7e6f6f6f6f6f6f6f6f6f6f6d6f6f6f6f046f7b604947606e4e6f6f6d6f6f6f6d6f6f6f6d6f60494760046f046f6f6f6f6f04046f605907077107000000002c3d0000000007437e04787f6f6f7e6d7e6f7f7e6f604a -2a07077100000007070700000707070715163d070700070707077e787a7a797a78797a7a1b1c6f6f7f797a7a7a7a7a7a787a7a7a7a7a6d7a7a7a6f040404605947606f7b7a7a6d7a7a7a6d7a7a7a7b6f604947606f7b04047a7a7a7a04046f606907077707000000001d007100000007077f79797a7a784d5e7a7a7e7b6f6149 -2a07770d0d2d07070707000007710707252607070700070707077f797a786f6f6f6f7a7a7a7a7a7a7a7a7a6f6f6f7f6f7a787a7a7a7a6d7a7a7a6f6f6d6f606957606f7a7a7a5d6f6f6f5e7a7a7a7a6f604947606f6d7a7a6f6f6f7a7a7a6f605a0d0d3d07000071072a2a0000000007536f787a7879786d787879797f7e6049 -2a070707071d0707070700000707072a0d3d2a7107000707077d7e7f6f6f7e04046f6f6f6f7f6f6f7f6f6f7e0404046f0b0c6f7a7a7a5d4e7a7a7a7a6d6f605a68606f7a7a7a6f7a7a7a7a6f4d6e6e6e604947606f6d7a6f047a7a6f7a046f604907710707000000072a2a0000000058617e7a7a7a7a6f6f6f7a0b797a7f604a -2a070707071d07070707000000002a3c2d070707070071072a7460606060607060604266464666437060606060606070606b6f047a7a6f6f6f6f7a0b5d6e604a68616f4d4e6f6d7a7a7a4d6e5e7a7a04604947606f5d6f7a7a307a046f4d6e60725050515654504c07071d0000710047607f797a7a6f7a6d7a6f1b7a786f6149 -2a070771071d070707070000002a072a1d0707070700072a0707664646664440404107070771070744414666664445406361047a7a7a6f7a7a6f7a1b0c6f604947606e5e6d6f5d6e2e2f5e7a6f7a7a04604947606f7a6f04307a306e6f5e6f6b6060606060606b4907071d0000000047606f787a6f7a7a307a7a6f797a6f604a -4b505050517651505050505050505050505050505050505050505050505050505050505050505050505050507656545073606f7a7a7a6f7a7a6f6f6f6f6f605947606f7a5d6f7a7a3e3f7a7a6f7a7a6f604947606f7a6f6e6e3004046f7a0404046f6f6f6f6f6049072a2a0000070748617e797a6f7a3079304e7a79786f6049 -476c60606060606060606060606060606060606060606060606060706060606060606070606060606060706060606070606c6f7a7a7a6f6f6f6f7a7a7a6f606a47606f7a7a6f7a7a6d7a7a7a6f4d6e6e604957606f7a7a6f046d7a6f047a6f6f6f046f6f7a6f60492c2a2a0000070747606f787a6f7a7a30795d6f6e6e6e614a -57606f6f6d6f6f7a0b2b046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f6d6f6f1b1c6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f6d7a307a4d606a47606f7a7a7a6f6e5e7a7a6f7a6d7a6f604968606f7a7a046f6d6f7a7a7a6f6c60606b047a6f60493d07077100000047607f787a7a6f7a5d4e7e7a797a6f6059 -68606f7b5e7a7a7a0b2b1c047a7a7a04047a7a0b0c7a7a7a7a7a7a4d5e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f5d307a305e605a47606f7a7a4d5e6f6f6f6f7a4d5e7a6f604968616f7a7a7a7a6d7a7a7a7a6f60626360047a6f60725107070707544c47606f797a7a7a6f6f6f787a797a6f6069 -68616f7a7a7a7a0b2b1c7a7a7a7a6f04046f0b2b0c6f6f7a7a6f6f6d6f6f6f6f6f6f6f0b0c6f6f7a7a6f6f6f6f6f6f6f6f6f6f7a7a7a7a7a6f7a7a307a7a604947606f6f7a6d7a7a7a7a7a7a6d7a7a6f604947606f6f7a4d6e5e7a7a046f6f604947606f7a6f6b6060520707536b49486079797a7a7a7a7a6d7a7a7a7978605a -68606f7a7a7a7a1b1c7a7a7a7a6f6c6060606060606b6f7a7a6f6c606060606070606060606b6f7a7a6f6c6060606060606b6f6f7a6f6f04046f6e5e7e7e604a47606f6f6f6d6f6f6f6f6f6f6d6f6f6f604947606f6f6f6d6f6f6f6f6f6f6f604947606f7a6f6f04047e6f7f6f605947607878797a78797a6d787a787979614a -58606f7b4e7a7a7a7a7a7a047a6f60624040404063606f7a7a6f6062404040404040404063606f7a7a6f606240404040636b6b6f7a6f6c606060606060606c49476b60606060606060606060606b6f6f6049476b60606060606060606060606c4947606f7a7a7a04047a787b6f606948606f78046f6f6f7e6d7f7e6f6f7f604a -48606f6f6d6f6f6f6f6f6f6f046f60494b50505073606f7a7a6f60725050504c4b50505073606f7a7a6f60725050504c5b63606f7a0b6162404146444045405c5b404040404646444542444063606f6f60495b404646444542444040404040405c47606f7a7a7a7a7a7a7a7a7f605a476b6070606060606b6f6f6c6060606c49 -476b6060606060606060606060606c49476c6060606c6f7a7a6f6b6060606b49476c6060606c6f7a7a6f6b6060606b494b73616f7a1b6072505051565450504c00000007070707070707545073606f6f607250505050505050505050505050505073606e6e6e6e4e7a7a04046f60495b4040404040456360046f60624040405c -5b40404046664440404040404040405c47600404046f6f7a7a6f6d6f0404604947600404046f6f7a7a6f046f6f6f6049476c6c6f7a6f6b606060606060606b49077700076452565653537060606c6f6f6b60606060606060606060606060606060606c6f7a7a7a6d7a7a7a046f6072515656765050507360046f60725050504c -4b50505050505050505050517651505073600404046f7a7a7a7a5d6e0404604948606f7a7a7a7a7a7a7a7a047b6f604947606f6f7a6f6f6f6f042b2b04046049001d0007436f6f6f6f6f6f6f6f6f6f6f6f6f1b1c6f6f6f0404046f6f6f6f6f6f307a7a7a7a7a7a6d7a7a7a7a6f6b6060606060606060606c6f6f6b6060606b49 -476c6060606060606060606060606060606c6f6f6f6f7a7a7a7a7a6f0b0b604948616f7a7a7a7a7a7a7a7a7a5d6e604957606f7a7a7a7a7a7a7a1b2b2b0c6059071d0007077a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f0404046f7a7a7a7a04307a7a7a7a7a7a6d7a7a7a7a6f6f6f6f6f0404046f6f6f6f6f6f6f6f6f6f6149 -47606f6f6f6f6f04046f6d6f6f6f6f6f6f6f6f7a7a7a7a7a7a7a7a0b2b2b604947606f7b4e7a7a7a7a7a7a047a6f604968600c7a7a7a7a7a7a7a7a1b2b6f6049073c2d07077a7a7a7a7a7a047a047a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a30047a7a7a7a7a045d4e7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b6f614a -47606f7b7a7a7a7a7a7a6d7a7a6f6f6f6f6f6f6f6f6f6f6f6f6f6f0b2b2b604957606f6f6d6f6f6f6f6f6f6f046f604968612b0c6f6f6f6f6f6f6f7a1b1c604907711d07536f6f6f6f6f0404046f6f6f6f6f6f6f6f0b0c6f6f6f6f6f6f6f6f306f7a7a6f6f6f04046d6f6f6f6f6f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604a -47606e6e6e6e6f6f6f6f5e7a7a6f6c6060606060606060606060606060606c49586b6060607060606060706060606c49586b606060706060606b6f7a7a6f604907001d077470606060706060606060606060606060606060606b6f04606263606f7a7a6f6c606060606060606060606060606060606060606042000043606c49 -48606f7a7a307a7a7a7a307a7a6f60624040404046664440404040404040405c5b40406644404040404040404040405c5b4040404040404063600c7a7a6f605907001d0707664644404040404040404040404040404040406361046f605947606f7a7a6f6062404040464644454244404040404040404040410707710044405c -48606f7a6f6d7a7a7a7a7a6f7a6f60494b505050505176515050505050505050505050505050504c4b5050505051077107567656545050507360040c7a6f606a07071d07070707073c0d0d0d2d000000000000000000000073606f6f6a004760047a7a6f604900072a2a0000000000000000710000000000000000000000072a -48616f7a6f5d6e6f6f7a4d6e6e6e6049476c60606060606060606060606060606060606060606b49476c6060606052565360706060606060606c1c7a7a6f606907001d0707070707070707071d6452765352765656765360606c046f60655760047a7a6f604900072a2a0000000071000000000000000071000000000000072a -47606f7a6f7a7a6f6f6e5e6f7a6f604947600404046f6f6f6f6f6f6f6f0404046f6f6f6f6f6f604947606f6f04046f6f6d6f6f6f0404046f6f6f6f7a7a0b606907711d0707070707070707071d430404047e6f7f6f7e6f6d7f6f7a04044207436f7a7a6f60490007071d00004b5051565676505051765150505050505050504c -57606f7a6f7a7a6d7a7a7a6f7a6f6072736004047a7a7a7a7a306f6f7a7a7a7a7a7a7a7a6f6f604947606f7a0b0c7a7a6d7a7a7a7a7a7a7a7a7a7a7a7a1b604907001d0707070771070707071d686f7a79787a7a04787a6d797a7a7a04072c0d6f7a7a6f60490071071d0000476c607060606060606070606060606070606b49 -68616f7a7a306e5e7a7a307a7a6f6b60606c6f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a0b6f604947606f7a7a1b0c6f5e7a7a7a7a7a7a7a7a4d6e6e6e6e60490d0d3d0707070707070707071d437f7a7b7a7a0479787a5d6e6e7b7a6f0d3d076f7a046f60490007772a000047600404046f6d6f6f6f6f6f6f6d6f6f6f6f6059 -68606f7a4d5e6f6f6f6f7a7a7a6f6f6f6f6f6f7a7a7a7a7a7a7a7a7a7a7a6f6f6f7a7a0b2b6f604947606f7a6f301b306f0c7a7a7a7a7a7a7a6d7a7a7a6f60490707070707070707070707071d677e786d7a0404787a7a797a7a78797e5207536f7a7a6f6049002c2a2a000047600404047a6d7a7a7a7a7a4d5e7a7a6f6f6049 -58606f7b6d7a7a7a7a7a7a7a7a6f6f6f6f6f6f7a7a7a7a7a7a306f7a7a6f7a7a7a6f7a0b2b1c604947606e6e6e6e7b0b2b1c7a7a7a7a7a7a7a6d7a7a7a6f60490707070707070707077107073c537f6f6d6f6f6f7f7e6f6f7f7e6f7f7f6a58606f7a7a6f6049001d0707000047606f7a7a7a5d4e7a7a7a7a6d7a7a7a7a6f6049 -48606f6f6d6f6f6f6f6f6f6f04046c60606b6f6f6f6f6f6f6f6f6f7a7a6f7a6f7a6f7a1b1c0460494760041b0c6f306f1c7a7a7a7a7a7a7a6f6d6f7a4d6e606907070707070707070707070707744246436646434246466643426646437547606f7a7a6f607250505050505073606e6e6e4e7a5d6f6f6f6f5e7a7a7a7a6f6049 +0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140ab00232612ca05028c09880e5480a2942022b020280aa9409905bc0012e0767039281eac0b480f0682801a01c00000ec018c0f005503990c06038180c0521500054538041c02ca018d00073840788d649239b204c8768 +0b3c045203c90154359260b6871004174a00642a2bc07120747022480f2c070a0fe83c06834542d84811c400073881cbe04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e14180c002c1ec014c0e433f010f07f82c91b4fc6f3f9f8de6025459 +ca13870ff488298542c3d8851443ccfc783d1e8f27a3c1e4f47a0d87210c500728009c050a01046d80614178808602c91cc06f3dc03d602667b83fa1bcf6040240b4e0fe6690381cef07f40e8020cea7f3cc1448f04d2f1e8f47e3d9bcc311e23b8341a2d8bfac3d480e25134628e31e707123c43c4e0eeb07038861c20160fe +90ed33495cc102032ec4a0a0a25094283fa4278a070a6f301681a0d1ec7688e3266287fa14cde7886991e0da783c1e4f27f3f18233cb1f848c220a81a3d1544bca42647d3f4498e2f0918328011c0308086f058320de84e882a41788d107f485221bc9a6e371ba0fec6f88011bcf517f224c350e3c0707f52c184fd0ca63d02c +f27a3fc5fd20ff121f21e4429615c474922b908cc46231988723159191c169208085d371809468309bc9a4e87819e89a6e8a990122fe85d89a10c20fa44d37180e530d3983181e4fe4710011cc11c8337c7008de1b8af145fce4d6507f79c414429662a7076322100804195f181c0e4420918cc6622114806330c768e32846f3 +d06c1926d437178da6f2e9b85c2f2f4414e0feb21b2181e8606e37978de6b98abc1fd00107f28bf942750f46f3cc6b48953bf29e01cee4e007f013f2850188a0738b7bcc3124fe91fd23d1e87c3f84aac1fd8dc6e184ccca502530958c3147fcc0e0724184fd07623d0c0f230271e8f2789969ceb56009f3c16803dcecd0e060 +36495d24bce6a8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160 +c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be75 +c68807e4dfca5bc555a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c2853b980d84923cd85a136f6112808dc080aa40959023c56708d2489bf9592d0db5fd237d1fca02051688a571ce3ce6c691b32ba6b403f0b70 +9c305984c44020918884022900b85b85819188c4422908890b73b3f916e01cf0be42e4a816c7887f91711ae2a2f16b485ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e +9ae6770001cc8522b158a653c1691b0df01988c155b04ee60778d23962958e707f38afc53f1aa0c90192a5c1db4ccdd07f38d815c01c0975d693f9dff48578162301270ac40721d08f286494438ab414e36645730a96c56c42bae8c500b05c1fca79ab7fc4b3795375701481b158ac1985e283f901e84ff81b4bdc2581ca0ff5 +4ddcc66998665ab8aa49f73dfc20162b15e135663f54dd2a4eb4200eebc7731399fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09da33cd8492c5d04ecf4d00fca51a6178b56a474d4e1dab5e81720 +4abe1d2e6add1a42000008743f292fa48912f24747f5987dcf7ca63b332f6a51ad894b4bc7b35333502ca001d8272bceb5355a82974e42c2b11c40044201725be78be4ca21d1fdb021f3278e07170312682a0cbbf9eebdf67640d16eb9ce006d40b33563b8b30e73252cff36c20f71670287038aa5be78be2b19266f5301ffbd +5c2915b818957a4b5816b78f86adc39432148ec53291db93a5c252366df08ca5783ff41ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06f +caa16fdc8f07a36f0b8c0807160360feda632cfe174ad32b53cff3c16a0c35747a123d171657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c +6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c0239acc1000a36188 +c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c +21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c +8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d +291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4 +f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513 +bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3 +dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573 +051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72 +d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab69 +52540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50 +771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e20734 +1fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c4884 +1334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 __sfx__ 151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 diff --git a/beta_v0.2.2_copy.p8 b/beta_v0.2.2_copy.p8 deleted file mode 100644 index 70ec74f..0000000 --- a/beta_v0.2.2_copy.p8 +++ /dev/null @@ -1,2310 +0,0 @@ -pico-8 cartridge // http://www.pico-8.com -version 41 -__lua__ - ---[[ -CORTEX PROTOCOL - by Emanuele Bonura - itch: https://izzy88izzy.itch.io/ - github: https://github.com/EBonura/CortexProtocol - instagram: https://www.instagram.com/izzy88izzy/ - minified with: https://thisismypassport.github.io/shrinko8/ - -How to Play: -* Use arrow keys to move -* Press X to use your selected ability -* Press O to open the ability menu and switch between abilities -* Interact with terminals using X when prompted - -Main Objective: -* Activate all terminals and reach the extraction point - -Optional Objectives: -* Collect data fragments to restore health and earn credits -* Eliminate all enemy units -]] - --- MAP COMPRESSION ----------------------- -function decompress_to_memory(compressed_data, dest_address) - local byte_index, bit_index, dest_index = 1, 0, dest_address - local function read_bits(num_bits) - local value = 0 - for _ = 1, num_bits or 1 do - if byte_index > #compressed_data then return end - value = bor(shl(value, 1), band(shr(compressed_data[byte_index], 7 - bit_index), 1)) - bit_index += 1 - if bit_index == 8 then - bit_index = 0 - byte_index += 1 - end - end - return value - end - - while true do - if read_bits() == 0 then - local byte = read_bits(8) - if not byte then return end - poke(dest_index, byte) - dest_index += 1 - else - local distance = dest_index - read_bits(12) - 1 - local length = read_bits(4) + 1 - for _ = 1, length do - poke(dest_index, peek(distance)) - distance += 1 - dest_index += 1 - end - end - end -end - -function decompress_current_map() - local i = current_mission > 2 and 3 or 1 - decompress_to_memory(map_data[i], 0x2000) - decompress_to_memory(map_data[i + 1], 0x1000) -end - - --- HELPER FUNCTIONS ----------------------- -function reset_pal(_cls) - pal() - palt(0) - palt(14,true) - if _cls then cls() end -end - -function check_tile_flag(x, y, flag) - return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) -end - -function stringToTable(str) - local tbl = {} - for pair in all(split(str, "|", false)) do - add(tbl, split(pair, ",", true)) - end - return tbl -end - -function dist_trig(dx, dy) - local ang = atan2(dx, dy) - return dx * cos(ang) + dy * sin(ang) -end - -function draw_shadow(circle_x, circle_y, radius, swap_palette) - local swapped_palette = {} - for i = 0, 15 do - for j = 0, 15 do - local index = (i << 4) | j - swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] - end - end - - -- Pre-compute squared radius - local radius_squared = radius * radius - - -- calculate the top and bottom y of the circle - local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) - - -- Function to swap palette for a line - local function swap_line(y, start_x, end_x) - local line_start_addr = 0x6000 + y * 64 - for i = 0, start_x >> 1 do - poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) - end - for i = (end_x >> 1) + 1, 64 do - poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) - end - end - - -- Swap palette for top and bottom sections - for y = 0, top_y do swap_line(y, 127, 127) end - for y = bottom_y, 127 do swap_line(y, 127, 127) end - - -- Pre-calculate values for the circle intersection - for y = top_y + 1, bottom_y - 1 do - local dy = circle_y - top_y - (y - top_y) - local dx = sqrt(radius_squared - dy * dy) - swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) - end -end - -function display_logo(x_cortex, x_protocol, y) - spr(224, x_protocol, y + 12, 9, 2) - spr(233, x_cortex, y, 7, 2) -end - -function count_remaining(t, cond) - local c = 0 - for i in all(t) do - c += cond(i) and 0 or 1 - end - return c -end - -function count_remaining_fragments() - return count_remaining(data_fragments, function(f) return f.collected end) -end - -function count_remaining_enemies() - return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) -end - -function count_remaining_terminals() - return count_remaining(terminals, function(t) return t.completed end) -end - - --- CAMERA ----------------------- -gamecam = {} -gamecam.__index = gamecam - -function gamecam.new() - return setmetatable({ - x = 0, - y = 0, - }, gamecam) -end - -function gamecam:update() - self.x += (player.x - self.x - 64) * 0.2 - self.y += (player.y - self.y - 64) * 0.2 - - -- shaking effect - if count_remaining_terminals() == 0 then - self.x += rnd(4) - 2 - self.y += rnd(4) - 2 - end - - camera(self.x, self.y) -end - --- TRANSITION ----------------------- -transition = {} - -function transition.new() - return setmetatable({ - active=false, - t=0, - duration=8, - closing=true - },{__index=transition}) -end - -function transition:start() - self.active, self.t, self.closing = true, 0, true -end - -function transition:update() - if self.active then - self.t += self.closing and 1 or -1 - - if self.closing and self.t == self.duration then - self.closing = false - return true - elseif not self.closing and self.t == 0 then - self.active = false - end - end -end - -function transition:draw() - if self.active then - local size = max(1, flr(16 * self.t/self.duration)) - for x = 0, 127, size do - for y = 0, 127, size do - rectfill(x, y, x+size-1, y+size-1, pget(x, y)) - end - end - end -end - --- TEXT PANEL ----------------------- -textpanel = {} - -function textpanel.new(x, y, height, width, textline, reveal, text_color) - return setmetatable({ - x=x, - y=y, - height=height, - width=width, - textline=textline, - selected=false, - expand_counter=0, - active=true, - x_offset=0, - move_direction=0, - max_offset=width, - line_offset=0, - reveal=reveal, - char_count=0, - text_color=text_color - }, {__index=textpanel}) -end - -function textpanel:draw() - if not self.active then return end - - local dx = cam.x + self.x + self.x_offset - self.expand_counter - local dy = cam.y + self.y - local w = self.width + self.expand_counter * 2 - local dy2 = dy + self.height - - rectfill(dx - 1, dy - 1, dx + 2, dy2 + 1, 3) - rectfill(dx + w - 2, dy - 1, dx + w + 1, dy2 + 1, 3) - rectfill(dx, dy, dx + w, dy2, 0) - - if self.selected then - local line_x = dx + (self.line_offset % (w + 1)) - line(line_x, dy, line_x, dy2, 2) - end - - local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline - print(display_text, dx + 2, dy + 2, self.text_color or (self.selected and 11 or 5)) -end - -function textpanel:update() - self.expand_counter += self.selected and (self.expand_counter < 3 and 1 or 0) or (self.expand_counter > 0 and -1 or 0) - - self.x_offset += self.move_direction * self.max_offset / 5 - if self.x_offset <= -self.max_offset or self.x_offset >= 0 then self.move_direction *= -1 end - - self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 - - if self.reveal and self.char_count < #self.textline then self.char_count += 2 end -end - --- TARGETING ----------------------- -targeting = {} - -function targeting.new(owner) - return setmetatable({ - owner = owner, - target = nil, - rotation = 0, - max_rect_size = 32, - rect_size = 12, - target_acquired_time = 0, - }, {__index=targeting}) -end - -function targeting:update() - local closest_dist, closest_target = self.owner.attack_range, nil - for e in all(entities) do - if e != self.owner and self.owner.subclass == "player" != (e.subclass == "player") then - local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) - if dist < closest_dist and self:has_line_of_sight(e) then - closest_dist, closest_target = dist, e - end - end - end - - if closest_target != self.target then - self.target = closest_target - if self.target then - self.target_acquired_time = time() - self.rect_size = self.max_rect_size - end - end - - if self.target then - local t = mid(0, time() - self.target_acquired_time, 1) - self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t - end - - self.rotation += 0.03 -end - -function targeting:has_line_of_sight(t) - local x,y = self.owner.x+self.owner.width/2, self.owner.y+self.owner.height/2 - local x1,y1 = t.x+t.width/2, t.y+t.height/2 - local dx,dy = x1-x, y1-y - local step = max(abs(dx), abs(dy)) - - for i=0,step do - if check_tile_flag(x+dx*i/step, y+dy*i/step) then return end - end - return true -end - -function targeting:draw() - if not self.target then return end - local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 - - for i = 0, 3 do - local angle = self.rotation + i * 0.25 - line(x + cos(angle) * half_size, y + sin(angle) * half_size, x + cos(angle + 0.25) * half_size, y + sin(angle + 0.25) * half_size, 3) - end -end - - --- ABILITY MENU ----------------------- -ability_menu = { - panels = {}, - last_selected_ability = 1 -} - -function ability_menu:open() - self.panels = {} - for i, a in ipairs(player.abilities) do - local p = textpanel.new(37, 30 + (i - 1) * 16, 10, 54, a.name) - p.ability_index = i - add(self.panels, p) - end - - add(self.panels, textpanel.new(13, 94, 20, 102, "")) - - self.active = true - if #self.panels > 1 then - self.panels[self.last_selected_ability].selected = true - end -end - -function ability_menu:update() - if not self.active then return end - - local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) - if change != 0 then - self.panels[self.last_selected_ability].selected = false - self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 - local current_panel = self.panels[self.last_selected_ability] - current_panel.selected = true - player.selected_ability = current_panel.ability_index - sfx(19) - end - - foreach(self.panels, function(p) p:update() end) - - self.panels[#self.panels].textline = - "dATA SHARDS LEFT: " .. count_remaining_fragments() .. - "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. - "\niNACTIVE TERMINALS: " .. count_remaining_terminals() -end - -function ability_menu:draw() - if self.active then - for p in all(self.panels) do - local ability = player.abilities[p.ability_index] - if ability then - p.text_color = ability.remaining_uses > 0 and (p.selected and 11 or 5) or 2 - end - p:draw() - end - end -end - -ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end -ability_menu.close = function(self) self.active = false end - - --- MAIN ----------------------- -function _init() - cam = gamecam.new() - - -- Missions - MISSION_BRIEFINGS = { - "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", - "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", - "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", - "FACILITY 800a:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" - } - - mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 - - v = 2040 - -- Load compressed map from map data - map_data = { - -- 4 Levels - pack(peek(0x2000, v)), - pack(peek(0x2000 + v, 1585)), - pack(peek(0x1000, 1788)), - pack(peek(0x1000 + 1788, 1402)), - -- Logo - pack(peek(0x1000 + 1788 + 1402, 243)) - } - - -- Decompress Logo - decompress_to_memory(map_data[5], 0x1C00) - - -- -- Load map 1 for Intro - decompress_current_map() - - SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ - 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| - 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| - 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| - 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) - - entity_abilities = { - dervish = {"mACHINE gUN"}, - vanguard = {"rIFLE bURST"}, - warden = {"mISSILE hAIL"}, - cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, - quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} - } - - entity_colors = { - dervish = 15, - vanguard = 13, - warden = 1, - player = 7, - preacher = 11, - cyberseer = 6, - quantumcleric = 1 - } - - states = {} - for name in all(STATE_NAMES) do - states[name] = { - init = _ENV["init_" .. name], - update = _ENV["update_" .. name], - draw = _ENV["draw_" .. name] - } - end - - trans = transition.new() - player = entity.new(0, 0, "bot", "player") - change_state("loadout_select", true) -end - - -function _update() - if trans.active then - if trans:update() then - current_state = next_state - current_state.init() - next_state = nil - end - else - current_state.update() - end -end - -function _draw() - current_state.draw() - trans:draw() - printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) -end - -function change_state(new_state_name, skip_transition) - local new_state = states[new_state_name] - - if skip_transition ~= true and not trans.active then - sfx(20) - next_state = new_state - trans:start() - else - current_state = new_state - current_state.init() - end -end - --- INTRO ----------------------- -function init_intro() - music(05) - intro_counter, intro_page, x_cortex, x_protocol = 0, 1, -50, 128 - - intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) - controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ WEAPON MENU\n❎ ATTACK/USE", true) - - intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true - - intro_pages = { - "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", - "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", - "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", - "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" - } -end - -function update_intro() - intro_counter += 1 - x_cortex, x_protocol = min(15, x_cortex + 2), max(45, x_protocol - 2) - - if intro_counter == 30 then sfx(20) end - - if btnp(❎) and intro_counter > 30 then - sfx(19) - if not intro_text_panel.active then - intro_text_panel.active, controls_text_panel.active = true, true - intro_text_panel.textline = intro_pages[intro_page] - else - intro_page += 1 - if intro_page <= #intro_pages then - intro_text_panel.textline = intro_pages[intro_page] - intro_text_panel.char_count = 0 - else - change_state("mission_select") - end - end - end - - intro_text_panel:update() - controls_text_panel:update() -end - -function draw_intro() - reset_pal(true) - map(unpack(INTRO_MAP_ARGS)) - draw_shadow(128,128,0, SWAP_PALETTE_DARK) - - if sin(t()) < .9 then circfill(63,64, 3, 2) end - - local y_logos = intro_text_panel.active and 0 or 30 - display_logo(x_cortex, x_protocol, y_logos) - - intro_text_panel:draw() - controls_text_panel:draw() - if intro_counter > 60 then print("PRESS ❎ TO CONTINUE", 24, 118, 11) end -end - --- MISSION SELECT ----------------------- -function init_mission_select() - music(0) - cam.x, cam.y = 0, 0 - camera(0,0) - - info_panel = textpanel.new(50,35,69,76,"", true) - LEVEL_SELECT_ARGS = stringToTable([[ - 4,35,9,38,MISSION 1,true| - 4,50,9,38,MISSION 2,true| - 4,65,9,38,MISSION 3,true| - 4,80,9,38,MISSION 4,true]] - ) - - level_select_text_panels = {} - for arg in all(LEVEL_SELECT_ARGS) do - add(level_select_text_panels, textpanel.new(unpack(arg))) - end - - show_briefing = true -end - -function update_mission_select() - local prev = current_mission - - if btnp(⬆️) or btnp(⬇️) then - current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 - info_panel.char_count = 0 - sfx(19) - elseif btnp(⬅️) or btnp(➡️) then - show_briefing = not show_briefing - info_panel.char_count = 0 - sfx(19) - elseif btnp(❎) then - change_state("loadout_select") - elseif btnp(🅾️) then - change_state("intro") - end - - foreach(level_select_text_panels, function(t) t:update() end) - info_panel:update() -end - -function draw_mission_select() - reset_pal(true) - map(unpack(INTRO_MAP_ARGS)) - draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) - display_logo(15, 45, 0) - - for i,panel in ipairs(level_select_text_panels) do - panel.selected = (i == current_mission) - panel:draw() - end - - if show_briefing then - info_panel.textline = MISSION_BRIEFINGS[current_mission] - else - local mission = mission_data[current_mission] - info_panel.textline = "STATUS:\n\n" .. - "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. - "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. - "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") - end - info_panel:draw() - - color(11) - print("⬆️ ⬇️ CHANGE MISSION", 25, 108) - print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) - print(" ❎ START MISSION", 25, 122) -end - --- LOADOUT SELECT ----------------------- -function init_loadout_select() - loadout_panels, count_panels = {}, {} - for i=1,5 do - add(loadout_panels, textpanel.new( - i<5 and 10 or 34, - i<5 and 20+(i-1)*20 or 98, - 9, - i<5 and 56 or 56, - i==5 and "bEGIN mISSION" or "", - true - )) - if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end - end - selected_panel = 1 -end - -function update_loadout_select() - local has_weapon = false - for a in all(player.abilities) do - if a.remaining_uses > 0 then - has_weapon = true - end - end - - if btnp(⬆️) or btnp(⬇️) then - sfx(19) - selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 - end - - if btnp(🅾️) then - change_state("mission_select") - elseif selected_panel <= 4 then - local a = player.abilities[selected_panel] - local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 - - if change < 0 and a.remaining_uses >= 25 - or change > 0 and credits >= 25 * a.cost then - sfx(19) - a.remaining_uses += change - credits -= change * a.cost - end - elseif selected_panel == 5 and btnp(❎) and has_weapon then - change_state("gameplay") - return - end - - for i, p in ipairs(loadout_panels) do - p.selected = (i == selected_panel) - if i <= 4 then - local a = player.abilities[i] - p.textline = a.name - count_panels[i].textline = a.remaining_uses.." AMMO" - else - p.active = has_weapon - end - p:update() - end - - foreach(count_panels, function(p) p:update() end) -end - -function draw_loadout_select() - reset_pal(true) - map(unpack(INTRO_MAP_ARGS)) - draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) - print("cREDITS: "..credits, 10, 10, 7) - - foreach(loadout_panels, function(p) p:draw() end) - foreach(count_panels, function(p) p:draw() end) - - local info_text = "⬆️⬇️: SELECT\n" - info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or - selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" - print(info_text, 10, 115, 11) -end - --- GAMEPLAY ----------------------- -function init_gameplay() - decompress_current_map() - music(0) - player_hud = player_hud.new() - entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 - - local mission_entities = { - [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], - [[0,0,bot,player|528,144,bot,dervish|624,160,bot,vanguard|688,288,bot,dervish|616,48,preacher,cyberseer|824,136,bot,warden|680,96,bot,dervish|920,32,bot,dervish|984,96,bot,warden|896,160,bot,vanguard|904,312,preacher,quantumcleric|976,248,bot,vanguard|800,376,bot,warden|728,336,bot,vanguard|816,320,bot,vanguard|608,360,bot,warden|968,1200,bot,vanguard]], - [[0,0,bot,player|240,416,bot,warden|88,336,bot,dervish|160,368,bot,vanguard|24,416,bot,warden|216,104,bot,vanguard|256,40,bot,dervish|296,72,bot,dervish|136,80,preacher,cyberseer|32,88,bot,dervish|32,32,bot,dervish|40,160,bot,warden|344,344,bot,vanguard|456,336,preacher,quantumcleric|368,416,bot,dervish|416,128,bot,vanguard|344,136,bot,vanguard|424,96,preacher,quantumcleric|352,240,bot,vanguard|432,264,bot,vanguard|496,152,bot,warden]], - [[0,0,bot,player|880,412,bot,dervish|760,408,bot,dervish|696,424,bot,vanguard|600,360,bot,warden|552,400,bot,warden|592,256,bot,vanguard|528,280,preacher,cyberseer|528,208,bot,vanguard|560,168,bot,vanguard|688,296,bot,dervish|688,360,bot,dervish|760,304,bot,warden|912,344,preacher,quantumcleric|848,344,bot,dervish|712,192,bot,warden|776,200,bot,warden|888,192,bot,warden|984,184,preacher,cyberseer|992,256,bot,vanguard|640,32,bot,vanguard|632,104,bot,vanguard|664,32,bot,dervish|664,104,bot,dervish|704,32,bot,dervish|704,104,bot,dervish|896,40,preacher,quantumcleric|968,96,preacher,cyberseer]] - } - - for e in all(stringToTable(mission_entities[current_mission])) do - if e[4] != "player" then - add(entities, entity.new(unpack(e))) - end - end - - boundaries = stringToTable("0,0,0,0,64,56|64,0,0,0,128,56|0,0,0,0,64,56|64,0,0,0,128,56")[current_mission] - - for map_y = boundaries[2], boundaries[6] do - for map_x = boundaries[1],boundaries[5] do - local tile, tile_x, tile_y = mget(map_x,map_y), map_x*8, map_y*8 - if fget(tile, 6) then - add(barrels, barrel.new(tile_x, tile_y)) - elseif fget(tile, 5) then - add(data_fragments, data_fragment.new(tile_x, tile_y)) - elseif fget(tile, 4) then - add(terminals, terminal.new(tile_x+4, tile_y-4)) - elseif fget(tile, 7) then - player_spawn_x, player_spawn_y = tile_x, tile_y - player.x, player.y = tile_x, tile_y - player.health = player.max_health - add(entities, player) - end - end - end - - local tutorial_terminals = { - [[112,48,MOVE: ⬅️➡️⬆️⬇️|192,48, ATTACK: ❎|40,-8, FRAGMENTS RESTORE HP|264,-2, WEAPONS MENU: 🅾️|368,-2, DEFEAT ENEMY]], - "", -- No tutorials for mission 2 - "", -- No tutorials for mission 3 - "" -- No tutorials for mission 4 - } - - if tutorial_terminals[current_mission] != "" then - for args in all(stringToTable(tutorial_terminals[current_mission])) do - add(terminals, terminal.new(args[1], args[2], nil, args[3])) - end - end - - local door_terminals = { - [[444,130,472,80,red|354,66,248,368,green]], - [[808,252,712,48,green|824,252,952,56,red|840,252,568,376,blue]], - [[184,2,160,392,green|392,282,144,224,red|360,170,320,408,blue]], - [[620,2,552,304,green|652,2,904,280,red|684,2,984,272,blue]] - } - for args in all(stringToTable(door_terminals[current_mission])) do - create_door_terminal_pair(unpack(args)) - end - - game_ability_menu = ability_menu.new() - game_minigame = minigame.new() -end - -function update_gameplay() - if game_minigame.active then - game_minigame:update() - else - if btn(🅾️) and not game_ability_menu.active then - game_ability_menu:open() - elseif not btn(🅾️) and game_ability_menu.active then - game_ability_menu:close() - end - - if game_ability_menu.active then - game_ability_menu:update() - else - foreach(entities, function(e) e:update() end) - foreach(terminals, function(t) t:update() end) - foreach(barrels, function(b) b:update() end) - - for i = #particles, 1, -1 do - local p = particles[i] - p:update() - if p.lifespan < 0 then del(particles, p) end - end - - cam:update() - player_hud:update() - end - end -end - -function draw_gameplay() - reset_pal(true) - map(0,0,0,0,128,56) - - for group in all({terminals, data_fragments, entities, particles, barrels}) do - foreach(group, function(e) e:draw() end) - end - - foreach(doors, function(d) d:draw() end) - - if player.health > 0 then - draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) - end - - player_hud:draw() - game_ability_menu:draw() - game_minigame:draw() - - for t in all(terminals) do - if t.interactive and not game_minigame.active then - t.panel:draw() - end - end - - -- check mission status - if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then - local message, color, prompt - - if player.health > 0 then - message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" - - -- Update mission completion status - mission_data[current_mission][1] = 1 - mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 - mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 - else - message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" - end - - draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) - print_centered(message, player.x, player.y - 6, color) - print_centered(prompt, player.x, player.y + 2, 7) - - if btnp(🅾️) then - change_state("mission_select") - end - end -end - -function print_centered(t,x,y,c) - print(t,x-#t*2,y,c) -end - --- PARTICLE --------------- -particle = {} - -function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) - local p = setmetatable({ - x=x, - y=y, - vx=vx, - vy=vy, - color=color, - lifespan=lifespan, - size=size, - behavior=behavior or "default", - owner=owner - }, {__index=particle}) - - if behavior == "missile" then - p.orbit_time = 15 - p.orbit_angle = rnd() - p.orbit_radius = 5 + rnd(10) - p.speed = 1 - p.max_speed = 3 - p.damage = 15 - p.explosion_radius = 16 - p.explosion_damage = 4 - p.direction = rnd() - elseif behavior == "plasma" then - p.damage = 75 - p.explosion_radius = 16 - p.explosion_damage = 10 - else - p.damage = 3 - p.speed = behavior == "machinegun" and 6 or 8 - end - - return p -end - -function particle:check_collision_and_damage() - -- Check collision with barrels first - for b in all(barrels) do - if self:collides_with(b) then - b:take_damage(self.damage) - self:create_impact_particles() - return true - end - end - - -- Check collision with solid tiles - if check_tile_flag(self.x, self.y) then - self:create_impact_particles() - return true - end - - -- Check collision with entities - for e in all(entities) do - if e != self.owner and self:collides_with(e) then - e:take_damage(self.damage) - self:create_impact_particles() - return true - end - end -end - -function particle:update() - local _G, _ENV = _ENV, self - lifespan -= 1 - - if behavior == "missile" then - if orbit_time > 0 then - -- Orbiting phase - orbit_time -= 1 - orbit_angle += 0.02 - x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius - y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius - else - -- Movement phase - if target and target.health > 0 then - -- Homing behavior - local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y - if _G.dist_trig(dx, dy) > 0 then - direction = _G.atan2(dx, dy) - speed = _G.min(speed + 1, max_speed) - end - else - -- Scattering - speed = _G.min(speed + 0.05, max_speed) - direction += _G.rnd(0.1) - 0.05 - end - - -- Apply movement - vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed - x += vx - y += vy - - -- Check for collision using the new method - if self:check_collision_and_damage() then - self:explode() - lifespan = -1 - end - end - - -- Explode if lifespan is over - if lifespan <= 0 then - self:explode() - end - else - x += vx - y += vy - - if behavior == "machinegun" or behavior == "rifle" then - if self:check_collision_and_damage() then lifespan = -1 end - elseif behavior == "plasma" then - if self:check_collision_and_damage() then - self:explode() - lifespan = -1 - end - else - vy += 0.03 - end - end -end - -function particle:collides_with(obj) - local _ENV = self - return x > obj.x and x < obj.x + obj.width and - y > obj.y and y < obj.y + obj.height -end - -function particle:explode() - -- Create explosion particles - for i = 1, 10 do - local angle, speed = rnd(), 0.5 + rnd(1) - add(particles, particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9)) - end - - -- Apply damage to nearby entities and barrels - for e in all(entities) do - if e != self.owner then - self:apply_explosion_damage(e) - end - end - - for b in all(barrels) do - self:apply_explosion_damage(b) - end - - sfx(28) -end - -function particle:apply_explosion_damage(obj) - local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) - if dist < self.explosion_radius then - obj:take_damage(self.explosion_damage * (1 - dist/self.explosion_radius)) - end -end - -function particle:create_impact_particles() - for i = 1, 3 do - local angle, speed = rnd(), 0.5 + rnd(1) - add(particles, particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 10 + rnd(5), 1, 6)) - end -end - -function particle:draw() - circfill(self.x, self.y, self.size, self.color) -end - - --- ENTITY ----------------------- -entity = {} - -function entity.new(x, y, base_class, subclass) - local is_preacher = base_class == "preacher" - local new_entity = setmetatable({ - -- Position and movement - x = x, - y = y, - vx = 0, - vy = 0, - width = is_preacher and 16 or 8, - height = is_preacher and 24 or 8, - max_speed = is_preacher and 3 or 4, - acceleration = 0.8, - deceleration = 0.9, - turn_speed = 0.3, - - -- Entity type - base_class = base_class, - subclass = subclass, - - -- Sprite and animation - current_sprite = is_preacher and 8 or 1, - bot_sprite_sets = { - idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, - walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} - }, - - -- Target and following - target_x = x, - target_y = y, - last_direction = "down", - facing_left = false, - - -- Physics - mass = 1, - - -- Ability system - abilities = {}, - selected_ability = 1, - - -- AI-related properties - state = "idle", - last_seen_player_pos = {x = nil, y = nil}, - alert_timer = 0, - max_alert_time = 180, - - idle_timer = 0, - - -- Poison-related properties - poison_timer = 0, - - -- Flash effect property - flash_timer = 0, - }, {__index=entity}) - - new_entity.targeting = targeting.new(new_entity) - - local ability_data = "15,100,rIFLE bURST,rifle_burst,20|30,200,mACHINE gUN,machine_gun,25|45,50,mISSILE hAIL,missile_hail,50|60,25,pLASMA cANNON,plasma_cannon,75" - - for i, a in ipairs(stringToTable(ability_data)) do - add(new_entity.abilities, { - index = i, - cooldown = a[1], - name = a[3], - action = new_entity[a[4]], - current_cooldown = 0, - remaining_uses = subclass != "player" and a[2] or 0, - cost = a[5] - }) - end - - local entity_data_str = [[ - 15,dervish,50,50,60,100| - 13,vanguard,70,70,50,120| - 1,warden,100,100,70,200| - 7,player,400,400,70,0| - 11,preacher,80,80,80,280| - 6,cyberseer,160,160,80,300| - 1,quantumcleric,170,170,70,320 - ]] - - for d in all(stringToTable(entity_data_str)) do - if d[2] == subclass then - new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) - end - end - - return new_entity -end - -function entity:update() - -- Common updates for all entities - self:apply_physics() - self.targeting:update() - - -- Update cooldowns and poison in one pass - for ability in all(self.abilities) do - ability.current_cooldown = max(0, ability.current_cooldown - 1) - end - - if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then - self.poison_timer = (self.poison_timer + 1) % 6 - if self.poison_timer == 5 then self:take_damage(1) end - else - self.poison_timer = 0 - end - - -- Handle plasma charging - if self.plasma_timer and self.plasma_timer > 0 then - self.plasma_timer -= 1 - if self.plasma_timer == 0 then - local dx, dy = self:get_aim_direction() - local sx, sy = self.x + self.width/2, self.y + self.height/2 - add(particles, particle:new(sx, sy, dx * 5, dy * 5, 120, 4, 12, "plasma", self)) - sfx(10) - self.vx -= dx * 5.5 - self.vy -= dy * 5.5 - self.plasma_timer = nil - end - end - - -- Entity-specific behavior - if self.subclass == "player" then - self.state = "idle" -- Player is always in idle state for terminal interaction - self:control() - self:follow_target() - - if btnp(❎) then - for t in all(terminals) do - if t.interactive and not t.tutorial_msg then -- Skip tutorials - game_minigame:start(t) - return - end - end - self:activate_ability(self.selected_ability) - end - - -- Fragment collection - for fragment in all(data_fragments) do - if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then - self.health = min(self.health + 25, self.max_health) - player_hud.credit_add_timer += 50 - fragment.collected = true - sfx(7) - end - end - else - -- Enemy AI consolidated - if self:can_see_player() then - self.alert_timer = self.max_alert_time - local player = self:find_player() - local dx, dy = player.x - self.x, player.y - self.y - local dist = dist_trig(dx, dy) - - if dist <= self.attack_range then - self.state = "attack" -- Set attack state - self.facing_left = dx < 0 - self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") - - local subclass_abilities = entity_abilities[self.subclass] - local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] - if ability and ability.current_cooldown == 0 then - self:activate_ability(ability.index) - end - else - self.state = "alert" -- Set alert state - self.vx, self.vy = dx / dist, dy / dist - end - elseif self.last_seen_player_pos.x then - self.state = "alert" -- Set alert state when moving to last known position - local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y - local dist = dist_trig(dx, dy) - self.vx, self.vy = dist > 1 and dx / dist or 0, dist > 1 and dy / dist or 0 - else - self.state = "idle" -- Set idle state - -- Idle behavior - self.idle_timer -= 1 - if self.idle_timer <= 0 then - self.idle_timer, angle, speed = 30, rnd(), rnd(1) - self.vx, self.vy = cos(angle)*speed, sin(angle)*speed - end - end - - self.alert_timer -= 1 - if self.alert_timer <= 0 then - self.last_seen_player_pos.x, self.last_seen_player_pos.y = nil, nil - end - end -end - -function entity:take_damage(amount) - self.health = max(0, self.health - amount) - self.flash_timer = 1 - if self.health <= 0 then self:on_death() end - if self.subclass == "player" then player_hud.shake_duration = 10 end -end - -function entity:on_death() - player_hud.credit_add_timer += self.kill_value - self:spawn_death_particles() - del(entities, self) - sfx(30) -end - -function entity:spawn_death_particles() - local particle_count = self.base_class == "preacher" and 40 or 20 - - for i = 1, particle_count do - local angle, speed = rnd(), 0.5 + rnd(1.5) - local p = particle:new( - self.x + self.width / 2, - self.y + self.height / 2, - cos(angle) * speed, - sin(angle) * speed, - 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) - add(particles, p) - end -end - -function entity:can_see_player() - local player = self:find_player() - if not player then return end - - if dist_trig(player.x - self.x, player.y - self.y) <= self.attack_range and self.targeting:has_line_of_sight(player) then - self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y - return true - end -end - - -function entity:find_ability(ability_name) - for i, ability in ipairs(self.abilities) do - if ability.name == ability_name then return i end - end -end - -function entity:find_player() - for e in all(entities) do - if e.subclass == "player" then return e end - end -end - -function entity:activate_ability(index) - local ability = self.abilities[index] - if ability.current_cooldown == 0 then - if ability.remaining_uses > 0 then - ability.action(self) - ability.current_cooldown = self.subclass == "player" and ability.cooldown or ability.cooldown * 3 - if self.subclass == "player" then ability.remaining_uses -= 1 end - else - -- Auto-switch to next weapon with ammo - for i = 1, #self.abilities do - local next_index = (index + i - 1) % #self.abilities + 1 - if self.abilities[next_index].remaining_uses > 0 then - self.selected_ability = next_index - game_ability_menu.last_selected_ability = next_index - self:activate_ability(next_index) - return - end - end - sfx(29) - end - end -end - -function entity:rifle_burst() - local dx, dy = self:get_aim_direction() - local decel = self.subclass == "player" and 5.5 or 1 - self.vx -= dx * decel - self.vy -= dy * decel - - for i = -2, 2 do - local angle = atan2(dx, dy) + i * 0.005 - local bullet = particle:new( - self.x + self.width/2 + cos(angle) * self.width/2, - self.y + self.height/2 + sin(angle) * self.height/2, - cos(angle) * 4, - sin(angle) * 4, - 30, 1, 8, "rifle", self - ) - add(particles, bullet) - end - - sfx(27) -end - -function entity:machine_gun() - local bullets, orig_update = 0, self.update - function self:update() - orig_update(self) - if bullets < 20 then - if bullets % 2 == 0 then - local dx, dy = self:get_aim_direction() - local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 - - local bullet = particle:new( - self.x + self.width/2 + cos(angle) * self.width/2, - self.y + self.height/2 + sin(angle) * self.height/2, - cos(angle) * 6, - sin(angle) * 6, - 20, 1, 8, "machinegun", self - ) - add(particles, bullet) - - self.vx -= dx * 0.15 - self.vy -= dy * 0.15 - - sfx(14) - end - bullets += 1 - else - self.update = orig_update - end - end -end - -function entity:missile_hail() - for i = 1, 3 do - local angle = rnd() - local offset = 10 + rnd(10) - local lifetime = 30 or 60 and self.subclass == "player" - local missile = particle:new( - self.x + self.width/2 + cos(angle) * offset, - self.y + self.height/2 + sin(angle) * offset, - 0, 0, lifetime, 1, 8, "missile", self) - missile.target = self.targeting.target - add(particles, missile) - end - - sfx(6) -end - -function entity:plasma_cannon() - self.plasma_timer = 20 -end - - -function entity:get_aim_direction() - local target = self.targeting.target - if target then - local dx, dy = target.x - self.x, target.y - self.y - local dist = dist_trig(dx, dy) - return dx/dist, dy/dist - end - - local speed = dist_trig(self.vx, self.vy) - if speed > 0 then - return self.vx / speed, self.vy / speed - end - - if self.last_direction == "horizontal" then - return self.facing_left and -1 or 1, 0 - end - return 0, self.last_direction == "up" and -1 or 1 -end - -function entity:control() - local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) - local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) - - if ix == 0 and iy == 0 then - self.target_x += (self.x - self.target_x) * 0.3 - self.target_y += (self.y - self.target_y) * 0.3 - return - end - - self.target_x += ix * 6 - self.target_y += iy * 6 - - local dx, dy = self.target_x - self.x, self.target_y - self.y - - if dist_trig(dx, dy) > 32 then - local angle = atan2(dx, dy) - self.target_x = self.x + cos(angle) * 32 - self.target_y = self.y + sin(angle) * 32 - end -end - -function entity:follow_target() - local dx, dy = self.target_x - self.x, self.target_y - self.y - local distance, follow_speed = dist_trig(dx, dy), .1 - - if distance > 1 then - self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) - - -- Update direction information - if abs(self.vx) > abs(self.vy) then - self.last_direction = "horizontal" - self.facing_left = self.vx < 0 - else - self.last_direction = self.vy < 0 and "up" or "down" - end - else - self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) - end - - -- Limit speed - local speed = dist_trig(self.vx, self.vy) - if speed > self.max_speed then - self.vx = (self.vx / speed) * self.max_speed - self.vy = (self.vy / speed) * self.max_speed - end -end - -function entity:approach(current, target, step) - if current < target then - return min(current + step, target) - elseif current > target then - return max(current - step, target) - else - return current - end -end - -function entity:apply_physics() - -- Apply deceleration - self.vx = abs(self.vx) < 0.01 and 0 or self.vx * self.deceleration - self.vy = abs(self.vy) < 0.01 and 0 or self.vy * self.deceleration - - -- Prepare new position - local new_x, new_y = self.x + self.vx, self.y + self.vy - - -- Check tile collision - if self:check_tile_collision(new_x, new_y) then - if not self:check_tile_collision(new_x, self.y) then - new_y = self.y - elseif not self:check_tile_collision(self.x, new_y) then - new_x = self.x - else - new_x, new_y = self.x, self.y - end - end - - -- Check laser door collision - for door in all(doors) do - if door:check_collision(new_x, new_y, self.width, self.height) then - new_x, new_y = self.x, self.y - self.vx, self.vy = 0, 0 - break - end - end - - -- Update position - self.x, self.y = new_x, new_y -end - -function entity:check_tile_collision(x, y) - local w, h = self.width - 1, self.height - 1 - return check_tile_flag(x, y) or check_tile_flag(x + w, y) or check_tile_flag(x, y + h) or check_tile_flag(x + w, y + h) -end - -function entity:draw() - local x,y,w,h = self.x,self.y,self.width,self.height - local is_preacher = self.base_class == "preacher" - local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 - - -- Plasma charge circle - if self.plasma_timer and self.plasma_timer > 0 then - circ(x + w/2, y + h/2, 32 * (self.plasma_timer / 20), 12) - end - - -- Shadow - if is_preacher then - local scale = 1 - (hover_offset / 8) - ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) - else - spr(49, x, y + 1) - end - - if self.flash_timer > 0 then - for i = 0, 15 do pal(i, 8) end - else - -- Normal color palette - pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) - if is_preacher then - if t() % 1 < .5 then pal(0, 8) end - elseif self.subclass != "player" then - pal(12,8) - end - end - - -- Sprite drawing - local speed = dist_trig(self.vx, self.vy) - local is_moving = speed > 0.2 - - if is_preacher then - spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) - else - local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] - local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 - spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) - end - - reset_pal() - - -- State indicators - local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) - if self.subclass != "player" and indicator then - spr(indicator, x + (is_preacher and 6 or 4), y - 8) - else - self.targeting:draw() - end - - self.flash_timer = max(0, self.flash_timer - 1) -end - - --- BARREL ----------- -barrel = {} - -function barrel.new(x, y) - local poison = rnd() > .5 - return setmetatable({ - x = x, - y = y, - poison = poison, - height = 8, - width = 8, - health = 1, - exploding = false, - explosion_time = 0, - }, {__index=barrel}) -end - -function barrel:draw() - if not self.exploding then - spr(self.poison and 5 or 6, self.x, self.y - 8) - end -end - -function barrel:update() - if self.health <= 0 and not self.exploding then - self.exploding = true - self.explosion_time = 0 - end - - if count_remaining_terminals() == 0 then - if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then - self.health = 0 - end - end - - if self.exploding then - self.explosion_time += 1 - - if self.explosion_time == 1 then - for i = 1, 20 do - local angle, speed = rnd(), 1 + rnd(2) - local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 - add(particles, particle:new( - self.x + self.width/2, - self.y + self.height/2, - p_vx, p_vy, - 20 + rnd(10), - 1 + rnd(2), - self.poison and 3 or 8 - )) - end - - for e in all(entities) do - local dx = e.x + e.width/2 - (self.x + self.width/2) - local dy = e.y + e.height/2 - (self.y + self.height/2) - local normalized_dist = dist_trig(dx/64, dy/32) - -- printh(normalized_dist) - if normalized_dist < 0.5 then - local damage = 20 * (1 - normalized_dist*2) - e:take_damage(damage * (self.poison and 1.5 or 1)) - end - end - - sfx(28) - end - - mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) - - if self.explosion_time >= 15 then - del(barrels, self) - end - end -end - -function barrel:take_damage(amount) - self.health = max(0, self.health - amount) -end - - --- LASER DOOR ----------------- -laser_door = {} - -function laser_door.new(x, y, color) - local laser_beams, color_map = {}, {} - - for beam in all(stringToTable("11,4|9,8|7,12")) do - local sx, sy = x + beam[1], y + beam[2] - local ex, ey = sx, sy + 10 - while not check_tile_flag(ex, ey) do ey += 1 end - add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) - end - - - for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do - local color_name, light_shade, dark_shade = unpack(color_data) - color_map[color_name] = { - beam_color = light_shade, - terminal_sequence = {7, light_shade, dark_shade, light_shade} - } - end - - return setmetatable({ - x = x, - y = y, - is_open = false, - laser_beams = laser_beams, - color = color or "red", - color_map = color_map - }, {__index=laser_door}) -end - -function laser_door:draw() - spr(14, self.x, self.y, 2, 2) - if not self.is_open then - for i, beam in ipairs(self.laser_beams) do - line( - beam.start_x, - beam.start_y, - beam.end_x, - beam.end_y + (#self.laser_beams - i + 1) * 2, - self.color_map[self.color].beam_color) - end - end -end - -function laser_door:check_collision(ex,ey,ew,eh) - if self.is_open then return end - for b in all(self.laser_beams) do - if ey+eh>b.start_y and eyb.start_x then return true end - end -end - --- DATA FRAGMENT ----------------- -data_fragment = {collected = false} - -function data_fragment.new(x, y) - return setmetatable({ - x = x, - y = y, - height = 8, - width = 8 - }, {__index=data_fragment}) -end - - -function data_fragment:draw() - if not self.collected then - local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] - spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) - end -end - - --- TERMINAL ----------------- -terminal = {} - -function terminal.new(x, y, target_door, tutorial_msg) - local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} - - local t = setmetatable({ - x = x, - y = y, - interactive = false, - pulse_index = 1, - pulse_timer = 0, - target_door = target_door, - pulse_colors = pulse_colors, - completed = false, - tutorial_msg = tutorial_msg - }, {__index = terminal}) - - -- Create panel at fixed screen position (will be adjusted with camera in draw) - local msg = tutorial_msg or " INTERACT: ❎" - local panel_width = max(40, #msg * 4 + 12) -- Adjust width based on message length - t.panel = textpanel.new(64 - panel_width/2, 114, 10, panel_width, msg, true) - t.panel.selected = true - - return t -end - -function terminal:update() - if self.completed then - self.interactive = false - return - end - - local dist = dist_trig(player.x-self.x, player.y-self.y) - - if self.tutorial_msg then - -- Tutorial: simply show when close - self.interactive = dist < 42 - else - -- Original terminal logic - self.interactive = true - for e in all(entities) do - if e.state != "idle" or dist >= 32 then - self.interactive = false - self.pulse_index, self.pulse_timer = 1, 0 - return - end - end - end - - -- Update panel animation when interactive - if self.interactive then - self.panel:update() - end - - self.pulse_timer = (self.pulse_timer + 1) % 6 - if self.pulse_timer == 0 then - self.pulse_index = self.pulse_index % #self.pulse_colors + 1 - end -end - - -function terminal:draw() - if self.completed then - pal(7, 8) - elseif self.interactive then - pal(7, self.pulse_colors[self.pulse_index]) - end - - spr(39, self.x, self.y + 8) - spr(23, self.x, self.y) - reset_pal() -end - -function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) - local new_door = laser_door.new(door_x, door_y, color) - add(doors, new_door) - add(terminals, terminal.new(terminal_x, terminal_y, new_door)) -end - --- MINIGAME ---------------- -minigame = { - directions = {"⬅️","➡️", "⬆️", "⬇️"}, - active = false, - current_input = {}, - time_limit = 180, - timer = 0, - current_terminal = nil -} - -function minigame.new() - return setmetatable({}, {__index = minigame}) -end - -function minigame:start(terminal) - self.sequence = {} - for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end - - local _ENV = self - active = true - timer = time_limit - current_input = {} - current_terminal = terminal -end - -function minigame:update() - self.timer -= 1 - if self.timer <= 0 then - self:end_game(false) - return - end - - for i = 0, 3 do - if btnp(i) then - add(self.current_input, self.directions[i+1]) - if #self.current_input == #self.sequence then - self:check_result() - end - return - end - end -end - -function minigame:check_result() - for i = 1, #self.sequence do - if self.sequence[i] != self.current_input[i] then - self:end_game(false) - return - end - end - self:end_game(true) -end - -function minigame:end_game(success) - self.active = false - local current_terminal = self.current_terminal - - if success then - sfx(15) - if current_terminal.target_door then - current_terminal.completed = true - current_terminal.target_door.is_open = true - else - current_terminal.completed = true - end - else - sfx(29) - end - current_terminal = nil -end - -function minigame:draw() - if not self.active or player.health <= 0 then return end - - local center_x, center_y = 64 + cam.x, 64 + cam.y - rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) - rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) - - local seq_width = #self.sequence * 12 - 4 - local seq_start_x = center_x - seq_width / 2 - - for x in all(self.sequence) do - print(x, seq_start_x, center_y - 10, 7) - seq_start_x += 12 - end - - seq_start_x = center_x - seq_width / 2 - - for i, dir in pairs(self.current_input) do - print(dir, seq_start_x, center_y, dir == self.sequence[i] and 11 or 8) - seq_start_x += 12 - end - - local timer_text = "time: "..flr(self.timer / 30) - print(timer_text, center_x - #timer_text * 2, center_y + 10, 8) -end - - --- PLAYER HUD ---------------- -player_hud = { - bar_width=80, - bar_height=5, - cooldown_bar_height=3, - x_offset=2, - y_offset=2, - text_padding=2, - show_interact_prompt=false, - shake_duration=0, - alert_bar_height=4, - credit_add_timer=0, - -} - -function player_hud.new() - return setmetatable({}, {__index=player_hud}) -end - -function player_hud:update() - - self.shake_duration = max(self.shake_duration - 1, 0) - if self.credit_add_timer > 0 then - credits += 5 - self.credit_add_timer = max(self.credit_add_timer - 5, 0) - end -end - -function player_hud:draw() - local cam_x, cam_y, health_percent = cam.x, cam.y, player.health / player.max_health - local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) - - if self.shake_duration > 0 then - start_x += rnd(4) - 2 - start_y += rnd(4) - 2 - end - - local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 - draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) - - local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 - draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) - - print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) - print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) - - local credits_text = "cREDITS: "..credits - if self.credit_add_timer > 0 then - credits_text ..= " +"..self.credit_add_timer - end - print_shadow(credits_text, start_x, cooldown_y + 12) - - local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height - - for entity in all(entities) do - if entity.state == "alert" or entity.state == "attack" then - local health_percent, bar_width = entity.health / entity.max_health, flr(entity.max_health * .4) - draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) - print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) - alert_y -= self.alert_bar_height + self.text_padding - end - end - - if count_remaining_terminals() == 0 then - if ending_sequence_timer == 1000 then - music(7) - elseif ending_sequence_timer > 0 then - print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) - print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) - -- Spawn point indicator - local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) - circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) - elseif ending_sequence_timer == 0 then - player.health = 0 - player:on_death() - end - ending_sequence_timer -= 1 - end - - local mx,my,px,py=cam.x+112,cam.y+112,flr(player.x/8),flr(player.y/8) - for i=0,255 do local tx,ty=i%16-8,flr(i/16)-8 - pset(mx+tx+8,my+ty+8,fget(mget(px+tx,py+ty),0)and 1 or 5)end - pset(mx+8,my+8,7) -end - -function draw_bar(x, y, w, h, bg, fill, pct) - rectfill(x, y, x + w - 1, y + h - 1, bg) - if pct > 0 then rectfill(x, y, x + max(1, flr(w * pct)) - 1, y + h - 1, fill) end - rect(x, y, x + w - 1, y + h - 1, 0) -end - - -function print_shadow(text, x, y, color) - print(text, x + 1, y + 1, 0) - print(text, x, y, color or 7) -end - - -__gfx__ -eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee -eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e -ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e -e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e -e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e -e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e -0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee -0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee -eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee -eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee -ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee -e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee -e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee -e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee -057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee -0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee -eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 -eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 -ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 -e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d -e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d -e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 -057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d -0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d -66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 -66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d -62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d -61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d -61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d -61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d -66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d -66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd -55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 -55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 -55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 -55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 -66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 -66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 -66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 -00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 -00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 -66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 -66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 -66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 -55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 -55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 -55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 -55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 -55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 -55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 -55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 -5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d -5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d -5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d -5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d -555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd -55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 -5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 -535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 -53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d -553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d -5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d -53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d -3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd -5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 -a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b -0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 -14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f -d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 -6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 -7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c -3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd -1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 -2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 -821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf -6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf -a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b -6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 -2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 -8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 -2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 -7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 -73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 -55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf -6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 -081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 -0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b -8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 -e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 -5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 -c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 -41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 -462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 -2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b -188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 -e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 -260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a -f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 -d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 -d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 -aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 -1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 -1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 -e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c -df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d -e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 -76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 -40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 -64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a -174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 -7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 -d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc -4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a -4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 -01e3a0f160f038782c301e120f0a8f85c703e3a1f1e0f07848f01a4f388300019f6cee918b0e0109d08240c2306a40f6108a18c1bb783c83f14b20e170ff30ff -28641c4dd93c831441b1a4c07c9055382814cf1a4315b98740c1b1e411961097581d04d316c6054a8ff14af22e70fee03c28210cce7c0dcd83f1ca20e070efe3 -18ff0cff0ef705cb8886c954a4717ed09338cf0c5a12f71bf7854cc345e41358205ab8ff1c151a9d11f384fdc927637172c12ec81f6cff3e2607ff8701c1d52a -243b6174b8cec47806d20bf38f904369a8a33372f5b9f8ec700efc3f1a9f8dcf0fe731f3e3f1f9e8080000000000000000000000000000000000000000000000 -__label__ -00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 -02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 -00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 -00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 -00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 -02000000020000000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 -00002000000020000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 -00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 -00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000001011000000000000000000000000000000000 -00002000200002000222222002222220001001100222222002222220022222200222222002222220000000000011001002222220022222200000000000000000 -00000000000000000200000002000000001101000200000002000000020000000200000002000000001111110101011002000000020000000000000000000000 -00000000000000000200000002000000001001100200000002000000020000000200000002000000001010101111001002000000020000000000000000000000 -00000002000000000200000002000000001101000200000002000000020000000200000002000000001100000000011002000000020000000000000000000000 -00000000000000000200000002000000001001100200000002000000020000000200000002000000001001111010101002000000020000000000000000000000 -00000000000000000200000002000000001101000200000002000000020000000200000002000000001101011111111002000000020000000000000000000000 -00000000000000000000000000000000001001100000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 -00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 -00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 -00000000020000000200000000000000001101011111111000000000000000000000000000000000001101000000000000000000020000000200000000000000 -00000000020000000200000000000000001001111010101000000000000000000000000000000000001001100000000000000000020000000200000000000000 -00000000020000000200000000000000001100000000011000000000000000000000000000000000001101000000000000000000020000000200000000000000 -00000000020000000200000000000000001010101111001000000000000000000000000000000000001001100000000000000000020000000200000000000000 -00000000020000000200000000000000001111110101011000000000000000000000000000000000001101000000000000000000020000000200000000000000 -00000000000000000000000000000000000000000011001000000000000000000000000000000000001001100000000000000000000000000000000000000000 -00000000000000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000000000000000000 -02222220022222200000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000222222002222220 -02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 -02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 -02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 -02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 -02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 -00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 -00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 -00000000000000000000000000000000000000000010011002222220022232200222222002222220001100100000000000000000000000000000000002222220 -01010101010101033333333033333333033333333033333333033333333033000233300002000000010101100000000000000000000000000000000002000000 -11111111111111133333333033333333033333333033333333033333333033300333000002000000111100100000000000000000000000000000000002000000 -00000000000000033000011033000033000000033011033002000000020003333330000002000000000001100000000000000000000000000000000002000000 -10101010101010133111001033000033033333333010133002033333333000333300000002000000101010100000000000000000000000000000000002000000 -11111111111111133101011033000033033333333011133102033333333000333300000002000000111111100000000000000000000000000000000002000000 -00000000000000033011001033000033033033300000033000000000000003333330000000000000000000000000000000000000000000000000000000000000 -00000000000000033333333033333333033003330000033000033333333033300333000000000000000000000000000000000000000000000000000000000000 -02222220000000033333333033333333033000333222233000033333333333000033000000000000022222200000000000000000000000000000000002222220 -02000000000000000011010000000000000000033200000000000000000000000003000000000000020000000000000000000000000000000000000002000000 -02000000000000000010011000000000000000003200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 -02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 -02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 -02000000000000000011010000000000000000000200033333333033000333033333333033333333033333333033033333333033333333000000000002000000 -00000000000000000010011000000000000000000000033333333033003330033333333033333333033333333033033333333033333333000000000000000000 -00000000000000000011010000000000000000000011033000033033033300000000000000000033000000033033033000033000000000000000000000000000 -02222220000000000010011000000000022222200010033000033033333000033333333033333333033333333233233000033033333333000000000002222220 -02000000000000000011010000000000020000000011033000033033330000033333333033333333033333333233033000333033333333000000000002000000 -02000000000000000010011000000000020000000010033000033033300000000000000033033300033033300233033003330000000000000000000002000000 -02000000000000000011010000000000020000000011033333333033000000033333333033003330033003330233033033300033333333000000000002000000 -02000000000000000010011000000000020000000010033333333030000000033333333033000333033000333233033333000033333333000000000002000000 -02000000000000000011010000000000020000000011010000000000000000000000000000000033000000033200033330000000000000000000000002000000 -00000000000000000010011000000000000000000010011000000000000000000000000000000003000000003000033300000000000000000000000000000000 -00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000033000000000000000000000000000000000 -02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222232000000000000000000000000000000000 -02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 -02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 -02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 -02000000000000000010011000000000020000000010101010101010201100000000000000000000001001110200000010101010101010101010101010101010 -02000000000000000011010000000000020000000011111111111111201000011000010000000000001101010200000011111111111111111111111111111111 -00000000000000000010011000000000000000000000000000000000201000100000010000000000001001100000000000000000000000000000000000000000 -00000000000000000011010000000000000000000000000000000000201000100100000000000000000101100000000000000000000000000000000000000000 -02222220000000000010011000000000022222200000000000000000201000011000010000000000001100100222222000000000000000000000000000000000 -02000000001111110011010101010101020000000000000000000000201000000000010001010101010101100200000000000000000000000000000000000000 -02000000001010100010011111111111020000000000000000000000200000000000110011111111111100100200000000000000000000000000000000000000 -02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 -02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 -02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 -00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 -00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 -01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 -11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 -00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 -10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 -11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 -00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 -02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 -02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 -02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 -02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 -02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 -02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 -00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 -02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 -02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 -02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 -02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 -02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 -02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 -00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 -00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 -02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 -02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 -02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 -02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 -02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 -02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 -00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 -00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 -00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 -00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 -00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 -00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 -00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 -00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 -00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 -00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 -00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 -00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 -00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 -00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 -00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 -00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 -00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 -00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000110100000000000000000000000000 -00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000020000200 -00000000000000000011111101010101010101010101011000000000000000000000000000000000000000000000000000110101111111100000000000000000 -00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 -00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 -00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 -00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 -00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 - -__gff__ -0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 -0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -__map__ -0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140ab00232612ca05028c09880e5480a2942022b020280aa9409905bc0012e0767039281eac0b480f0682801a01c00000ec018c0f005503990c06038180c0521500054538041c02ca018d00073840788d649239b204c8768 -0b3c045203c90154359260b6871004174a00642a2bc07120747022480f2c070a0fe83c06834542d84811c400073881cbe04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e14180c002c1ec014c0e433f010f07f82c91b4fc6f3f9f8de6025459 -ca13870ff488298542c3d8851443ccfc783d1e8f27a3c1e4f47a0d87210c500728009c050a01046d80614178808602c91cc06f3dc03d602667b83fa1bcf6040240b4e0fe6690381cef07f40e8020cea7f3cc1448f04d2f1e8f47e3d9bcc311e23b8341a2d8bfac3d480e25134628e31e707123c43c4e0eeb07038861c20160fe -90ed33495cc102032ec4a0a0a25094283fa4278a070a6f301681a0d1ec7688e3266287fa14cde7886991e0da783c1e4f27f3f18233cb1f848c220a81a3d1544bca42647d3f4498e2f0918328011c0308086f058320de84e882a41788d107f485221bc9a6e371ba0fec6f88011bcf517f224c350e3c0707f52c184fd0ca63d02c -f27a3fc5fd20ff121f21e4429615c474922b908cc46231988723159191c169208085d371809468309bc9a4e87819e89a6e8a990122fe85d89a10c20fa44d37180e530d3983181e4fe4710011cc11c8337c7008de1b8af145fce4d6507f79c414429662a7076322100804195f181c0e4420918cc6622114806330c768e32846f3 -d06c1926d437178da6f2e9b85c2f2f4414e0feb21b2181e8606e37978de6b98abc1fd00107f28bf942750f46f3cc6b48953bf29e01cee4e007f013f2850188a0738b7bcc3124fe91fd23d1e87c3f84aac1fd8dc6e184ccca502530958c3147fcc0e0724184fd07623d0c0f230271e8f2789969ceb56009f3c16803dcecd0e060 -36495d24bce6a8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160 -c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be75 -c68807e4dfca5bc555a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c2853b980d84923cd85a136f6112808dc080aa40959023c56708d2489bf9592d0db5fd237d1fca02051688a571ce3ce6c691b32ba6b403f0b70 -9c305984c44020918884022900b85b85819188c4422908890b73b3f916e01cf0be42e4a816c7887f91711ae2a2f16b485ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e -9ae6770001cc8522b158a653c1691b0df01988c155b04ee60778d23962958e707f38afc53f1aa0c90192a5c1db4ccdd07f38d815c01c0975d693f9dff48578162301270ac40721d08f286494438ab414e36645730a96c56c42bae8c500b05c1fca79ab7fc4b3795375701481b158ac1985e283f901e84ff81b4bdc2581ca0ff5 -4ddcc66998665ab8aa49f73dfc20162b15e135663f54dd2a4eb4200eebc7731399fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09da33cd8492c5d04ecf4d00fca51a6178b56a474d4e1dab5e81720 -4abe1d2e6add1a42000008743f292fa48912f24747f5987dcf7ca63b332f6a51ad894b4bc7b35333502ca001d8272bceb5355a82974e42c2b11c40044201725be78be4ca21d1fdb021f3278e07170312682a0cbbf9eebdf67640d16eb9ce006d40b33563b8b30e73252cff36c20f71670287038aa5be78be2b19266f5301ffbd -5c2915b818957a4b5816b78f86adc39432148ec53291db93a5c252366df08ca5783ff41ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06f -caa16fdc8f07a36f0b8c0807160360feda632cfe174ad32b53cff3c16a0c35747a123d171657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c -6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c0239acc1000a36188 -c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c -21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c -8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d -291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4 -f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513 -bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3 -dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573 -051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72 -d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab69 -52540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50 -771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e20734 -1fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c4884 -1334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -__sfx__ -151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 -d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 -c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 -a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 -151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 -cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 -47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 -46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 -93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 -cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 -d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 -a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 -a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 -d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 -17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 -1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 -170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 -a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 -a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 -d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 -d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 -d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 -d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 -311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 -c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 -c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 -010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 -15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 -a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 -8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 -__music__ -01 00175144 -00 00184344 -00 00170144 -00 04185844 -00 00171144 -00 02034344 -02 19034344 -01 1a154344 -02 1a164344 -00 02424344 - From c92ebe7989189dc330aa1ee362741767c38a8ec1 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 17:26:11 +0100 Subject: [PATCH 07/16] got map working --- beta_v0.2.2.p8 | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index f33c505..fc14210 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -24,6 +24,8 @@ Optional Objectives: * Eliminate all enemy units ]] +-- bug to fix, info terminals shouldn't count as terminals for the endgame + -- MAIN ---------------------- function _init() @@ -256,7 +258,7 @@ function count_remaining_enemies() end function count_remaining_terminals() - return count_remaining(terminals, function(t) return t.completed end) + return count_remaining(terminals, function(t) return t.completed or t.tutorial_msg end) end @@ -1954,6 +1956,8 @@ function player_hud:draw() elseif ending_sequence_timer > 0 then print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + print_shadow("BACK TO SPAWN POINT", cam_x + 26, cam_y + 108) + -- Spawn point indicator local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) From a99da8fb80c9d54f2ed5e0c5bdedf6f3897d1386 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 18:09:53 +0100 Subject: [PATCH 08/16] got map working --- beta_v0.2.2.p8 | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index fc14210..d4aedf6 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -24,7 +24,6 @@ Optional Objectives: * Eliminate all enemy units ]] --- bug to fix, info terminals shouldn't count as terminals for the endgame -- MAIN ---------------------- @@ -58,7 +57,6 @@ function _init() -- -- Load map 1 for Intro decompress_current_map() - SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| @@ -94,7 +92,7 @@ function _init() trans = transition.new() player = entity.new(0, 0, "bot", "player") - change_state("intro", true) + change_state("gameplay", true) end function _update() @@ -508,8 +506,6 @@ end ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end ability_menu.close = function(self) self.active = false end - - -- INTRO ---------------------- function init_intro() @@ -546,7 +542,7 @@ function update_intro() intro_text_panel.textline = intro_pages[intro_page] intro_text_panel.char_count = 0 else - change_state("mission_select") + change_state("intro") end end end @@ -726,7 +722,7 @@ function init_gameplay() decompress_current_map() music(0) player_hud = player_hud.new() - entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer, win_lose_timer = {}, {}, {}, {}, {}, {}, 1000, 0 local mission_entities = { [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], @@ -856,12 +852,19 @@ function draw_gameplay() message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" end + -- Increment timer + win_lose_timer += 1 + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) print_centered(message, player.x, player.y - 6, color) - print_centered(prompt, player.x, player.y + 2, 7) - if btnp(🅾️) then - change_state("mission_select") + -- Only show prompt after timer threshold (0.1 seconds = 6 frames at 60fps) + if win_lose_timer > 30 then + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select") + end end end end @@ -1118,7 +1121,7 @@ function entity.new(x, y, base_class, subclass) 15,dervish,50,50,60,100| 13,vanguard,70,70,50,120| 1,warden,100,100,70,200| - 7,player,400,400,70,0| + 7,player,4,4,70,0| 11,preacher,80,80,80,280| 6,cyberseer,160,160,80,300| 1,quantumcleric,170,170,70,320 From f2ab3c45447fd9579d9739c3b981678526b3d736 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 18:19:13 +0100 Subject: [PATCH 09/16] got map working --- beta_v0.2.2.p8 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index d4aedf6..d680cec 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -92,7 +92,7 @@ function _init() trans = transition.new() player = entity.new(0, 0, "bot", "player") - change_state("gameplay", true) + change_state("intro", true) end function _update() @@ -542,7 +542,7 @@ function update_intro() intro_text_panel.textline = intro_pages[intro_page] intro_text_panel.char_count = 0 else - change_state("intro") + change_state("mission_select") end end end @@ -1121,7 +1121,7 @@ function entity.new(x, y, base_class, subclass) 15,dervish,50,50,60,100| 13,vanguard,70,70,50,120| 1,warden,100,100,70,200| - 7,player,4,4,70,0| + 7,player,400,400,70,0| 11,preacher,80,80,80,280| 6,cyberseer,160,160,80,300| 1,quantumcleric,170,170,70,320 From bc9bcadbefd660b5dcfd6c7dfc64c733ff64407e Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 18:24:51 +0100 Subject: [PATCH 10/16] got map working --- beta_v0.2.2.p8 | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/beta_v0.2.2.p8 b/beta_v0.2.2.p8 index d680cec..4a1edfa 100644 --- a/beta_v0.2.2.p8 +++ b/beta_v0.2.2.p8 @@ -722,7 +722,7 @@ function init_gameplay() decompress_current_map() music(0) player_hud = player_hud.new() - entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer, win_lose_timer = {}, {}, {}, {}, {}, {}, 1000, 0 + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 local mission_entities = { [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], @@ -839,8 +839,6 @@ function draw_gameplay() -- check mission status if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then - local message, color, prompt - if player.health > 0 then message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" @@ -852,20 +850,12 @@ function draw_gameplay() message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" end - -- Increment timer - win_lose_timer += 1 - draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) print_centered(message, player.x, player.y - 6, color) -- Only show prompt after timer threshold (0.1 seconds = 6 frames at 60fps) - if win_lose_timer > 30 then - print_centered(prompt, player.x, player.y + 2, 7) - - if btnp(🅾️) then - change_state("mission_select") - end - end + print_centered(prompt, player.x, player.y + 2, 7) + if btnp(🅾️) then change_state("mission_select") end end end From 3f8d27e0c04ffc0cf19eb5bcd63a5ea767fb3e91 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 23 May 2025 18:48:14 +0100 Subject: [PATCH 11/16] got map working --- beta_v0.2.2_compressed.png | Bin 0 -> 42347 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 beta_v0.2.2_compressed.png diff --git a/beta_v0.2.2_compressed.png b/beta_v0.2.2_compressed.png new file mode 100644 index 0000000000000000000000000000000000000000..2d788323b079f6d96add7348759db8d402c7c643 GIT binary patch literal 42347 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 literal 0 HcmV?d00001 From 633da2cdb279f532fd7786c7d98ef00a2a898761 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Thu, 9 Oct 2025 20:33:56 +0100 Subject: [PATCH 12/16] Reorganize project structure with semantic versioning - Created organized directory structure (versions, releases, assets, tests) - Renamed alpha releases to v0.0.x format (v0.0.1, v0.0.2, v0.0.3) - Renamed beta releases to v0.x.x format (v0.1.0 through v0.2.2) - Moved all cart versions to versions/ directory - Moved compression utilities and assets to assets/ directory --- .../compression_utilities.ipynb | 68 +- beta_v0.2.0_compressed.png | Bin 44159 -> 0 bytes beta_v0.2.1_compressed.png | Bin 42369 -> 0 bytes beta_v0.2.2_compressed.png | Bin 42347 -> 0 bytes versions/v0.0.1.p8 | 2304 +++++++++++++++ versions/v0.0.2.p8 | 2306 +++++++++++++++ versions/v0.0.3.p8 | 2358 ++++++++++++++++ versions/v0.1.0.p8 | 2428 ++++++++++++++++ versions/v0.1.1.p8 | 2428 ++++++++++++++++ versions/v0.1.2.p8 | 2465 +++++++++++++++++ versions/v0.1.3.p8 | 2405 ++++++++++++++++ beta_v0.2.0.p8 => versions/v0.2.0.p8 | 0 beta_v0.2.1.p8 => versions/v0.2.1.p8 | 0 beta_v0.2.2.p8 => versions/v0.2.2.p8 | 14 +- 14 files changed, 16735 insertions(+), 41 deletions(-) rename compression_utilities.ipynb => assets/compression_utilities.ipynb (87%) delete mode 100644 beta_v0.2.0_compressed.png delete mode 100644 beta_v0.2.1_compressed.png delete mode 100644 beta_v0.2.2_compressed.png create mode 100644 versions/v0.0.1.p8 create mode 100644 versions/v0.0.2.p8 create mode 100644 versions/v0.0.3.p8 create mode 100644 versions/v0.1.0.p8 create mode 100644 versions/v0.1.1.p8 create mode 100644 versions/v0.1.2.p8 create mode 100644 versions/v0.1.3.p8 rename beta_v0.2.0.p8 => versions/v0.2.0.p8 (100%) rename beta_v0.2.1.p8 => versions/v0.2.1.p8 (100%) rename beta_v0.2.2.p8 => versions/v0.2.2.p8 (99%) diff --git a/compression_utilities.ipynb b/assets/compression_utilities.ipynb similarity index 87% rename from compression_utilities.ipynb rename to assets/compression_utilities.ipynb index 4878778..2169d03 100644 --- a/compression_utilities.ipynb +++ b/assets/compression_utilities.ipynb @@ -251,7 +251,7 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 31, "metadata": {}, "outputs": [], "source": [ @@ -452,7 +452,7 @@ }, { "cell_type": "code", - "execution_count": 30, + "execution_count": 32, "metadata": {}, "outputs": [ { @@ -462,7 +462,7 @@ "Processing Map 1_2:\n", "Map piece 1:\n", "Original length: 4096\n", - "Compressed length: 2031\n", + "Compressed length: 2040\n", "Compression ratio: 0.50\n", "Matches original: True\n", "\n", @@ -473,39 +473,39 @@ "Matches original: True\n", "\n", "Lengths of compressed pieces:\n", - "[2031, 1585]\n", - "Total compressed length: 3616\n", + "[2040, 1585]\n", + "Total compressed length: 3625\n", "\n", "Compressed data:\n", - "0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140aa50800113096502814604c4072a40516022d020280aa9409905bc0012e0767039381e94100e03c4078411400c80e000007600c607802a81cc860301c0c060290a8002a29c020e016500c680059ac92473640990ed011\n", - "c8119c0554d64982da1c40105d280190a8af01c4384134e0be701e580e141fd0780d068a85b0902388000e710397c086f379f8de6f379fcde1b158100900c2806a40098db00a3021bcf6602491cc06e27403d6006b0113024068a04080437980b307c28301800583d802981c867e021e0ff0592369f8de7f3f1bcc04a8b39427\n", - "0e1fe910530a8587b10a288799f8f07a3d1e4f4783c9e8f41b0e4218a00e5001380a140208db00c282f1010c05923980de7b807ac04ccf707f4379ec08048169c1fccd207039de0fe81d00419d4fe7982891e09a5e3d1e8fc7b3798623c477068345b17f587a901c4a268c51c63ce0e24788789c1dd60e0710c38402c1fd21da\n", - "6692b98204065d89414144a128507f484f140e14de602d0341a3d8ed11c64cc50ff4299bcf10d323c1b4f0783c9e4fe7e30467963f091844150347a2a8979484c8fa7e8931c5e12306500238061010de0b0641bd09d105482f11a20fe90a4437934dc6e3741fd8df1002379ea2fe44986a1c780e0fea58309fa194c7a059e4f4\n", - "7f8bfa41fe243e43c8852c2b88e92457211988c463310e462b232382d241010ba6e30128d061379349d0f033d134dd15320245fd0bb13421841f489a6e301ca61a7306303c9fc8e2002398239066f8e011bc3715e28bf9c9aca0fef38828852cc54e0ec64420100832be30381c884123198cc4422900c6618ed1c6508de7a0d8\n", - "324da86e2f1b4de5d370b85e5e8829c1fd64364303d0c0dc6f2f1bcd7327783fa0020fe517f284ea1e8de798d6912a77e53c039e59c00fe027e50a031140e716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f5936495d24bce6a\n", - "8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003\n", - "f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e\n", - "755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b309888041231\n", - "10804520170b70b032311888452111216e767f22dc039e17c85c9502d8f10ff22e235dd25228a7726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8\n", - "522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715\n", - "493ee7bf8402c562bc26acc7ea9ba549d68401dd78ee62733f7c3aa52d30610125025ec1699fd87f29df9e2fca87af56cc83fa567da63f06830d278eb0ea1b0e5f78c3b853286c752c4e2fcd6a95020121bb15e1ca1f9ca78f3b275c13b4679b09258ba09dff1a01f14a38c2f16ad48e9a9c3811c52ce00957c3a64f6af64840\n", - "00010e87e525f491225e48e8feb30fb9ef94c76665ed4a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e2e0624d0541977f3dd7becec81a2dd739c00da81666ac771661ce64a59fe6d841ee2ce050e07154b7cf17c56324cdea603ff7ab8522b7031300e96b\n", - "02d6f1f0d5b872864291d8a6523b7274b84a46cdbe1194af07fe83dc421180e2ac96a6b6ccdc5ec13a51fda44ff6e74e029efe4f1aad7b42daf149fca0ff043e141599ccfc6f36eca480804215068a1fed9fe325f46132df84cd9f9e2f9ef4e747f399caede66e8cf8183bb535009a4feba215e9c91a0df9542dfb91e0f46de1\n", - "718100e2c06c1fdb4c659fc2e95a7471b9fed82d4186ae8f4247a2e2cafa61e34179d56d389e24e36d87b6d2def1c3fe4fe7ae46101210645d371b8f67a37efe8b708a00039df7fc55db5bd6563fd717cf8637c72dc59528ce7ad541806c606fa3f3f40937bc5000a0e99cfc7836f3d4ba709d3923f14b8dc527f5cff2596d31\n", - "58dd003d4b9dffce2a714ffba1ca716933345de74e2e91ef24ed07ffdd1a8f0a67ff0ca5f693bbe47f356174a5febeda2dbe272ee7a5ad7ffc0268b8f4cf529a3a731dc1d77de43037c1f9f4be7c5e2eef144068e8422310ccc462190b9607cb62219d67fed7936bc5213bb589dad8239acc1000a36188c700f7359bcf47a801\n", - "1400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8\n", - "de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75\n", - "c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a57\n", - "69084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d298767505343\n", - "5834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b14\n", - "68e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8\n", - "b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870f\n", - "e602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3\n", - "a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c2914921809\n", - "32922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74\n", - "c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4a\n", - "c2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881\n", - "c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n", + "0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140ab00232612ca05028c09880e5480a2942022b020280aa9409905bc0012e0767039281eac0b480f0682801a01c00000ec018c0f005503990c06038180c0521500054538041c02ca018d00073840788d649239b204c8768\n", + "0b3c045203c90154359260b6871004174a00642a2bc07120747022480f2c070a0fe83c06834542d84811c400073881cbe04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e14180c002c1ec014c0e433f010f07f82c91b4fc6f3f9f8de6025459\n", + "ca13870ff488298542c3d8851443ccfc783d1e8f27a3c1e4f47a0d87210c500728009c050a01046d80614178808602c91cc06f3dc03d602667b83fa1bcf6040240b4e0fe6690381cef07f40e8020cea7f3cc1448f04d2f1e8f47e3d9bcc311e23b8341a2d8bfac3d480e25134628e31e707123c43c4e0eeb07038861c20160fe\n", + "90ed33495cc102032ec4a0a0a25094283fa4278a070a6f301681a0d1ec7688e3266287fa14cde7886991e0da783c1e4f27f3f18233cb1f848c220a81a3d1544bca42647d3f4498e2f0918328011c0308086f058320de84e882a41788d107f485221bc9a6e371ba0fec6f88011bcf517f224c350e3c0707f52c184fd0ca63d02c\n", + "f27a3fc5fd20ff121f21e4429615c474922b908cc46231988723159191c169208085d371809468309bc9a4e87819e89a6e8a990122fe85d89a10c20fa44d37180e530d3983181e4fe4710011cc11c8337c7008de1b8af145fce4d6507f79c414429662a7076322100804195f181c0e4420918cc6622114806330c768e32846f3\n", + "d06c1926d437178da6f2e9b85c2f2f4414e0feb21b2181e8606e37978de6b98abc1fd00107f28bf942750f46f3cc6b48953bf29e01cee4e007f013f2850188a0738b7bcc3124fe91fd23d1e87c3f84aac1fd8dc6e184ccca502530958c3147fcc0e0724184fd07623d0c0f230271e8f2789969ceb56009f3c16803dcecd0e060\n", + "36495d24bce6a8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160\n", + "c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be75\n", + "c68807e4dfca5bc555a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c2853b980d84923cd85a136f6112808dc080aa40959023c56708d2489bf9592d0db5fd237d1fca02051688a571ce3ce6c691b32ba6b403f0b70\n", + "9c305984c44020918884022900b85b85819188c4422908890b73b3f916e01cf0be42e4a816c7887f91711ae2a2f16b485ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e\n", + "9ae6770001cc8522b158a653c1691b0df01988c155b04ee60778d23962958e707f38afc53f1aa0c90192a5c1db4ccdd07f38d815c01c0975d693f9dff48578162301270ac40721d08f286494438ab414e36645730a96c56c42bae8c500b05c1fca79ab7fc4b3795375701481b158ac1985e283f901e84ff81b4bdc2581ca0ff5\n", + "4ddcc66998665ab8aa49f73dfc20162b15e135663f54dd2a4eb4200eebc7731399fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09da33cd8492c5d04ecf4d00fca51a6178b56a474d4e1dab5e81720\n", + "4abe1d2e6add1a42000008743f292fa48912f24747f5987dcf7ca63b332f6a51ad894b4bc7b35333502ca001d8272bceb5355a82974e42c2b11c40044201725be78be4ca21d1fdb021f3278e07170312682a0cbbf9eebdf67640d16eb9ce006d40b33563b8b30e73252cff36c20f71670287038aa5be78be2b19266f5301ffbd\n", + "5c2915b818957a4b5816b78f86adc39432148ec53291db93a5c252366df08ca5783ff41ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06f\n", + "caa16fdc8f07a36f0b8c0807160360feda632cfe174ad32b53cff3c16a0c35747a123d171657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c\n", + "6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c0239acc1000a36188\n", + "c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c\n", + "21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c\n", + "8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d\n", + "291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4\n", + "f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513\n", + "bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3\n", + "dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573\n", + "051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72\n", + "d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab69\n", + "52540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50\n", + "771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e20734\n", + "1fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c4884\n", + "1334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000\n", "\n", "Processing Map 3_4:\n", "Map piece 1:\n", diff --git a/beta_v0.2.0_compressed.png b/beta_v0.2.0_compressed.png deleted file mode 100644 index 2521853012080a61672c3b4446f75cfac73d72f6..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44159 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 diff --git a/beta_v0.2.1_compressed.png b/beta_v0.2.1_compressed.png deleted file mode 100644 index c0a1e868ba6995256b31ff142592ebb87c1468d5..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42369 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 JPDHLkV1g_*Jz@X= diff --git a/beta_v0.2.2_compressed.png b/beta_v0.2.2_compressed.png deleted file mode 100644 index 2d788323b079f6d96add7348759db8d402c7c643..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 42347 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 diff --git a/versions/v0.0.1.p8 b/versions/v0.0.1.p8 new file mode 100644 index 0000000..09c7618 --- /dev/null +++ b/versions/v0.0.1.p8 @@ -0,0 +1,2304 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + + + +-- strech goal: implement a flag for cables sprites, and do a pal swap vertically or horizontally periodically across the map + +-- function compress(data) +-- local result = "" +-- local i = 1 +-- local data_len = #data + +-- while i <= data_len do +-- local best_len, best_dist = 0, 0 + +-- for j = max(1, i - 255), i - 1 do +-- local k = 0 +-- while i + k <= data_len and sub(data, j + k, j + k) == sub(data, i + k, i + k) do +-- k += 1 +-- end + +-- if k > best_len then +-- best_len, best_dist = k, i - j +-- end +-- end + +-- if best_len > 2 then +-- result = result..chr(128 + best_len)..chr(best_dist) +-- i += best_len +-- else +-- result = result..sub(data, i, i) +-- i += 1 +-- end +-- end + +-- return result +-- end + +-- function test_and_save_map_compression(start_address, end_address, filename) +-- -- Gather map data +-- local map_data = "" +-- for addr = start_address, end_address do +-- map_data = map_data..chr(peek(addr)) +-- end + +-- -- Compress the data +-- local compressed = compress(map_data) + +-- -- Convert compressed data to Lua string +-- local lua_string = "" +-- for x in all(compressed) do +-- lua_string = lua_string.."\\"..ord(x) +-- end + +-- -- Save to file +-- printh(lua_string, filename, true) +-- printh("Compressed data saved to "..filename) +-- end + +function decompress_to_memory(data, mem_index) + local i = 1 + while i <= #data do + local byte = ord(data[i]) + if byte >= 128 then + local source = mem_index - ord(data[i + 1]) + for j = 1, byte - 128 do + poke(mem_index, peek(source + j - 1)) + mem_index += 1 + end + i += 2 + else + poke(mem_index, byte) + mem_index += 1 + i += 1 + end + end +end + + + +-- HELPER FUNCTIONS +---------------------- +function reset_pal(_cls) + pal() + palt(0,false) + palt(14,true) + if _cls then cls() end +end + +function check_tile_flag(x, y, flag) + return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) +end + +function stringToTable(str) + local tbl = {} + for pair in all(split(str, "|", false)) do + add(tbl, split(pair, ",", true)) + end + return tbl +end + +function dist_trig(dx, dy) + local ang = atan2(dx, dy) + return dx * cos(ang) + dy * sin(ang) +end + +function draw_shadow(circle_x, circle_y, radius, swap_palette) + local swapped_palette = {} + for i = 0, 15 do + for j = 0, 15 do + local index = (i << 4) | j + swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] + end + end + + -- Pre-compute squared radius + local radius_squared = radius * radius + + -- calculate the top and bottom y of the circle + local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) + + -- Function to swap palette for a line + local function swap_line(y, start_x, end_x) + local line_start_addr = 0x6000 + y * 64 + for i = 0, start_x >> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + +-- CAMERA +---------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +-------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(9, 94, 20, 110, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "rEMAINING DATA SHARDS: " .. count_remaining_fragments() .. + "\niNFECTED UNITS ONLINE: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- STATE MANAGEMENT +---------------------- +function _init() + -- test_and_save_map_compression(0x2000, 0x2fff, "compressed_map_upper.txt") + -- test_and_save_map_compression(0x1000, 0x1fff, "compressed_map_lower.txt") + + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS, mission_data = { + "PROTOCOL ZERO:\n\nCONTAINMENT\nFACILITY ALPHA-7\nCOMPROMISED\n\nACTIVATE ALL \nTERMINALS TO \nRESTORE FACILITY\nLOCKDOWN", + "SILICON GRAVEYARD:\n\nBARRACUDA VIRUS\nSPREADS TO MEGA-\nCITY DISPOSAL SITE\n\nTRAVERSE HAZARDOUS\nWASTE, EVADE OR \nNEUTRALIZE\nSCAVENGER BOTS", + "NEURAL NEXUS:\n\nBARRACUDA ASSAULTS\nCITY'S CENTRAL CORTEX\n\nBATTLE THROUGH\nVIRTUAL MINDSCAPE\nOF INFECTED AIs", + "HEAVEN'S SPIRE:\n\nLAST STAND ATOP THE\nGLOBAL NETWORK HUB\n\nASCEND THE TOWER,\nCONFRONT BARRACUDA,\nACTIVATE CORTEX\nPROTOCOL" + }, {} + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 3000, 3 + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 42,1,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + + change_state("mission_select", false) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, use_transition) + local new_state = states[new_state_name] + + if use_transition and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + intro_counter, intro_blink = 0, 0 + x_cortex, x_protocol = -50, 128 + + TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_page = 1 + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + if intro_counter == 0 then music(05) end + + intro_counter += 1 + intro_blink += 0.02 + + local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol + x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) + x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + + if prev_x_cortex != TITLE_FINAL_X_CORTEX and x_cortex == TITLE_FINAL_X_CORTEX or + prev_x_protocol != TITLE_FINAL_X_PROTOCOL and x_protocol == TITLE_FINAL_X_PROTOCOL then + sfx(20) + end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select", true) + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(intro_blink) < .9 then circfill(63,64, 3, 2) end + + display_logo(x_cortex, x_protocol, 0, 12) + + intro_text_panel:draw() + controls_text_panel:draw() + print("PRESS ❎ TO CONTINUE", 24, 118, 11) +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,67,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + elseif btnp(⬅️) or btnp(➡️) then + sfx(19) + show_briefing = not show_briefing + elseif btnp(❎) then + change_state("loadout_select", true) + elseif btnp(🅾️) then + change_state("intro", true) + end + + if prev != current_mission or btnp(⬅️) or btnp(➡️) then + info_panel.char_count = 0 + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select", true) + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay", true) + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + if current_mission <= 2 then + compressed_map_lower="Gk`⬅️¹lbc☉▮kozz⬇️³░⁴⁴{`IG`⁴、、✽■✽³o0^0n^o`i⁷●¹q✽⁸KPPQ⬇️ᵇVvVTP✽¹s`░F웃cl⬇️Tm●E●⁵`Y[@@@FfD⬇️⁶✽³\\☉▮c░:zoo░⬇️⁴o░█웃|●░{⬇️うᵇ`Z♥z●♥Wl``RVS`p⌂カ░を⬅️¹⬇️█om⬇️サMN✽チ`j⬇️は░つQvQ●の☉⁶✽む☉メ⁴…█Mnn0ozᵇ+`I✽◝*✽³⬇️웃C☉l⁴●³0ᶜᶜ+ᶜ🅾️まmo]n./^]on⬇️¹`iG⬇️はˇ²░む웃▒…█⬇️れ░○☉█⁷‖◀⬇️◝,\r\r\r♥ふ⁴●⁵⁴0•+•、✽っᵇ♥♥M]⬇️ン>?░@⬇️'⬇️█`o•++、om●む♥⁷●ん✽J⬇️Ko⬆️█zz•⁴⁴`Y●█%&⬇️◝。⁷q\0⌂ふ●⬇️0⬇️~░Z⬇️てᶜ⬇️bMn^░y░?●█░ス░█●R♥Y😐░⁴⁴░▥✽█♥▮░■░なᵇ+⬇️█Z✽◝*⬇️³\r\r=⁷⁷S⌂8●;0ozxol`♥¹k^✽_⬇️✽✽◝zo`YH⬇️ス•、☉ンM^✽ロ●2p☉7``lIWkko○웃I⌂「✽◝☉⁵Xk…!kozyo`b@✽¹c⬇️lzz░d░fMnnn`iHa⬇️⌂⬇️u]N♥◝░▒░2fD●5●;\\qFao⬇️IAFFE@@F░。Ef@D@\\⁷⁷\0⌂¹[⬇️hFFDEB☉?n^✽M⁴⁴`IW`⁴z⬇️▥⬇️⁴•+、○`J⁷H⁴xy✽}░⁶░◝⁷\0,=⁷⁷h`⬇️のN●そzM░ゆ^⬇️█⁷♥█⬇️さ░+q⁷⁷**⁷⁷KPQVVvQ⬇️めVvVPQvQPPLG░@^✽k░る✽r░█h⬇️█omz0░▒、o`j⁷h⁴⁴⬇️f○oo~⬇️²|✽█⬇️◝⁷ha⬇️む⬇️やᵇo•ᶜ░B⬇️●o`i⁷⁷<\r-웃|⬇️◜,░█Wl`░¹RVS`p✽\n`k⬇️ヘo✽フzo♥●░ロ⬇️るha○zo]0y0✽ケai⁷tBFfCfFC✽:u✽█░ケg░@mᶜ0•ᶜo、⬇️るᵇ+++oO░レ░ヨ●|░て░つ⁷⁷Co○~⬇️テ⁴o~⬇️イ⬇️⁶⬇️hg`⬇️ス⬇️ンM^⬇️テ⬇️◜✽on⬇️█●▤0y^░ナ░キ♥MfFfFF\0░サ⌂█]0z0░せ^░○、z✽レ░ヨ⌂²✽█\0\0~░マzx✽⁶z⁴{o`Yh░█⬇️ワ]N●‖mzooᵇᶜ`IX⬇️スzon^z░3○✽キ⌂P\0\0웃サ░(░そ⬇️し⬇️を•、•、z⬇️█\0\0\0。⬇️⁴dRVSe⬇️ᶜq\0<**⬇️⁘\0o♥{●▒]⬇️ヘg⬇️█⬇️s░y●█⬇️○+、`IH⬇️スM^oo░2xy●◜░レ✽⁷✽█q\0░サG`oᶜ✽そ░○♥]✽█<-\0\0h~~○j░x\0\0░█q\0⬅️%✽ヤ`Y✽ヘ⬇️ルo⬇️y░²mozᵇ、o⬇️█aᶜ☉⧗✽R♥ヲ░わ⬇️ュ*✽\r░█W`o+✽そzzᵇᵇ⬇️⌂░F|✽そ⬇️えg○{oj⁷░と✽ᶠ\0\0⬇️て⬇️オ⬅️█i⬇️ら{zzMn⬇️る░@z]Nᵇ、{⬇️█G`+ᶜmzy●コy♥█,*\r\r\r-●\\*⬇️░░c`o、●そᵇ、••ᶜoz⁴⁴⁴`Z░゜=q⁷h○~~R░█⬇️-⬇️て⁷\0~•⬇️c{N●◝⁴zo`Z⬇️█♥ョᵇ+++ᶜ⬇️ᵇ•⬇️イ`IW`+、mo~⬇️セ○~○~░キ⬇️|=░セ<░ \r\r*⬇️😐w⁷⁷X⬇️@░>oᵇ、✽ュ⬇️○⁴`I●か⁷tBqCu✽█w✽◆¹░ョ⬇️て➡️,lI[@✽¹AqDAffFFDEBD░□\\q⁷CBFFf⬇️²A⬇️⁶░オ∧²♥@@@⌂<⬇️>あ,😐'░hモン¹>✽◝3⬇️¹>⬇️⁵ネ∧ モ3あ$ネモ>⬇️ む@⬇️]ネ✽⬇️░♪ネ⬇️i⌂³⬇️\\♥ᶠ✽█✽!░³☉$☉め😐らこ@✽キ✽g░タ>░○り@●やネ⬅️やい█⬇️ル…ら✽ョ⬇️「♪う●キ░ニ░ナ⬅️ノ♥s●ロ░ᵉ♥チ♥.く@░▒♥ら⬇️◜░9●♪✽ヨさ³☉や●ろネ⬇️²☉\nすA>◆h" + compressed_map_upper='⁷⁷*∧¹⁷⁷SvvQ⁷q⁷VvVTPPQ⬇️ᶜ⁷⬇️⁸⬇️²UQVVv⬇️⁷░\rLK☉•●‖♥□⬇️!♥1L♪`\0K░<░:⬇️>░A⬇️゜⁷♥¹\0\0。✽ᵇ●⁶⁷d``p``RVS░⁷●ᶜv░□✽ᵉ☉⁴kIGl♥•😐□⌂1kI✽`q\0⬇️^⬇️⁴*W✽。p●Ep☉゜♥█<\r\r*-⬇️➡️q\0⬇️ワ_⁴oo~ooo○o•+⁴⁴⬇️\r✽ᵉ♥³m░ᵇ⁴o{`IG`nN●▮●⁴░□⁴⬇️•✽!⁴o`Y⬇️}⬇️タ\0\0,=░⁶⁷C~⁴x○⬇️Zm~o○~o`J⬇️◝q☉█░◝‖◀=⬇️⬅️░ひ~xzzyzxyzz•、⬇️♥⬇️⁸░²⬇️‖⬇️ m⬇️\r⬇️_⁴`YG`o{●▮░⁘{✽█o{⁴⁴░.░█i⁷⁷w✽█。\0q░█⁷○y⬇️RxM^zz~{oaI*⁷w\r\r-♥█⬇️⌂%&░⬅️░★⬇️rx░ラ●x░q⬇️●o●▒✽█░モiW`░!]⬇️ュ⬇️R⬇️ˇ✽█⬇️か●9o`Z\r\r=⬇️タq⁷**░●⁷Sox⬇️ケxmxxyy○~`⬇️█⬇️◜。♥█⁷⁷*\r=*q✽█}~○oo~⬇️れ✽w░³⬇️\r⁴oᵇᶜ✽pN✽●⬇️_h✽█✽しoMnnn♥█o⁴░‖⁴⬇️◆░コ░ン●█Xa~♥ょzᵇyz○`J🅾️█,=░ッ⁷⬇️に⁷t`░¹p``BfFFfC⬇️ ●▮k✽[✽A]n`JhaoMN░ヨzMn^zz⁴✽█]⬇️ュ0⬇️~Mn`rPPQVTPL⬇️ハ\0\0q\0G`○y░に⬇️け•zxoaI⬇️█q⌂█✽웃⬇️ぬ░ˇ░wD@@A⬇️く⬇️れDAFffDE@ca⬇️ソ░ユoz•ᶜ⬇️トG`n^mo]n./^░◀●█⬇️◝0z0no^ok●ほkI✽█\0⬇️█ox░>0⬇️ろyzo`JKP⬇️くvQ⬇️⁶け³v⬇️ゃs⬇️^●█░¹`Y░n░ヤ>?●∧♥█nn0⁴⁴oz⁴⬇️⁵✽(I⁷**░ニHa~⬇️wz0y0Nzyx░らl●けた⁶l✽█⬇️~░♥`j░モ░ンm░∧Mnn`IW✽□⁴m⬇️⁴░し⬇️▒░∧,●█⬇️にx░ゆ0y]⬇️さnaJ⬇️.om⬇️Mᵇ+●ち░2♥ふ░「•、☉◀⌂⁸⬇️█⬇️◝mz0zM♥█⬇️ユ^░◝⬇️t`Ih░★⁴⬇️U░そ⬇️テk⬇️♥`I=⁷⁷q\0\0\0G`○x░ぬ]N~zy⬇️らY⬇️.{⬇️<⬇️█、⁴⬇️セ⁴⬇️⁵ᵇᶜ✽bzM░「▤³o]0z0^`Z✽█M^✽▤⬇️1░█a✽けm✽す`bc`░█rQ⁷⬇️¹TL⬇️にy░ゆoox✽█i♥.⬇️○✽サ⬇️ャᵇ+ᶜ░ノ✽◝✽ョᵇ♥▮☉²●█zz0zz`I⬇️█░ヲ✽の⬇️s⬇️マ✽□Mn⬇️コ⁴o●ᶠzok``R⁷⁷SkIH`y░█●かyx`Zh░らzz•●○l`✽¹k░█♥ᶜ…▮k⬇️ホo░そn^~~`J░█☉ち░ね●★웃も♥█⬇️ス~o○o`YG`xxyz⬇️³mx⬇️⁶yaJX`o{N●よ⁴⬇️ゆb@⬇️¹c░⬆️♥ᶜ…▮ck⬇️え웃うlIG⬇️タ⬅️そ░ヨ😐□⬇️!✽ニ⁴⁴zx{o`iH`ox⁴⬇️の~m○~oo○`J⬇️■웃と⁴⬇️ゆKPPPs●█r⬇️ᵇL…▮[░えᵇab@AFD@E@\\[░ねFFDEBD░ま⬇️ヨ[☉ᶠ✽よ\\●█✽ヌ○`Z⬇️にp♥ち░ろ◆ら░オ✽▶░ユ░セk➡️▮Ksaoz•░うQVT⬇️き\0\0\0⁷●¹T░ま●へ😐³s`n⬇️¹Nzz⁴░ト✽に@Ec`⬇️ムb░か░らFf♥と✽に⁴⬇️)░ユmo⁴⁴`I⌂▮⬇️◀⬇️テGl⬇️え✽う✽んI⁷w\0⁷dRVVSS⬇️スlo웃¥⬅️ケ⬇️イzm⬇️◝⬇️█rQVVv✽ˇ░\r⬇️ちLK⌂にQvQ✽¥⬇️た⬇️2z]n░█H`✽\r░■⁴{░█⬇️ノz⬇️⌂o⁴++░き\0。\0⁷C░▮웃⁴•、░゛░わ░\'0●>░█⌂ほ웃ちk⬇️オ➡️に⬇️ヘ✽soᵇᵇ⬇️█a웃█⬇️★`IW웃…•++ᶜ`Y⁷⬇️█⁷웃"☉つ✽█✽ひ♪█✽ま☉あ░わa✽オ✽ぬom☉を☉ユᵇ++✽ユ{N●ろ⁴zo`Ih`ᶜ☉…•+⬇️▮⁷<-☉█⁴⬇️"…⌂0●⁙⁴]♥D♪ち{oaJ░`웃こ⌂▥░つ✽█⬇️ユ웃あ⁴░█a+ᶜ☉そ•、⬇️█q。⁷S…セᵇ☉#o0⬇️ウ✽レ웃テ♪+`⬇️█n⬇️¹░ャ^⬇️█l``k`⬅️¹lIX░▮p◆▮░#k░Z⬇️█\0。⁷t░ 웃$웃 ⁴`bc`░█⬇️V★²B\0\0C⬇️`H░"0⬇️◝░⁵o`b@⬇️¹FfD░⁷░ᵇ\\[@@⌂ᵉ●▮✽、c`ᶜ░█Y░█⁷fF⬅️#♥ a⁴⬇️゛G✽█✽VFFDEB⌂JA⁷⁷q\0D@\\░█om░█z⬇️ `IKP⬇️¹QvQ░⁷⬅️⁴L●「⁷q⁷VvVT⬇️$s`⁴ᶜ⬇️ぬj⁷⁷⬇️█⬇️²<\r\r\r-\0⌂¹s`ooj\0G`⁴░サI\0⁷**☉。q⬅️&⁷*Ha⬇️█]noozMnnn`IGl`⧗¹k♥「RVS`p●!l、░ソi⁷\0●█░✽。dRvSRvVVvS⬇️ ⁴o`eW◆█웃|♥✽⁷*G`⬇️ワ⬇️ュon^⬇️웃⬇️█`⁴⁴⁴o♥¹웃ᵇ░「░ᶠoom⌂▶zzᵇ⬇️█q⬅️█C⬇️<~o○o~om○oz⁴⁴B⁷C░\\░█⁷。\0\0KPQ⬇️うPPQvQP♥¹LW●█mz⬇️ウ⬇️オrs⬇️█⬇️ᶜzz0░a●²♥█zᵇᶜ●)웃³•`I⁷\0✽█q✽█hozyxzz⁴xzmy⬇️Q⁴⁷,\r♥█q░█Gl`p`✽¹●⁷p`kIha⬇️◜0n^⬇️q⬇️ちk``l웃w웃jᵇ♥█z•ᶜo⬇️)●❎Mn⬇️¹`I\r\r=☉◝⁷。C○z{⬇️○⬇️░]nn{zo⬇️、oz⁴░█⁷w*⬇️█⬇️チ⁴omo✽¹✽⁷`Yh⬇️スM^░⁙✽ホ♥ ⌂ッ⬇️らᵇ+♥█o0•0oᶜ♥◝m░0`I웃}░█g~xmz⁴⬇️◝z⬇️◝xy~R⁷S⬇️テ░█,*☉█✽9⬇️よ⬇️ラ⬇️█IX`o{●□◆█0░:░⬆️⬇️█、░█░ユ{⬇️ᶜ❎█q⁷⁷ 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + +-- check missoin status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select", true) + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(3) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,999,999,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end + + sfx(3) +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end + return nil +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 30 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx + dx * self.width/2, + sy + dy * self.height/2, + dx * 4, + dy * 4, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(6) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx = target.x - self.x + local dy = target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + local points = { + {x, y}, + {x + self.width - 1, y}, + {x, y + self.height - 1}, + {x + self.width - 1, y + self.height - 1} + } + + for point in all(points) do + if check_tile_flag(unpack(point)) then + return true + end + end + + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 30 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 30), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state == "attack" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + ending_sequence_timer = max(-1, ending_sequence_timer - 1) + if ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 24, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 32, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +a27070707070707070707070740603030303a7a7a7a7f606947070707070705262707070707070707070707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a27070707070b405050505053706f6a7a7a703a7a7a7f606947070707070a27070a27070707070707070707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a2707070707074c60606060606c6f6a7a7a7a70303030306947070707070707070707070707070707070707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6f6a2f6f6f6f6a7a7a7a7a7a7a7f606947070707070707070707070b40505050505050505050505050505050505050505050505050505c4 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7a7a7f60694707070707070707070707074c6060606060606060606060606060606060606060606060606b694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6f6f6f6f6f6a7a7f6069470707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6c606060624707034c69470707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6062604147070707044c570707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f60694707070707070707070701770707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f60694707070707070707070707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f606270505050505050505c4707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6b60606060606060606b694707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6f6f6f6f6f6f6f6f6f60694707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7f60694707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270701770707406f6f6f6f6f6f6f6f6f6f6a7a7a7a7a7f60694707017707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707017707070707070707070707070707070707070707070 +a2707070707074b60606060606060606b6f6a7a7a7a7a7f60694707070707070707070707406a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a70694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a27070707070b504040404040404043606f6a7a7a7a7a7f606947070707070707070707074b6060606060606060606060606060606060606060606060606c694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707070707070707070707406f6a7a703a7a7f6069470707070707070707070b50404040404040404040404040404040404040404040404040404c5 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707070707070707070707406f6a703a703a7f6069470707070707070707070a200000000000000000000000000000000000000000000000000a270 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707070707070707070707406f6a7a703a7a7f606947070707070707070707070a27000a200000000000000000000000000000000000000707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707070707070707070707406f6a7a7a7a7a7f606947070707070707070707070a270a270a2707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707070707070707070707406f6f6f6f6f6f6f606947070707070707070707070a27070a270707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a2707070707070707070707070707074b606060606060606c6947070707070707070707070a270707070707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +70a2a2a2a2a2a2a2a2a2a2a2a2a2a2b5040404040404040404c5a2a2a2a2a2a2a2a2a2a2a270a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a270 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeee +33333333e33eee333e33333333e33333333e33333333e33e33333333e33333333eeeeeee33333333e33333333e33333333e33333333e33333333e33eeee333ee +33333333e33ee333ee33333333e33333333e33333333e33e33333333e33333333eeeeeee33333333e33333333e33333333e33333333e33333333e333ee333eee +33eeee33e33e333eeeeeeeeeeeeeeeeee33eeeeeee33e33e33eeee33eeeeeeeeeeeeeeee33eeeeeee33eeee33eeeeeee33eeee33eeeeeeeeeeeeee333333eeee +33eeee33e33333eeee33333333e33333333e33333333e33e33eeee33e33333333eeeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33eeee33e3333eeeee33333333e33333333e33333333e33e33eee333e33333333eeeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33eeee33e333eeeeeeeeeeeeeee33e333eee33e333eee33e33ee333eeeeeeeeeeeeeeeee33eeeeeee33eeee33e33e333eeeeee33eeeeeeeeeeeeee333333eeee +33333333e33eeeeeee33333333e33ee333ee33ee333ee33e33e333eee33333333eeeeeee33333333e33333333e33ee333eeeee33eeee33333333e333ee333eee +33333333e3eeeeeeee33333333e33eee333e33eee333e33e33333eeee33333333eeeeeee33333333e33333333e33eee333eeee33eeee33333333333eeee33eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee33eeeeeee33eeee3333eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeee3eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeee3eeee333eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +4b50505050505050515656765450505550505050505550505050505050504c2a2a2a2a2a2a2a2a004b5050505050505050505050505050505050504c2a2a070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +476c6060606060606060606060606060606060606060606060606060606b49070707070707070700476c606060606060606060606060606060606b4900002a0707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f604907070707070707070047606f6f6f6f6d6f6f6f6f6f6f6d6f6f6f6f60490000002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f6f6f6f6f7a7a6f604907070707070707000047606f6f7a7a6d7a7a7a7a7a4d5e7a7a6f6f60490000002a07070707070707070707070707070707070707070707070707070707070771070707070707070707070707710707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f6c6060420707436c4907070707070707070047606f7a7a7a6d7a7a7a7a7a6d7a7a7a7a6f60490000002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60624107070707445c07070707070707070047606f7a7a7a5d4e6f6f6f6f5e7a7a7a7a6f60490000002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a7a7a6f7a7a7a7a6f7a7a7a7a6f60490000002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606e6e6e4e6f6d7a7a7a4d4e6f7a7a4d6e60490007072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a6d6f5d6e2e2f5e5d6e6e6e5e6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a5d6f7a7a3e3f7a7a6f7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707000047606f7a7a7a6f7a7a6d7a7a7a6f7a7a7a6f60490007072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a7a7a6f6e5e7a7a6f7a7a7a4d6e60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707074b73606f7a7a7a4d5e6f6f6f6f7a4d6e6e5e6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6049070707070707070707070707070707476c6c6f7a7a7a6d7a7a7a7a7a7a6d7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +476b60606060606060606060606060606060606060606c4907070707070707070707070707070747606f6f6f7a7a6d7a7a7a7a7a7a6d7a7a6f6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707710707070707070707070707070707070707070707 +5b404040404040404040404040404040404040404040405c07070707070707070707070707070747606f7a7a7a3030303030303030303030306f60490707072a07070707077107070707070707070707070707070707070707070707077107070707070707070707070707070707070707070707070707070707070707070707 +2a000000000000000000000000000000000000000000002a07070707070707070707070707070747606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0000000000000000000000000000000000000000002a0007070707070707070707070707070747606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0000000000000000000000000000000000000000002a0007070707070707070707070707070747606f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0000000000000000000000000000000000000000002a00070707070707070707070707070707476b606060606060606060606060606b6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0000000000000000000000000000000000000000002a000707070707070707070707070707075b4040404040404040404040404063606f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0000000000000000000000000000000000000000002a00070707070707070707070707070707074b50505050505050505050505073606f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a000000000000000000000000000000000000000000002a07070707070707070707070707070707576c6060606060606060606060606c6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a00000000000000000000004b50505050505050505050504c07070707070707070707070707070707436f6f6f6f6f6f6f6f6f6f6f6f6f6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0707070707070707070707476c6060606060606060606b4907070707070707070707070707070707076f7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070757606f6f6f6f6f6f6f6f6f604907070707070707070707070707070707076f7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070707436f7a7a7a7a7a7a7a6f604907070707070707070707070707070707536f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070700007a7a7a7a7a7a7a7a6f6049070707070707070707070707070707586b606060606060606060606060606060606c490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070707076f7a7a7a307a7a7a6f60490707070707070707070707070707075b4040404040404040404040404040404040405c0707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070707076f7a7a307a307a7a6f604907070707070707070707070707070707070707070707070707070707070707070707070707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a077107070707070707070707536f7a7a7a307a7a7a6f604907070707072a07072a07070707070707070707070707070707070707070707070707070707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070758606f7a7a7a7a7a7a7a6f604907070707070715160707070707070707070707070707070707070707070707070707070707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5090000397702d67029770246701e77018670127700c6700a7500565005750056500475004650037500265001730016300073000630007200062000720006200071000610007100161001710016100171001610 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000257571b757147570000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d50e000027d550cd5510d5513d5517d5518d5517d5514d550ed5509d5505d5501d5505d5508d550dd5510d5508d550cd5510d5513d5517d5518d5517d5514d5510d550bd5509d5508d5507d5509d550dd5513d55 +cd0e00000cd550fd5513d550cd550fd5512d550cd550fd5510d550cd550fd5513d550cd550fd5514d5512d550cd550fd5513d550cd550fd5512d550cd550fd550ed550cd550fd5513d5516d5515d5514d5512d55 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +03 1a154344 +00 03164344 +00 02424344 + diff --git a/versions/v0.0.2.p8 b/versions/v0.0.2.p8 new file mode 100644 index 0000000..4dbc1c9 --- /dev/null +++ b/versions/v0.0.2.p8 @@ -0,0 +1,2306 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + + + +-- strech goal: implement a flag for cables sprites, and do a pal swap vertically or horizontally periodically across the map + +-- function compress(data) +-- local result = "" +-- local i = 1 +-- local data_len = #data + +-- while i <= data_len do +-- local best_len, best_dist = 0, 0 + +-- for j = max(1, i - 255), i - 1 do +-- local k = 0 +-- while i + k <= data_len and sub(data, j + k, j + k) == sub(data, i + k, i + k) do +-- k += 1 +-- end + +-- if k > best_len then +-- best_len, best_dist = k, i - j +-- end +-- end + +-- if best_len > 2 then +-- result = result..chr(128 + best_len)..chr(best_dist) +-- i += best_len +-- else +-- result = result..sub(data, i, i) +-- i += 1 +-- end +-- end + +-- return result +-- end + +-- function test_and_save_map_compression(start_address, end_address, filename) +-- -- Gather map data +-- local map_data = "" +-- for addr = start_address, end_address do +-- map_data = map_data..chr(peek(addr)) +-- end + +-- -- Compress the data +-- local compressed = compress(map_data) + +-- -- Convert compressed data to Lua string +-- local lua_string = "" +-- for x in all(compressed) do +-- lua_string = lua_string.."\\"..ord(x) +-- end + +-- -- Save to file +-- printh(lua_string, filename, true) +-- printh("Compressed data saved to "..filename) +-- end + +function decompress_to_memory(data, mem_index) + local i = 1 + while i <= #data do + local byte = ord(data[i]) + if byte >= 128 then + local source = mem_index - ord(data[i + 1]) + for j = 1, byte - 128 do + poke(mem_index, peek(source + j - 1)) + mem_index += 1 + end + i += 2 + else + poke(mem_index, byte) + mem_index += 1 + i += 1 + end + end +end + + + +-- HELPER FUNCTIONS +---------------------- +function reset_pal(_cls) + pal() + palt(0,false) + palt(14,true) + if _cls then cls() end +end + +function check_tile_flag(x, y, flag) + return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) +end + +function stringToTable(str) + local tbl = {} + for pair in all(split(str, "|", false)) do + add(tbl, split(pair, ",", true)) + end + return tbl +end + +function dist_trig(dx, dy) + local ang = atan2(dx, dy) + return dx * cos(ang) + dy * sin(ang) +end + +function draw_shadow(circle_x, circle_y, radius, swap_palette) + local swapped_palette = {} + for i = 0, 15 do + for j = 0, 15 do + local index = (i << 4) | j + swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] + end + end + + -- Pre-compute squared radius + local radius_squared = radius * radius + + -- calculate the top and bottom y of the circle + local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) + + -- Function to swap palette for a line + local function swap_line(y, start_x, end_x) + local line_start_addr = 0x6000 + y * 64 + for i = 0, start_x >> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + +-- CAMERA +---------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +-------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(9, 94, 20, 110, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "rEMAINING DATA SHARDS: " .. count_remaining_fragments() .. + "\niNFECTED UNITS ONLINE: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- STATE MANAGEMENT +---------------------- +function _init() + -- test_and_save_map_compression(0x2000, 0x2fff, "compressed_map_upper.txt") + -- test_and_save_map_compression(0x1000, 0x1fff, "compressed_map_lower.txt") + + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS, mission_data = { + "PROTOCOL ZERO:\n\nCONTAINMENT\nFACILITY ALPHA-7\nCOMPROMISED\n\nACTIVATE ALL \nTERMINALS TO \nRESTORE FACILITY\nLOCKDOWN", + "SILICON GRAVEYARD:\n\nBARRACUDA VIRUS\nSPREADS TO MEGA-\nCITY DISPOSAL SITE\n\nTRAVERSE HAZARDOUS\nWASTE, EVADE OR \nNEUTRALIZE\nSCAVENGER BOTS", + "NEURAL NEXUS:\n\nBARRACUDA ASSAULTS\nCITY'S CENTRAL CORTEX\n\nBATTLE THROUGH\nVIRTUAL MINDSCAPE\nOF INFECTED AIs", + "HEAVEN'S SPIRE:\n\nLAST STAND ATOP THE\nGLOBAL NETWORK HUB\n\nASCEND THE TOWER,\nCONFRONT BARRACUDA,\nACTIVATE CORTEX\nPROTOCOL" + }, {} + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 3000, 3 + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 42,1,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + + change_state("mission_select", false) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, use_transition) + local new_state = states[new_state_name] + + if use_transition and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + intro_counter, intro_blink = 0, 0 + x_cortex, x_protocol = -50, 128 + + TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_page = 1 + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + if intro_counter == 0 then music(05) end + + intro_counter += 1 + intro_blink += 0.02 + + local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol + x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) + x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + + if prev_x_cortex != TITLE_FINAL_X_CORTEX and x_cortex == TITLE_FINAL_X_CORTEX or + prev_x_protocol != TITLE_FINAL_X_PROTOCOL and x_protocol == TITLE_FINAL_X_PROTOCOL then + sfx(20) + end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select", true) + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(intro_blink) < .9 then circfill(63,64, 3, 2) end + + display_logo(x_cortex, x_protocol, 0, 12) + + intro_text_panel:draw() + controls_text_panel:draw() + print("PRESS ❎ TO CONTINUE", 24, 118, 11) +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,67,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + elseif btnp(⬅️) or btnp(➡️) then + sfx(19) + show_briefing = not show_briefing + elseif btnp(❎) then + change_state("loadout_select", true) + elseif btnp(🅾️) then + change_state("intro", true) + end + + if prev != current_mission or btnp(⬅️) or btnp(➡️) then + info_panel.char_count = 0 + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select", true) + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay", true) + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + if current_mission <= 2 then + compressed_map_lower="Gk`⬅️¹lbc☉▮kozz⬇️³░⁴⁴{`IG`⁴、、✽■✽³o0^0n^o`i⁷●¹q✽⁸KPPQ⬇️ᵇVvVTP✽¹s`░F웃cl⬇️Tm●E●⁵`Y[@@@FfD⬇️⁶✽³\\☉▮c░:zoo░⬇️⁴o░█웃|●░{⬇️うᵇ`Z♥z●♥Wl``RVS`p⌂カ░を⬅️¹⬇️█om⬇️サMN✽チ`j⬇️は░つQvQ●の☉⁶✽む☉メ⁴…█Mnn0ozᵇ+`I✽◝*✽³⬇️웃C☉l⁴●³0ᶜᶜ+ᶜ🅾️まmo]n./^]on⬇️¹`iG⬇️はˇ²░む웃▒…█⬇️れ░○☉█⁷‖◀⬇️◝,\r\r\r♥ふ⁴●⁵⁴0•+•、✽っᵇ♥♥M]⬇️ン>?░@⬇️'⬇️█`o•++、om●む♥⁷●ん✽J⬇️Ko⬆️█zz•⁴⁴`Y●█%&⬇️◝。⁷q\0⌂ふ●⬇️0⬇️~░Z⬇️てᶜ⬇️bMn^░y░?●█░ス░█●R♥Y😐░⁴⁴░▥✽█♥▮░■░なᵇ+⬇️█Z✽◝*⬇️³\r\r=⁷⁷S⌂8●;0ozxol`♥¹k^✽_⬇️✽✽◝zo`YH⬇️ス•、☉ンM^✽ロ●2p☉7``lIWkko○웃I⌂「✽◝☉⁵Xk…!kozyo`b@✽¹c⬇️lzz░d░fMnnn`iHa⬇️⌂⬇️u]N♥◝░▒░2fD●5●;\\qFao⬇️IAFFE@@F░。Ef@D@\\⁷⁷\0⌂¹[⬇️hFFDEB☉?n^✽M⁴⁴`IW`⁴z⬇️▥⬇️⁴•+、○`J⁷H⁴xy✽}░⁶░◝⁷\0,=⁷⁷h`⬇️のN●そzM░ゆ^⬇️█⁷♥█⬇️さ░+q⁷⁷**⁷⁷KPQVVvQ⬇️めVvVPQvQPPLG░@^✽k░る✽r░█h⬇️█omz0░▒、o`j⁷h⁴⁴⬇️f○oo~⬇️²|✽█⬇️◝⁷ha⬇️む⬇️やᵇo•ᶜ░B⬇️●o`i⁷⁷<\r-웃|⬇️◜,░█Wl`░¹RVS`p✽\n`k⬇️ヘo✽フzo♥●░ロ⬇️るha○zo]0y0✽ケai⁷tBFfCfFC✽:u✽█░ケg░@mᶜ0•ᶜo、⬇️るᵇ+++oO░レ░ヨ●|░て░つ⁷⁷Co○~⬇️テ⁴o~⬇️イ⬇️⁶⬇️hg`⬇️ス⬇️ンM^⬇️テ⬇️◜✽on⬇️█●▤0y^░ナ░キ♥MfFfFF\0░サ⌂█]0z0░せ^░○、z✽レ░ヨ⌂²✽█\0\0~░マzx✽⁶z⁴{o`Yh░█⬇️ワ]N●‖mzooᵇᶜ`IX⬇️スzon^z░3○✽キ⌂P\0\0웃サ░(░そ⬇️し⬇️を•、•、z⬇️█\0\0\0。⬇️⁴dRVSe⬇️ᶜq\0<**⬇️⁘\0o♥{●▒]⬇️ヘg⬇️█⬇️s░y●█⬇️○+、`IH⬇️スM^oo░2xy●◜░レ✽⁷✽█q\0░サG`oᶜ✽そ░○♥]✽█<-\0\0h~~○j░x\0\0░█q\0⬅️%✽ヤ`Y✽ヘ⬇️ルo⬇️y░²mozᵇ、o⬇️█aᶜ☉⧗✽R♥ヲ░わ⬇️ュ*✽\r░█W`o+✽そzzᵇᵇ⬇️⌂░F|✽そ⬇️えg○{oj⁷░と✽ᶠ\0\0⬇️て⬇️オ⬅️█i⬇️ら{zzMn⬇️る░@z]Nᵇ、{⬇️█G`+ᶜmzy●コy♥█,*\r\r\r-●\\*⬇️░░c`o、●そᵇ、••ᶜoz⁴⁴⁴`Z░゜=q⁷h○~~R░█⬇️-⬇️て⁷\0~•⬇️c{N●◝⁴zo`Z⬇️█♥ョᵇ+++ᶜ⬇️ᵇ•⬇️イ`IW`+、mo~⬇️セ○~○~░キ⬇️|=░セ<░ \r\r*⬇️😐w⁷⁷X⬇️@░>oᵇ、✽ュ⬇️○⁴`I●か⁷tBqCu✽█w✽◆¹░ョ⬇️て➡️,lI[@✽¹AqDAffFFDEBD░□\\q⁷CBFFf⬇️²A⬇️⁶░オ∧²♥@@@⌂<⬇️>あ,😐'░h" + compressed_map_upper='⁷⁷*∧¹⁷⁷SvvQ⁷q⁷VvVTPPQ⬇️ᶜ⁷⬇️⁸⬇️²UQVVv⬇️⁷░\rLK☉•●‖♥□⬇️!♥1L♪`\0K░<░:⬇️>░A⬇️゜⁷♥¹\0\0。✽ᵇ●⁶⁷d``p``RVS░⁷●ᶜv░□✽ᵉ☉⁴kIGl♥•😐□⌂1kI✽`q\0⬇️^⬇️⁴*W✽。p●Ep☉゜♥█<\r\r*-⬇️➡️q\0⬇️ワ_⁴oo~ooo○o•+⁴⁴⬇️\r✽ᵉ♥³m░ᵇ⁴o{`IG`nN●▮●⁴░□⁴⬇️•✽!⁴o`Y⬇️}⬇️タ\0\0,=░⁶⁷C~⁴x○⬇️Zm~o○~o`J⬇️◝q☉█░◝‖◀=⬇️⬅️░ひ~xzzyzxyzz•、⬇️♥⬇️⁸░²⬇️‖⬇️ m⬇️\r⬇️_⁴`YG`o{●▮░⁘{✽█o{⁴⁴░.░█i⁷⁷w✽█。\0q░█⁷○y⬇️RxM^zz~{oaI*⁷w\r\r-♥█⬇️⌂%&░⬅️░★⬇️rx░ラ●x░q⬇️●o●▒✽█░モiW`░!]⬇️ュ⬇️R⬇️ˇ✽█⬇️か●9o`Z\r\r=⬇️タq⁷**░●⁷Sox⬇️ケxmxxyy○~`⬇️█⬇️◜。♥█⁷⁷*\r=*q✽█}~○oo~⬇️れ✽w░³⬇️\r⁴oᵇᶜ✽pN✽●⬇️_h✽█✽しoMnnn♥█o⁴░‖⁴⬇️◆░コ░ン●█Xa~♥ょzᵇyz○`J🅾️█,=░ッ⁷⬇️に⁷t`░¹p``BfFFfC⬇️ ●▮k✽[✽A]n`JhaoMN░ヨzMn^zz⁴✽█]⬇️ュ0⬇️~Mn`rPPQVTPL⬇️ハ\0\0q\0G`○y░に⬇️け•zxoaI⬇️█q⌂█✽웃⬇️ぬ░ˇ░wD@@A⬇️く⬇️れDAFffDE@ca⬇️ソ░ユoz•ᶜ⬇️トG`n^mo]n./^░◀●█⬇️◝0z0no^ok●ほkI✽█\0⬇️█ox░>0⬇️ろyzo`JKP⬇️くvQ⬇️⁶け³v⬇️ゃs⬇️^●█░¹`Y░n░ヤ>?●∧♥█nn0⁴⁴oz⁴⬇️⁵✽(I⁷**░ニHa~⬇️wz0y0Nzyx░らl●けた⁶l✽█⬇️~░♥`j░モ░ンm░∧Mnn`IW✽□⁴m⬇️⁴░し⬇️▒░∧,●█⬇️にx░ゆ0y]⬇️さnaJ⬇️.om⬇️Mᵇ+●ち░2♥ふ░「•、☉◀⌂⁸⬇️█⬇️◝mz0zM♥█⬇️ユ^░◝⬇️t`Ih░★⁴⬇️U░そ⬇️テk⬇️♥`I=⁷⁷q\0\0\0G`○x░ぬ]N~zy⬇️らY⬇️.{⬇️<⬇️█、⁴⬇️セ⁴⬇️⁵ᵇᶜ✽bzM░「▤³o]0z0^`Z✽█M^✽▤⬇️1░█a✽けm✽す`bc`░█rQ⁷⬇️¹TL⬇️にy░ゆoox✽█i♥.⬇️○✽サ⬇️ャᵇ+ᶜ░ノ✽◝✽ョᵇ♥▮☉²●█zz0zz`I⬇️█░ヲ✽の⬇️s⬇️マ✽□Mn⬇️コ⁴o●ᶠzok``R⁷⁷SkIH`y░█●かyx`Zh░らzz•●○l`✽¹k░█♥ᶜ…▮k⬇️ホo░そn^~~`J░█☉ち░ね●★웃も♥█⬇️ス~o○o`YG`xxyz⬇️³mx⬇️⁶yaJX`o{N●よ⁴⬇️ゆb@⬇️¹c░⬆️♥ᶜ…▮ck⬇️え웃うlIG⬇️タ⬅️そ░ヨ😐□⬇️!✽ニ⁴⁴zx{o`iH`ox⁴⬇️の~m○~oo○`J⬇️■웃と⁴⬇️ゆKPPPs●█r⬇️ᵇL…▮[░えᵇab@AFD@E@\\[░ねFFDEBD░ま⬇️ヨ[☉ᶠ✽よ\\●█✽ヌ○`Z⬇️にp♥ち░ろ◆ら░オ✽▶░ユ░セk➡️▮Ksaoz•░うQVT⬇️き\0\0\0⁷●¹T░ま●へ😐³s`n⬇️¹Nzz⁴░ト✽に@Ec`⬇️ムb░か░らFf♥と✽に⁴⬇️)░ユmo⁴⁴`I⌂▮⬇️◀⬇️テGl⬇️え✽う✽んI⁷w\0⁷dRVVSS⬇️スlo웃¥⬅️ケ⬇️イzm⬇️◝⬇️█rQVVv✽ˇ░\r⬇️ちLK⌂にQvQ✽¥⬇️た⬇️2z]n░█H`✽\r░■⁴{░█⬇️ノz⬇️⌂o⁴++░き\0。\0⁷C░▮웃⁴•、░゛░わ░\'0●>░█⌂ほ웃ちk⬇️オ➡️に⬇️ヘ✽soᵇᵇ⬇️█a웃█⬇️★`IW웃…•++ᶜ`Y⁷⬇️█⁷웃"☉つ✽█✽ひ♪█✽ま☉あ░わa✽オ✽ぬom☉を☉ユᵇ++✽ユ{N●ろ⁴zo`Ih`ᶜ☉…•+⬇️▮⁷<-☉█⁴⬇️"…⌂0●⁙⁴]♥D♪ち{oaJ░`웃こ⌂▥░つ✽█⬇️ユ웃あ⁴░█a+ᶜ☉そ•、⬇️█q。⁷S…セᵇ☉#o0⬇️ウ✽レ웃テ♪+`⬇️█n⬇️¹░ャ^⬇️█l``k`⬅️¹lIX░▮p◆▮░#k░Z⬇️█\0。⁷t░ 웃$웃 ⁴`bc`░█⬇️V★²B\0\0C⬇️`H░"0⬇️◝░⁵o`b@⬇️¹FfD░⁷░ᵇ\\[@@⌂ᵉ●▮✽、c`ᶜ░█Y░█⁷fF⬅️#♥ a⁴⬇️゛G✽█✽VFFDEB⌂JA⁷⁷q\0D@\\░█om░█z⬇️ `IKP⬇️¹QvQ░⁷⬅️⁴L●「⁷q⁷VvVT⬇️$s`⁴ᶜ⬇️ぬj⁷⁷⬇️█⬇️²<\r\r\r-\0⌂¹s`ooj\0G`⁴░サI\0⁷**☉。q⬅️&⁷*Ha⬇️█]noozMnnn`IGl`⧗¹k♥「RVS`p●!l、░ソi⁷\0●█░✽。dRvSRvVVvS⬇️ ⁴o`eW◆█웃|♥✽⁷*G`⬇️ワ⬇️ュon^⬇️웃⬇️█`⁴⁴⁴o♥¹웃ᵇ░「░ᶠoom⌂▶zzᵇ⬇️█q⬅️█C⬇️<~o○o~om○oz⁴⁴B⁷C░\\░█⁷。\0\0KPQ⬇️うPPQvQP♥¹LW●█mz⬇️ウ⬇️オrs⬇️█⬇️ᶜzz0░a●²♥█zᵇᶜ●)웃³•`I⁷\0✽█q✽█hozyxzz⁴xzmy⬇️Q⁴⁷,\r♥█q░█Gl`p`✽¹●⁷p`kIha⬇️◜0n^⬇️q⬇️ちk``l웃w웃jᵇ♥█z•ᶜo⬇️)●❎Mn⬇️¹`I\r\r=☉◝⁷。C○z{⬇️○⬇️░]nn{zo⬇️、oz⁴░█⁷w*⬇️█⬇️チ⁴omo✽¹✽⁷`Yh⬇️スM^░⁙✽ホ♥ ⌂ッ⬇️らᵇ+♥█o0•0oᶜ♥◝m░0`I웃}░█g~xmz⁴⬇️◝z⬇️◝xy~R⁷S⬇️テ░█,*☉█✽9⬇️よ⬇️ラ⬇️█IX`o{●□◆█0░:░⬆️⬇️█、░█░ユ{⬇️ᶜ❎█q⁷⁷ 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + +-- check missoin status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select", true) + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(3) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,10,10,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end + + sfx(3) +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end + return nil +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 30 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx + dx * self.width/2, + sy + dy * self.height/2, + dx * 4, + dy * 4, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(6) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx = target.x - self.x + local dy = target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + local points = { + {x, y}, + {x + self.width - 1, y}, + {x, y + self.height - 1}, + {x + self.width - 1, y + self.height - 1} + } + + for point in all(points) do + if check_tile_flag(unpack(point)) then + return true + end + end + + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 30 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 30), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + printh(self.interactive) + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + ending_sequence_timer = max(-1, ending_sequence_timer - 1) + if ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 24, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 32, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +a27070707070707070707070740603030303a7a7a7a7f606947070707070705262707070707070707070707070701770707070707070707070c3d0d0777070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a27070707070b405050505053706f6a7404003a7a74040069470707070c2a27070a2d270707070707070707070707070707070707070707070177070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a2707070707074c60606060606c6f6a7a7a74003030303069470707070d100707070d170707070707070707070707070707070707070707070707070707070a2 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6404040f6f6f6a7a7a7a7a7a7a7f6069470707070d100707070d170b40505050515707070704505050505050505050505050505050505c4 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7a7a7f6069470707070d100707070d17074c6060606062570703506060606060606060606060606060606b694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6f6f6f6f6f6a7a7f6069470707070c3d2707070c3d07406f6f6f6f6f6a7a7f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406c0a7a7a7a7a7f6c606060624707034c6947070707070d170707070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406b2c0a7a7a7a7f6062604147070707044c57070707070d170707070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406b2c1a7a7a7a7f6069470707070707070707070177070c3d0d0d0d0d07406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406b2c0a7a7a7a7f60694707070707070707070707070707070177070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406b2c1a7a7a7a7f606270505050505050505c4707070707070707070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406c1a7a7a7a7a7f6b60606060606060606b694707070707070707070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7f6f6f6f6f6f6f6f6f6f60694707070707070707070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7f60694707070707070707070707406f6a7a7a7a7a7a7a7a7a7a7f6a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270701770707406f6a740a7a7a7a7a7a7a7a7a7a7a7a7f60694707017707070707070707406f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707017707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7f606947070707070707070177074b6060606060606060606060606060606b6f6a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7f6069470707070707070707070b50404040404040404040404040404043606f6a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a270707070707406f6f6f6f6f6f6f6f6f6f6a7a703a7a7f6069470707070707070707000a2000000000000d100000000000000007506f6a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a2707070707074b60606060606060606b6f6a703a703a7f6069470707070707070707070a2000000000000d100170000000000000034f6a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a27070707070b504040404040404043606f6a7a703a7a7b006947070707017707070707070a27000a2d0d0d300000000000000000070a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a27070707070707070707070707070740640a7a7a7a7b0b206947070707070707070707070a2d0a270a2707070707070707070707070a7a7a7a7a7a7a7f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a27070707070707070707070777070740640f6f6f6f6b2b206947070707070707070707070a27070a2d2707070707070701770707035f6f6f6f6f6f6f6f60694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +a2707070707070707070707070707074b606060606060606c6947070707070707070707070a2707070d17070707070707070707085b60606060606060606c694 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +70a2a2a2a2a2a2a2a2a2a2a2a2a2a2b5040404040404040404c5a2a2a2a2a2a2a2a2a2a2a270a2a2a2a2a2a2a2a2a2a2a2a2a2a2b504040404040404040404c5 +70707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070 +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeee +33333333e33333333e33333333e33333333e33333333e33333333e33333333ee33eeeeee33333333e33333333e33333333e33333333e33333333e33eeee333ee +33333333e33333333e33333333e33333333e33333333e33333333e33333333ee33eeeeee33333333e33333333e33333333e33333333e33333333e333ee333eee +eeeeee33eeeeeee33e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33eeeeeee33eeee33eeeeeeeeeeeeee333333eeee +33333333e33333333e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33333333e33333333e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33eeeeeee33e333eee33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33e333eeeeee33eeeeeeeeeeeeee333333eeee +33eeeeeee33ee333ee33333333eeee33eeee33333333e33333333e33333333ee3333333333333333e33333333e33ee333eeeee33eeee33333333e333ee333eee +33eeeeeee33eee333e33333333eeee33eeee33333333e33333333e33333333ee3333333333333333e33333333e33eee333eeee33eeee33333333333eeee33eee +33eeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeee3eee +3eeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeee +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +4b50505050505050515656765450505550505050505550505050505050504c2a2a2a2a2a2a2a2a004b5050505050505050505050505050505050504c2a2a070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +476c6060606060606060606060606060606060606060606060606060606b49070707070707070700476c606060606060606060606060606060606b4900002a0707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f6f6d6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f604907070707070707070047606f6f6f6f6d6f6f6f6f6f6f6d6f6f6f6f60490000002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a6d7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f6f6f6f6f7a7a6f604907070707070707000047606f6f7a7a6d7a7a7a7a7a4d5e7a7a6f6f60490077002a07070707070707070707070707070707070707070707070707070707070771070707070707070707070707710707070707070707070707070707070707070707 +47606f7a5d4e6f6f6f6f7a7a7a307a7a7a7a7a7a7a6f6c6060420707436c4907070707070707070047606f7a7a7a6d7a7a7a7a7a6d7a7a7a7a6f6049001d002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a6f7a7a7a7a6f7a7a7a7a7a7a7a7a7a7a6f60624107070707445c07070707070707070047606f7a7a7a5d4e6f6f6f6f5e7a7a7a7a6f60490d3d002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a6f6d7a7a7a4d4e6f7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a7a7a6f7a7a7a7a6f7a7a7a7a6f60497100002a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a6f6d7a2e2f5e5d6e4e7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606e6e6e4e6f6d7a7a7a4d4e6f7a7a4d6e60490007072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a6f5d6e3e3f7a7a6f6d7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a6d6f5d6e2e2f5e5d6e6e6e5e6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a6f7a7a7a6d7a7a6f6d7a7a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a5d6f7a7a3e3f7a7a6f7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a7a6f6e6e5e7a6f7a6d7a7a7a7a7a7a7a7a6f60490707070707070707070707070707000047606f7a7a7a6f7a7a6d7a7a7a6f7a7a7a6f60490007072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a4d5e6f6f6f6f7a4d5e307a7a7a7a7a7a7a6f60490707070707070707070707070707070047606f7a7a7a7a6f6e5e7a7a6f7a7a7a4d6e60490777712a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f7a6d7a7a7a7a7a7a6d7a7a7a7a7a7a7a7a7a6f60490707070707070707070707070707074b73606f7a7a7a4d5e6f6f6f6f7a4d6e6e5e6f6049071d072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +47606f6f6d6f6f6f6f6f6f6d6f6f6f6f6f6f6f6f6f6f6049070707070707070707070707070707476c6c6f7a7a7a6d7a7a7a7a7a7a6d7a7a7a6f6049071d072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +476b60606060606060606060606042000707436060606c4907070707070707070707070707070747606f6f6f7a7a6d7a7a7a7a7a7a6d7a7a6f6f6049071d072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707710707070707070707070707070707070707070707 +5b404040404040404040404040410700070707444040405c07070707070707070707070707070747606f7a7a7a3030303030303030303030306f6049073c0d2a07070707077107070707070707070707070707070707070707070707077107070707070707070707070707070707070707070707070707070707070707070707 +2a000000000000000000000000000000000000000000002a07070707070707070707070707070747606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a000000000000000000000000000000000000000000002a07070707070707070707070707070747606f7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60497107072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a000000000000000000000000000000000000000000002a07070707070707070707070707070747606f6f6f6f6f6f6f6f6f6f6f6f6f6f6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a00000000000000000000000000000000000000000000002a0707070707070707070707070707476b606060606060606060606060606b6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a00000000000000000000000000000000000000000000002a07070707070707070707070707075b4040404040404040404040404063606f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a00000000000000000000000000000000000000000000002a0707070707070707070707070707074b50505050505050505050505073606f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a00000000000000000000000000000000000000000000002a070707070707070707070707070707576c6060606060606060606060606c6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a00000000000000000000004b50505050505050505050504c07070707070707070707070707070707436f6f6f6f6f6f6f6f6f6f6f6f6f6f7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a0707070707070707070707476c6060606060606060606b4907070707070707070707070707070707076f7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490771072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070757606f6f6f6f6f6f04040460490d0d0d0d2d0707070707070707070707076f7a7a7a7a7a7a7a7a7a7a7a7a7a7a6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070707436f7a7a7a7a6f0404046049070707073c0d0d0d2d07070707070707536f6f6f6f6f6f6f6f6f6f6f6f6f6f6f6f60490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070700007a7a7a7a7a6f6f6f6f604907070707070707071d070707070707586b606060606060606060606060606060606c490707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070707077a7a7a7a307a7a7a6f60490d0d2d07070707071d0707070707075b4040404040404040404040404040404040405c0707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070707077a7a7a307a307a7a6f604907071d07070707071d07070707070707070707070707070707070707070707071d07070707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a077107070707070707070707536f7a7a7a307a7a7a6f604907073c0d0d2a07072a07070707070707070707070707070707070707070707071d07070707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +2a070707070707070707070758606f7a7a7a7a7a7a7a6f604907070707070715160707070707070771070707070707070707070707710707071d07070707072a07070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707070707 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5090000397702d67029770246701e77018670127700c6700a7500565005750056500475004650037500265001730016300073000630007200062000720006200071000610007100161001710016100171001610 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000257571b757147570000000001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d50e000027d550cd5510d5513d5517d5518d5517d5514d550ed5509d5505d5501d5505d5508d550dd5510d5508d550cd5510d5513d5517d5518d5517d5514d5510d550bd5509d5508d5507d5509d550dd5513d55 +cd0e00000cd550fd5513d550cd550fd5512d550cd550fd5510d550cd550fd5513d550cd550fd5514d5512d550cd550fd5513d550cd550fd5512d550cd550fd550ed550cd550fd5513d5516d5515d5514d5512d55 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +03 1a154344 +00 03164344 +00 02424344 + diff --git a/versions/v0.0.3.p8 b/versions/v0.0.3.p8 new file mode 100644 index 0000000..f5eec70 --- /dev/null +++ b/versions/v0.0.3.p8 @@ -0,0 +1,2358 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + + + +-- strech goal: implement a flag for cables sprites, and do a pal swap vertically or horizontally periodically across the map + +-- function compress(data) +-- local result = "" +-- local i = 1 +-- local data_len = #data + +-- while i <= data_len do +-- local best_len, best_dist = 0, 0 + +-- for j = max(1, i - 255), i - 1 do +-- local k = 0 +-- while i + k <= data_len and sub(data, j + k, j + k) == sub(data, i + k, i + k) do +-- k += 1 +-- end + +-- if k > best_len then +-- best_len, best_dist = k, i - j +-- end +-- end + +-- if best_len > 2 then +-- result = result..chr(128 + best_len)..chr(best_dist) +-- i += best_len +-- else +-- result = result..sub(data, i, i) +-- i += 1 +-- end +-- end + +-- return result +-- end + +-- function test_and_save_map_compression(start_address, end_address, filename) +-- -- Gather map data +-- local map_data = "" +-- for addr = start_address, end_address do +-- map_data = map_data..chr(peek(addr)) +-- end + +-- -- Compress the data +-- local compressed = compress(map_data) + +-- -- Convert compressed data to Lua string +-- local lua_string = "" +-- for x in all(compressed) do +-- lua_string = lua_string.."\\"..ord(x) +-- end + +-- -- Save to file +-- printh(lua_string, filename, true) +-- printh("Compressed data saved to "..filename) +-- end + +function decompress_to_memory(data, mem_index) + local i = 1 + while i <= #data do + local byte = ord(data[i]) + if byte >= 128 then + local source = mem_index - ord(data[i + 1]) + for j = 1, byte - 128 do + poke(mem_index, peek(source + j - 1)) + mem_index += 1 + end + i += 2 + else + poke(mem_index, byte) + mem_index += 1 + i += 1 + end + end +end + + + +-- HELPER FUNCTIONS +---------------------- +function reset_pal(_cls) + pal() + palt(0,false) + palt(14,true) + if _cls then cls() end +end + +function check_tile_flag(x, y, flag) + return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) +end + +function stringToTable(str) + local tbl = {} + for pair in all(split(str, "|", false)) do + add(tbl, split(pair, ",", true)) + end + return tbl +end + +function dist_trig(dx, dy) + local ang = atan2(dx, dy) + return dx * cos(ang) + dy * sin(ang) +end + +function draw_shadow(circle_x, circle_y, radius, swap_palette) + local swapped_palette = {} + for i = 0, 15 do + for j = 0, 15 do + local index = (i << 4) | j + swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] + end + end + + -- Pre-compute squared radius + local radius_squared = radius * radius + + -- calculate the top and bottom y of the circle + local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) + + -- Function to swap palette for a line + local function swap_line(y, start_x, end_x) + local line_start_addr = 0x6000 + y * 64 + for i = 0, start_x >> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + +-- CAMERA +---------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +-------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(13, 94, 20, 102, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "dATA SHARDS LEFT: " .. count_remaining_fragments() .. + "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- STATE MANAGEMENT +---------------------- +function _init() + -- test_and_save_map_compression(0x2000, 0x2fff, "compressed_map_upper_2.txt") + -- test_and_save_map_compression(0x1000, 0x1fff, "compressed_map_lower_2.txt") + + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS, mission_data = { + "PROTOCOL ZERO:\n\nCONTAINMENT\nFACILITY ALPHA-7\nCOMPROMISED\n\nACTIVATE ALL \nTERMINALS TO \nRESTORE FACILITY\nLOCKDOWN", + "SILICON GRAVEYARD:\n\nBARRACUDA VIRUS\nSPREADS TO MEGA-\nCITY DISPOSAL SITE\n\nTRAVERSE HAZARDOUS\nWASTE, EVADE OR \nNEUTRALIZE\nSCAVENGER BOTS", + "NEURAL NEXUS:\n\nBARRACUDA ASSAULTS\nCITY'S CENTRAL CORTEX\n\nBATTLE THROUGH\nVIRTUAL MINDSCAPE\nOF INFECTED AIs", + "HEAVEN'S SPIRE:\n\nLAST STAND ATOP THE\nGLOBAL NETWORK HUB\n\nASCEND THE TOWER,\nCONFRONT BARRACUDA,\nACTIVATE CORTEX\nPROTOCOL" + }, {} + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 3000, 3 + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 42,1,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + + change_state("mission_select", false) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, use_transition) + local new_state = states[new_state_name] + + if use_transition and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + music(05) + intro_counter, intro_blink = 0, 0 + x_cortex, x_protocol = -50, 128 + + TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_page = 1 + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + intro_counter += 1 + intro_blink += 0.02 + + local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol + x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) + x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + + if prev_x_cortex != TITLE_FINAL_X_CORTEX and x_cortex == TITLE_FINAL_X_CORTEX or + prev_x_protocol != TITLE_FINAL_X_PROTOCOL and x_protocol == TITLE_FINAL_X_PROTOCOL then + sfx(20) + end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select", true) + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(intro_blink) < .9 then circfill(63,64, 3, 2) end + + display_logo(x_cortex, x_protocol, 0, 12) + + intro_text_panel:draw() + controls_text_panel:draw() + print("PRESS ❎ TO CONTINUE", 24, 118, 11) +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + music(0) + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,67,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + elseif btnp(⬅️) or btnp(➡️) then + sfx(19) + show_briefing = not show_briefing + elseif btnp(❎) then + change_state("loadout_select", true) + elseif btnp(🅾️) then + change_state("intro", true) + end + + if prev != current_mission or btnp(⬅️) or btnp(➡️) then + info_panel.char_count = 0 + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select", true) + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay", true) + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + if current_mission <= 2 then + compressed_map_lower="Gk`⬅️¹lbc☉▮kozz⬇️³░⁴⁴{`IG`⁴、、✽■✽³o0^0n^o`i⁷●¹q✽⁸KPPQ⬇️ᵇVvVTP✽¹s`░F웃cl⬇️Tm●E●⁵`Y[@@@FfD⬇️⁶✽³\\☉▮c░:zoo░⬇️⁴o░█웃|●░{⬇️うᵇ`Z♥z●♥Wl``RVS`p⌂カ░を⬅️¹⬇️█om⬇️サMN✽チ`j⬇️は░つQvQ●の☉⁶✽む☉メ⁴…█Mnn0ozᵇ+`I✽◝*✽³⬇️웃C☉l⁴●³0ᶜᶜ+ᶜ🅾️まmo]n./^]on⬇️¹`iG⬇️はˇ²░む웃▒…█⬇️れ░○☉█⁷‖◀⬇️◝,\r\r\r♥ふ⁴●⁵⁴0•+•、✽っᵇ♥♥M]⬇️ン>?░@⬇️'⬇️█`o•++、om●む♥⁷●ん✽J⬇️Ko⬆️█zz•⁴⁴`Y●█%&⬇️◝。⁷q\0⌂ふ●⬇️0⬇️~░Z⬇️てᶜ⬇️bMn^░y░?●█░ス░█●R♥Y😐░⁴⁴░▥✽█♥▮░■░なᵇ+⬇️█Z✽◝*⬇️³\r\r=⁷⁷S⌂8●;0ozxol`♥¹k^✽_⬇️✽✽◝zo`YH⬇️ス•、☉ンM^✽ロ●2p☉7``lIWkko○웃I⌂「✽◝☉⁵Xk…!kozyo`b@✽¹c⬇️lzz░d░fMnnn`iHa⬇️⌂⬇️u]N♥◝░▒░2fD●5●;\\qFao⬇️IAFFE@@F░。Ef@D@\\⁷⁷\0⌂¹[⬇️hFFDEB☉?n^✽M⁴⁴`IW`⁴z⬇️▥⬇️⁴•+、○`J⁷H⁴xy✽}░⁶░◝⁷\0,=⁷⁷h`⬇️のN●そzM░ゆ^⬇️█⁷♥█⬇️さ░+q⁷⁷**⁷⁷KPQVVvQ⬇️めVvVPQvQPPLG░@^✽k░る✽r░█h⬇️█omz0░▒、o`j⁷h⁴⁴⬇️f○oo~⬇️²|✽█⬇️◝⁷ha⬇️む⬇️やᵇo•ᶜ░B⬇️●o`i⁷⁷<\r-웃|⬇️◜,░█Wl`░¹RVS`p✽\n`k⬇️ヘo✽フzo♥●░ロ⬇️るha○zo]0y0✽ケai⁷tBFfCfFC✽:u✽█░ケg░@mᶜ0•ᶜo、⬇️るᵇ+++oO░レ░ヨ●|░て░つ⁷⁷Co○~⬇️テ⁴o~⬇️イ⬇️⁶⬇️hg`⬇️ス⬇️ンM^⬇️テ⬇️◜✽on⬇️█●▤0y^░ナ░キ♥MfFfFF\0░サ⌂█]0z0░せ^░○、z✽レ░ヨ⌂²✽█\0\0~░マzx✽⁶z⁴{o`Yh░█⬇️ワ]N●‖mzooᵇᶜ`IX⬇️スzon^z░3○✽キ⌂P\0\0웃サ░(░そ⬇️し⬇️を•、•、z⬇️█\0\0\0。⬇️⁴dRVSe⬇️ᶜq\0<**⬇️⁘\0o♥{●▒]⬇️ヘg⬇️█⬇️s░y●█⬇️○+、`IH⬇️スM^oo░2xy●◜░レ✽⁷✽█q\0░サG`oᶜ✽そ░○♥]✽█<-\0\0h~~○j░x\0\0░█q\0⬅️%✽ヤ`Y✽ヘ⬇️ルo⬇️y░²mozᵇ、o⬇️█aᶜ☉⧗✽R♥ヲ░わ⬇️ュ*✽\r░█W`o+✽そzzᵇᵇ⬇️⌂░F|✽そ⬇️えg○{oj⁷░と✽ᶠ\0\0⬇️て⬇️オ⬅️█i⬇️ら{zzMn⬇️る░@z]Nᵇ、{⬇️█G`+ᶜmzy●コy♥█,*\r\r\r-●\\*⬇️░░c`o、●そᵇ、••ᶜoz⁴⁴⁴`Z░゜=q⁷h○~~R░█⬇️-⬇️て⁷\0~•⬇️c{N●◝⁴zo`Z⬇️█♥ョᵇ+++ᶜ⬇️ᵇ•⬇️イ`IW`+、mo~⬇️セ○~○~░キ⬇️|=░セ<░ \r\r*⬇️😐w⁷⁷X⬇️@░>oᵇ、✽ュ⬇️○⁴`I●か⁷tBqCu✽█w✽◆¹░ョ⬇️て➡️,lI[@✽¹AqDAffFFDEBD░□\\q⁷CBFFf⬇️²A⬇️⁶░オ∧²♥@@@⌂<⬇️>あ,😐'░h" + compressed_map_upper='⁷⁷*∧¹⁷⁷SvvQ⁷q⁷VvVTPPQ⬇️ᶜ⁷⬇️⁸⬇️²UQVVv⬇️⁷░\rLK☉•●‖♥□⬇️!♥1L♪`\0K░<░:⬇️>░A⬇️゜⁷♥¹\0\0。✽ᵇ●⁶⁷d``p``RVS░⁷●ᶜv░□✽ᵉ☉⁴kIGl♥•😐□⌂1kI✽`q\0⬇️^⬇️⁴*W✽。p●Ep☉゜♥█<\r\r*-⬇️➡️q\0⬇️ワ_⁴oo~ooo○o•+⁴⁴⬇️\r✽ᵉ♥³m░ᵇ⁴o{`IG`nN●▮●⁴░□⁴⬇️•✽!⁴o`Y⬇️}⬇️タ\0\0,=░⁶⁷C~⁴x○⬇️Zm~o○~o`J⬇️◝q☉█░◝‖◀=⬇️⬅️░ひ~xzzyzxyzz•、⬇️♥⬇️⁸░²⬇️‖⬇️ m⬇️\r⬇️_⁴`YG`o{●▮░⁘{✽█o{⁴⁴░.░█i⁷⁷w✽█。\0q░█⁷○y⬇️RxM^zz~{oaI*⁷w\r\r-♥█⬇️⌂%&░⬅️░★⬇️rx░ラ●x░q⬇️●o●▒✽█░モiW`░!]⬇️ュ⬇️R⬇️ˇ✽█⬇️か●9o`Z\r\r=⬇️タq⁷**░●⁷Sox⬇️ケxmxxyy○~`⬇️█⬇️◜。♥█⁷⁷*\r=*q✽█}~○oo~⬇️れ✽w░³⬇️\r⁴oᵇᶜ✽pN✽●⬇️_h✽█✽しoMnnn♥█o⁴░‖⁴⬇️◆░コ░ン●█Xa~♥ょzᵇyz○`J🅾️█,=░ッ⁷⬇️に⁷t`░¹p``BfFFfC⬇️ ●▮k✽[✽A]n`JhaoMN░ヨzMn^zz⁴✽█]⬇️ュ0⬇️~Mn`rPPQVTPL⬇️ハ\0\0q\0G`○y░に⬇️け•zxoaI⬇️█q⌂█✽웃⬇️ぬ░ˇ░wD@@A⬇️く⬇️れDAFffDE@ca⬇️ソ░ユoz•ᶜ⬇️トG`n^mo]n./^░◀●█⬇️◝0z0no^ok●ほkI✽█\0⬇️█ox░>0⬇️ろyzo`JKP⬇️くvQ⬇️⁶け³v⬇️ゃs⬇️^●█░¹`Y░n░ヤ>?●∧♥█nn0⁴⁴oz⁴⬇️⁵✽(I⁷**░ニHa~⬇️wz0y0Nzyx░らl●けた⁶l✽█⬇️~░♥`j░モ░ンm░∧Mnn`IW✽□⁴m⬇️⁴░し⬇️▒░∧,●█⬇️にx░ゆ0y]⬇️さnaJ⬇️.om⬇️Mᵇ+●ち░2♥ふ░「•、☉◀⌂⁸⬇️█⬇️◝mz0zM♥█⬇️ユ^░◝⬇️t`Ih░★⁴⬇️U░そ⬇️テk⬇️♥`I=⁷⁷q\0\0\0G`○x░ぬ]N~zy⬇️らY⬇️.{⬇️<⬇️█、⁴⬇️セ⁴⬇️⁵ᵇᶜ✽bzM░「▤³o]0z0^`Z✽█M^✽▤⬇️1░█a✽けm✽す`bc`░█rQ⁷⬇️¹TL⬇️にy░ゆoox✽█i♥.⬇️○✽サ⬇️ャᵇ+ᶜ░ノ✽◝✽ョᵇ♥▮☉²●█zz0zz`I⬇️█░ヲ✽の⬇️s⬇️マ✽□Mn⬇️コ⁴o●ᶠzok``R⁷⁷SkIH`y░█●かyx`Zh░らzz•●○l`✽¹k░█♥ᶜ…▮k⬇️ホo░そn^~~`J░█☉ち░ね●★웃も♥█⬇️ス~o○o`YG`xxyz⬇️³mx⬇️⁶yaJX`o{N●よ⁴⬇️ゆb@⬇️¹c░⬆️♥ᶜ…▮ck⬇️え웃うlIG⬇️タ⬅️そ░ヨ😐□⬇️!✽ニ⁴⁴zx{o`iH`ox⁴⬇️の~m○~oo○`J⬇️■웃と⁴⬇️ゆKPPPs●█r⬇️ᵇL…▮[░えᵇab@AFD@E@\\[░ねFFDEBD░ま⬇️ヨ[☉ᶠ✽よ\\●█✽ヌ○`Z⬇️にp♥ち░ろ◆ら░オ✽▶░ユ░セk➡️▮Ksaoz•░うQVT⬇️き\0\0\0⁷●¹T░ま●へ😐³s`n⬇️¹Nzz⁴░ト✽に@Ec`⬇️ムb░か░らFf♥と✽に⁴⬇️)░ユmo⁴⁴`I⌂▮⬇️◀⬇️テGl⬇️え✽う✽んI⁷w\0⁷dRVVSS⬇️スlo웃¥⬅️ケ⬇️イzm⬇️◝⬇️█rQVVv✽ˇ░\r⬇️ちLK⌂にQvQ✽¥⬇️た⬇️2z]n░█H`✽\r░■⁴{░█⬇️ノz⬇️⌂o⁴++░き\0。\0⁷C░▮웃⁴•、░゛░わ░\'0●>░█⌂ほ웃ちk⬇️オ➡️に⬇️ヘ✽soᵇᵇ⬇️█a웃█⬇️★`IW웃…•++ᶜ`Y⁷⬇️█⁷웃"☉つ✽█✽ひ♪█✽ま☉あ░わa✽オ✽ぬom☉を☉ユᵇ++✽ユ{N●ろ⁴zo`Ih`ᶜ☉…•+⬇️▮⁷<-☉█⁴⬇️"…⌂0●⁙⁴]♥D♪ち{oaJ░`웃こ⌂▥░つ✽█⬇️ユ웃あ⁴░█a+ᶜ☉そ•、⬇️█q。⁷S…セᵇ☉#o0⬇️ウ✽レ웃テ♪+`⬇️█n⬇️¹░ャ^⬇️█l``k`⬅️¹lIX░▮p◆▮░#k░Z⬇️█\0。⁷t░ 웃$웃 ⁴`bc`░█⬇️V★²B\0\0C⬇️`H░"0⬇️◝░⁵o`b@⬇️¹FfD░⁷░ᵇ\\[@@⌂ᵉ●▮✽、c`ᶜ░█Y░█⁷fF⬅️#♥ a⁴⬇️゛G✽█✽VFFDEB⌂JA⁷⁷q\0D@\\░█om░█z⬇️ `IKP⬇️¹QvQ░⁷⬅️⁴L●「⁷q⁷VvVT⬇️$s`⁴ᶜ⬇️ぬj⁷⁷⬇️█⬇️²<\r\r\r-\0⌂¹s`ooj\0G`⁴░サI\0⁷**☉。q⬅️&⁷*Ha⬇️█]noozMnnn`IGl`⧗¹k♥「RVS`p●!l、░ソi⁷\0●█░✽。dRvSRvVVvS⬇️ ⁴o`eW◆█웃|♥✽⁷*G`⬇️ワ⬇️ュon^⬇️웃⬇️█`⁴⁴⁴o♥¹웃ᵇ░「░ᶠoom⌂▶zzᵇ⬇️█q⬅️█C⬇️<~o○o~om○oz⁴⁴B⁷C░\\░█⁷。\0\0KPQ⬇️うPPQvQP♥¹LW●█mz⬇️ウ⬇️オrs⬇️█⬇️ᶜzz0░a●²♥█zᵇᶜ●)웃³•`I⁷\0✽█q✽█hozyxzz⁴xzmy⬇️Q⁴⁷,\r♥█q░█Gl`p`✽¹●⁷p`kIha⬇️◜0n^⬇️q⬇️ちk``l웃w웃jᵇ♥█z•ᶜo⬇️)●❎Mn⬇️¹`I\r\r=☉◝⁷。C○z{⬇️○⬇️░]nn{zo⬇️、oz⁴░█⁷w*⬇️█⬇️チ⁴omo✽¹✽⁷`Yh⬇️スM^░⁙✽ホ♥ ⌂ッ⬇️らᵇ+♥█o0•0oᶜ♥◝m░0`I웃}░█g~xmz⁴⬇️◝z⬇️◝xy~R⁷S⬇️テ░█,*☉█✽9⬇️よ⬇️ラ⬇️█IX`o{●□◆█0░:░⬆️⬇️█、░█░ユ{⬇️ᶜ❎█q⁷⁷ 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + + -- check mission status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select", true) + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(28) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,999,999,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) + sfx(30) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + local decel = self.subclass == "player" and 5.5 or 1 + self.vx -= dx * decel + self.vy -= dy * decel + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 20 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx, + sy, + dx * 5, + dy * 5, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(10) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self:reset_plasma_cannon() + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx, dy = target.x - self.x, target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + local points = { + {x, y}, + {x + self.width - 1, y}, + {x, y + self.height - 1}, + {x + self.width - 1, y + self.height - 1} + } + + for point in all(points) do + if check_tile_flag(unpack(point)) then + return true + end + end + + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 20 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 20), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + printh(self.interactive) + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + if ending_sequence_timer == 1000 then + music(7) + elseif ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + ending_sequence_timer -= 1 + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd + +__label__ +55555555555555556666665000000000000555000000000000000000000000000000000000000000000000065555555555555550000000000000000000000000 +55555555555555556555555000000000000555000000000000000000000000000000000000000000000000065550000000055550000000000000000000000000 +55000000000000000000000000000000000000000000000000000000000000000000000000000000000077067770557577007770000000000000000000000000 +550bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb00007067070575057007070000000000000000000000000 +550bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb00007067070570557057070000000000000000000000000 +550bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb00007067070570557057070000000000000000000000000 +55000000000000000000000000000000000000000000000000000000000000000000000000000000000077767770750577757770000000000000000000000000 +55000000000000000000000000000000000000000000000000000000000000000000000000000000000000005000505550000000000000000000000000000000 +550cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc00000065055555555550520000000000000000000000000 +55000000000000000000000000000000000000000000000000000000000000000000000000000000000000065005555555500520000000000000000000000000 +55555552222220223633362000000000000555000000000000000000000000000000000000000000000000065500555555005520000000000000000000000000 +55555552222002223333332000000000000555000000000000000000000000000000000000000000000000065550000000055520000000000000000000000000 +55777550222002023363332000777000000555000000000000700077007770777000700000000000000000065555555555555520000000000000000000000000 +55707077727770702377720000707070707755077077700000770007007070707007700000000000000000055555555555555520000000000000000000000000 +55775057007700700277000000770070707075700007000000777007007070707077700000000000000000055222222222222220000000000000000000000000 +55707557007000700070000000707070707750007007000000770007007070707007700000000000000000000000000000000000000000000000000000000000 +55707077707000077007700000777007707075770007000000700077707770777000700000000000022220000000000000000000000000000000000000000000 +55505050000000220000002000000000000050000000000000000000000000000000000001111110222222000000000000000000000000000000000000000000 +55555550200002002000020000000000000555000000000000000000000000000000000001212120122225000000000000000000000000000000000000000000 +55577550022220000222200000000000000555770077707770777000000000000000000001100000111555000000000000000000000000000000000000000000 +55750077007770770077707770077007000555070070707070707000000000000000000001201110111555000000000000000000000000000000000000000000 +55705570707700707007000700700000000555070070707070707000000000000000000001101210111555000000000000000000000000000000000000000000 +55705577007000707007005700007007000555070070707070707000000000000000000001201100011550000000000000000000000000000000000000000000 +55577570755775775077755700770000000555777077707770777000000000000000000001101200000000000000000000000000000000000000000000000000 +50000050500000500000005000000000000555000000000000000000000000000000000001201100000000000000000000000000000000000000000000000000 +00000055000000550000005000000000000555000000000000000000000000000000000001101200000000000000000000000000000000000000000000000000 +00000055000000550000005000000000000555000000000000000000000000000000000001201100000000000000000000000000000000000000000000000000 +00000055000000550000005000000000000566000000000000000000000000000000000001101200000000000000000000000000000000000000000000000000 +00000055000000550000005000000000000566000000000000000000000000000000000001201100000000000000000000000000000000000000000000000000 +50000555500005555000055000000000005666000000000000000000000000000000000001101200000000000000000000000000000000000000000000000000 +55555555555555555555555000000000005666000000000000000000000000000000000001201100000000000000000000000000000000000000000000000000 +55555555555555555555555000000000555666000000000000000000000000000000000001101200000000000000000000000000000000000000000000000000 +55555555555555556666665000000000555666000000000000000000000000000000000001201100000000000000000000000000000000000000000000000000 +55555555555555556555555000000055555666000000000000000000000000000000000001101200000000000000000000000000000000000000000000000000 +55555555555555556555552000000055555666000000000000000000000000000000000001201100000000000000000000000000000000000000000000000000 +55555555555555556555552000000055555666000000000000000000000000000000000001101200000000000000000000000000000000000000000000000000 +55555555555555556555552000005555555666000000000000000000000000000000000001201100000000000000000000000000000000000100000000000000 +55555555555555556555552000005555555666000000000000000000000000000000000001101200000000000000000000000000000000000100000000000000 +55555555555555555522222000005555555666000000000000000000000000000000000001201100000000000000000000000000000000000100000000000000 +55555555555555555555555000005555555666000000000000000000000000000000000001101200000000000000000000000000000000000110000000000000 +55555555555555556666665000555555555666000000000000000000000000000000000001201100000000000000000000000000000000000120000000000000 +55555555555555556555555000555555555666000000000000000000000000000000000001101200000000000000000000000000000000000110000000000000 +55555555555555556555552000555555555666000000000000000000000000000000000001201100000000000000000000000000000000000120000000000000 +55555555555555556555552000555555555666000000000000000000000000000000000001101200000000000000000000000000000000000110120000000000 +55555555555555556555552055555555555666000000000000000000000000000000000001201100000000000000000000000000000000000120110000000000 +55555555555555556555552055555555555666000000000000000000000000000000000001101200000000000000000000000000000000000110120000000000 +55555555555555555522222055555555555666000000000000000000000000000000000001201100000000000000000000000000000000000120110000000000 +55555555555555555555555055555555555666000000000000000000000000000000000001101200000000000000000000000000000000000110120000000000 +55555555555555556666665055555555555666000000000000000000000000000000000001201100000000000000000000000000000000000120110000000000 +55555555555555556555555055555555555666000000000000000000000000000000000001101211111111000000000000000000000000000110121200000000 +5555555555555555655555d555555555555666000000000000000000000000000000000001201111212121000000000000000000000000000120111100000000 +5555555555555555655555d555555555555666000000000000000000000000000000000001100000000011000000000000000000000000000110000000000000 +5555555555555555655555d555555555555666000000000000000000000000000000000001212121111021000000000000000000000000000121212100000000 +5555555555555555655555d555555555555666000000000000000000000000000000000001111112121011000000000000000000000000000111111100000000 +5555555555555555552222d555555555555666000000000000000000000000000000000000000000011021000000000000000000000000000000000000000000 +00000000000000000000005155555555555666000000000000000000000000000000000000000000011012000000000000000000000000000000000000000000 +00000000000000000000005515555555555666000000000000000000000000000000000000000000012011000000000000000000000000000000000000000000 +00000000000000000000005551555555555666000000000000000000000000000000000000000000011012000000000000000000000000000000000000000000 +00000000000000000000005555155555555666000000000000000000000000000000000000000000012011000000000000000000000000000000000000000000 +00000000000000000000005555515555555666000000000000000000000000000000000000000000011012000000000000000000000000000000000000000000 +00000000000000000000005555551555555666000000000000000000000000000000000000000000012011000000000000000000000000000000000000000000 +00000000000000000000001555555155555666000000000000000000000000000000000000000000011012000000000000000000000000000000000000000000 +00000000000000000000001555555515555666000000000000000000000000000000000000000000012011000000000000000000000000000000000000000000 +00000000000000000000000115555551555666000000000000000000000000000000000000000000011012000000000000000000000000000000000000000000 +00000000000000000000000011555555155666000000000000000000000000000000000057770000012011000000000000000000000000000000000000000000 +00000000000000000000000001555555515666000000000000000000000000000000000577cc7000011012000000000000000000000000000000000000000000 +00000000000000000000000001155555551666000000000000000000000000000000000777cc7000012011000000000000000000000000000000000000000000 +00000000000000000000000000116666666666000000000000000000000000000000000577777000011012000000000000000000000000000000000000000000 +00000000000000000000000000011666666666000000000000000000000000000000000577077000012011000000000000000000000000000000000000000000 +00000000000000000000000000001166666666000000000000000000000000000000000011010000011012000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000111111000012011000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000011110000011012000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000012011000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000011012121212121212121212121212121212121200000000 +00000000000000000000000000000000000000000000000000000000003000000000000000000000012011111111111111111111111111111111111100000000 +00000000000000000000000000000000000000000000000000000000303000000000000000000000011000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000303030000000000000000000012121212121212121212121212121212121212100000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000011111111111111111111111111111111111111100000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030300000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000030303000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555555556666666666666666660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555555556666666666666666660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555555555566666666666666660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000055555555555516660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000055555555555156660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000555555551556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000555555515556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000005555155556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000005551555556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000005515555556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000055555556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000055555556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000555556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000005556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000005556660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555555555550000000000056660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +66666655666666556666665566666650000000000056660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +65555555655555556555555565555550000000000005660000000000000000000000000000000000000000000000000000000000000000000000000000000000 +65555525655555256555552565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +65555525655555256555552565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +65555525655555256555552565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +65555525655555256555552565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55222225552222255522222555222220000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555555555550000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555566666650000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555565555550000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +55555555555555555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +50222555555555555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222255555555555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +22222255555555555555555555222220000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222255555555555555555555555550000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02220205555555555555555566666650000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000055555555555555555565555550000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +70000000000005555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +07000000000005555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00700000000005555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +07000000000005555555555565555520000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +70000000000005555555555555222220000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000005555555555555555550000000000005550000000000000000000000000000000000000000000000000000000000000000000000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +74b6068b01c626368810b6f6a7a78303840440b70694740640c1c185118503f603e503e6e5f60696708601178508b4050515830b65676545058501370684468963c68354d6864586050695b504040464664483068503c5881036843aa7f6f6848340f68480897c8684b7839cb006a5877a868775c6060625653506078ad184c6 +8b018380f6d683d6d4e485dc06a683b384ab15671586b2880685ba88ed409080d4e6e603f6a7b0b2069485ffa28503838934886c40860303c0c0b2c08eb8d6f6d5e6e2f2e5d5f6e6830106967483b3950284ba8981908083c3847f888070516183ffc2d0d0d087b54086054003b1b2b1c185c8b08787d4d583f9e3f384408327 +838006f6b1b2b2c1f6d686ba870786c7854a834bf69480a7a7b1404006958680526283ffd17017008ab5868303837e845a83acc08362d4e6e58479843f868084d88480865287598c844040849985808710841184aeb0b28380a585ffa28303d0d0d37070358a38863b03f6a787f6c6068701b6e5855f838585ffa7f606958483 +d8b1c188f9d4e585f686320788370606c69475b6b6f6f789498a1885ff880585b69021b6f6a797f6062604850136836ca7a784648466d4e6e6e606968416838a8375d5e487ff8481843266448635863bc5176416f6834914646454040464841d5466044404c57070008a01b583686464445424883c3606e797a7f706957070d1 +83a9748580866a866c06a5841485f4837b8605839cf60694b40505550583041570706567701770008606f6e70694856d170000831283541770a2a28b7f0083358502c47000847000007416e783de24842c707017758580866a8780967506e6830183fdd5f68301e5849b858074c60686012535060735567077701640e7062715 +778479870384d483a81567156567654583ae378635b694700077a284817516f7a7a7e72583fca2831076068aea84f1f68380748394f6858c867b888084832cf6d6f6e7f7f6f640404006a57046c640f6b60606072567839156848500845487a80786aec6f640f6b783be00851c70001786e78797f7a6701785808584548d0306 +a58880f6869684a085807406e7b785aad4e6830106a67034853cf7f6e78490f48a808854d6865ca74040858000d1001783f74724000034570083afd183acb688a887af06c683688780d5e6e2f2a7d4f6846e8370948406e6e5a78454e5b0c0c0f616941785f68785a283a797e783ff17700084547583c085ba86c083c58680a2 +a2838470838885038680b5049101c58580d4d5e683f9e3f3e6e5854d40400694750640a783998304b1b2c1f706a47084408797857d840684ff7000c2d37070860683b2e486a8a7d484bee5838070878083a4842b177070a2a27070b405156565671583bb656765051567150505c4748440e5856b84c285728480868380f6d6a7 +038481c1f606a6708640408366f7f6f6e78302c7858083ff70861683ba83bdb0f6b1c084428386f606967070c3d0d2897c83fec2848075c60684012565350607850a06b683e8f685e7a7f6878684f683c28616f7a7f6d503970385d41696704724646634666434853a57858084d4768440d6c003b1c0f6c183c2b0b2b2b2f6f4 +84f584f1867c84ac84ab707034f6f7e783de40f6e783cd83068368760683d883f9d4e583de83fe856fe6838086980397e584e084d2874d66646664640084d68a80d503a70384a7e5847fc1a785f584f18a0285800000e784eaa7878506a740b7f6069586848083f7d5e48615d6a7f6f6b0c006948583d8a7f6e6e5a78433f785 +d28a50000089d6842884a883a583c6b1c1b1c1a78380000000d183044625653556830c1700c3a2a2831400f6877b8681d583e8768380837384798680837fb2c106948483d8d4e5f6f68432879786fe84f585078580170084d67406f6c085a8847f875d8580c3d2000086e7e7f7a684780000848017008b2585ef069585e883f4 +f683798402d6f6a7b0c1f6838016c08893855287f884c583fca2850d84807506f6b285a8a7a7b0b0838a8446c785a8839d76f7b7f6a67084ad850f000083ac83d08b809683c0b7a7a7d4e683c28440a7d5e4b0c1b783807406b2c0d6a79786d5978780c2a2d0d0d0d2865ca28384846306f6c186a8b0c1b1b1c0f6a740404006 +a5841fd3177086f7e7e7258480832d83ac7000e7b18363b7e486ff40a7f606a5838087fdb0b2b2b2c0830bb183cd06947506b2c1d6f6e783d9f7e7f7e784d2837cd384d9c38409d0d0a2838c777070858340843ef6b0c185fc837f400694869f704724173457858077853c35f7f6e7f7846e8458e7f64084e8b6068501668607 +8706c6947047831807851c83065785ff7070178f08707088408f3e8f0184fd83ac912cc694b50485011417441466666464445424448412c517703424646466830214830684d09602874004048a3c833e9a2c8c278468 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + diff --git a/versions/v0.1.0.p8 b/versions/v0.1.0.p8 new file mode 100644 index 0000000..36ff4e7 --- /dev/null +++ b/versions/v0.1.0.p8 @@ -0,0 +1,2428 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + +--[[ +CORTEX PROTOCOL + by Emanuele Bonura + itch: https://izzy88izzy.itch.io/ + github: https://github.com/EBonura/CortexProtocol + instagram: https://www.instagram.com/izzy88izzy/ + minified with: https://thisismypassport.github.io/shrinko8/ + +How to Play: +* Use arrow keys to move +* Press X to use your selected ability +* Press O to open the ability menu and switch between abilities +* Interact with terminals using X when prompted + +Main Objective: +* Activate all terminals and reach the extraction point + +Optional Objectives: +* Collect data fragments to restore health and earn credits +* Eliminate all enemy units +]] + +-- MAP COMPRESSION +---------------------- +-- function compress(data) +-- local result = "" +-- local i = 1 +-- local data_len = #data + +-- while i <= data_len do +-- local best_len, best_dist = 0, 0 + +-- for j = max(1, i - 255), i - 1 do +-- local k = 0 +-- while i + k <= data_len and sub(data, j + k, j + k) == sub(data, i + k, i + k) do +-- k += 1 +-- end + +-- if k > best_len then +-- best_len, best_dist = k, i - j +-- end +-- end + +-- if best_len > 2 then +-- result = result..chr(128 + best_len)..chr(best_dist) +-- i += best_len +-- else +-- result = result..sub(data, i, i) +-- i += 1 +-- end +-- end + +-- return result +-- end + +-- function save_compressed_map(start_address, end_address, filename) +-- -- Gather map data +-- local map_data = "" +-- for addr = start_address, end_address do +-- map_data = map_data..chr(peek(addr)) +-- end + +-- -- Compress the data +-- local compressed = compress(map_data) + +-- -- Convert compressed data to Lua string +-- local lua_string = "" +-- for x in all(compressed) do +-- lua_string = lua_string.."\\"..ord(x) +-- end + +-- -- Save to file +-- printh(lua_string, filename, true) +-- printh("Compressed data saved to "..filename) +-- end + +function decompress_to_memory(data, mem_index) + local i = 1 + while i <= #data do + local byte = ord(data[i]) + if byte >= 128 then + local source = mem_index - ord(data[i + 1]) + for j = 1, byte - 128 do + poke(mem_index, peek(source + j - 1)) + mem_index += 1 + end + i += 2 + else + poke(mem_index, byte) + mem_index += 1 + i += 1 + end + end +end + +function load_compressed_map() + if current_mission <= 2 then + compressed_map_lower="Gk`⬅️¹lbc☉▮kozz⬇️³░⁴⁴{`IG`⁴、、✽■✽³o0^0n^o`i⁷●¹q✽⁸KPPQ⬇️ᵇVvVTP✽¹s`░F웃cl⬇️Tm●E●⁵`Y[@@@FfD⬇️⁶✽³\\☉▮c░:zoo░⬇️⁴o░█웃|●░{⬇️うᵇ`Z♥z●♥Wl``RVS`p⌂カ░を⬅️¹⬇️█om⬇️サMN✽チ`j⬇️は░つQvQ●の☉⁶✽む☉メ⁴…█Mnn0ozᵇ+`I✽◝*✽³⬇️웃C☉l⁴●³0ᶜᶜ+ᶜ🅾️まmo]n./^]on⬇️¹`iG⬇️はˇ²░む웃▒…█⬇️れ░○☉█⁷‖◀⬇️◝,\r\r\r♥ふ⁴●⁵⁴0•+•、✽っᵇ♥♥M]⬇️ン>?░@⬇️'⬇️█`o•++、om●む♥⁷●ん✽J⬇️Ko⬆️█zz•⁴⁴`Y●█%&⬇️◝。⁷q\0⌂ふ●⬇️0⬇️~░Z⬇️てᶜ⬇️bMn^░y░?●█░ス░█●R♥Y😐░⁴⁴░▥✽█♥▮░■░なᵇ+⬇️█Z✽◝*⬇️³\r\r=⁷⁷S⌂8●;0ozxol`♥¹k^✽_⬇️✽✽◝zo`YH⬇️ス•、☉ンM^✽ロ●2p☉7``lIWkko○웃I⌂「✽◝☉⁵Xk…!kozyo`b@✽¹c⬇️lzz░d░fMnnn`iHa⬇️⌂⬇️u]N♥◝░▒░2fD●5●;\\qFao⬇️IAFFE@@F░。Ef@D@\\⁷⁷\0⌂¹[⬇️hFFDEB☉?n^✽M⁴⁴`IW`⁴z⬇️▥⬇️⁴•+、○`J⁷H⁴xy✽}░⁶░◝⁷\0,=⁷⁷h`⬇️のN●そzM░ゆ^⬇️█⁷♥█⬇️さ░+q⁷⁷**⁷⁷KPQVVvQ⬇️めVvVPQvQPPLG░@^✽k░る✽r░█h⬇️█omz0░▒、o`j⁷h⁴⁴⬇️f○oo~⬇️²|✽█⬇️◝⁷ha⬇️む⬇️やᵇo•ᶜ░B⬇️●o`i⁷⁷<\r-웃|⬇️◜,░█Wl`░¹RVS`p✽\n`k⬇️ヘo✽フzo♥●░ロ⬇️るha○zo]0y0✽ケai⁷tBFfCfFC✽:u✽█░ケg░@mᶜ0•ᶜo、⬇️るᵇ+++oO░レ░ヨ●|░て░つ⁷⁷Co○~⬇️テ⁴o~⬇️イ⬇️⁶⬇️hg`⬇️ス⬇️ンM^⬇️テ⬇️◜✽on⬇️█●▤0y^░ナ░キ♥MfFfFF\0░サ⌂█]0z0░せ^░○、z✽レ░ヨ⌂²✽█\0\0~░マzx✽⁶z⁴{o`Yh░█⬇️ワ]N●‖mzooᵇᶜ`IX⬇️スzon^z░3○✽キ⌂P\0\0웃サ░(░そ⬇️し⬇️を•、•、z⬇️█\0\0\0。⬇️⁴dRVSe⬇️ᶜq\0<**⬇️⁘\0o♥{●▒]⬇️ヘg⬇️█⬇️s░y●█⬇️○+、`IH⬇️スM^oo░2xy●◜░レ✽⁷✽█q\0░サG`oᶜ✽そ░○♥]✽█<-\0\0h~~○j░x\0\0░█q\0⬅️%✽ヤ`Y✽ヘ⬇️ルo⬇️y░²mozᵇ、o⬇️█aᶜ☉⧗✽R♥ヲ░わ⬇️ュ*✽\r░█W`o+✽そzzᵇᵇ⬇️⌂░F|✽そ⬇️えg○{oj⁷░と✽ᶠ\0\0⬇️て⬇️オ⬅️█i⬇️ら{zzMn⬇️る░@z]Nᵇ、{⬇️█G`+ᶜmzy●コy♥█,*\r\r\r-●\\*⬇️░░c`o、●そᵇ、••ᶜoz⁴⁴⁴`Z░゜=q⁷h○~~R░█⬇️-⬇️て⁷\0~•⬇️c{N●◝⁴zo`Z⬇️█♥ョᵇ+++ᶜ⬇️ᵇ•⬇️イ`IW`+、mo~⬇️セ○~○~░キ⬇️|=░セ<░ \r\r*⬇️😐w⁷⁷X⬇️@░>oᵇ、✽ュ⬇️○⁴`I●か⁷tBqCu✽█w✽◆¹░ョ⬇️て➡️,lI[@✽¹AqDAffFFDEBD░□\\q⁷CBFFf⬇️²A⬇️⁶░オ∧²♥@@@⌂<⬇️>あ,😐'░h" + compressed_map_upper='⁷⁷*∧¹⁷⁷SvvQ⁷q⁷VvVTPPQ⬇️ᶜ⁷⬇️⁸⬇️²UQVVv⬇️⁷░\rLK☉•●‖♥□⬇️!♥1L♪`\0K░<░:⬇️>░A⬇️゜⁷♥¹\0\0。✽ᵇ●⁶⁷d``p``RVS░⁷●ᶜv░□✽ᵉ☉⁴kIGl♥•😐□⌂1kI✽`q\0⬇️^⬇️⁴*W✽。p●Ep☉゜♥█<\r\r*-⬇️➡️q\0⬇️ワ_⁴oo~ooo○o•+⁴⁴⬇️\r✽ᵉ♥³m░ᵇ⁴o{`IG`nN●▮●⁴░□⁴⬇️•✽!⁴o`Y⬇️}⬇️タ\0\0,=░⁶⁷C~⁴x○⬇️Zm~o○~o`J⬇️◝q☉█░◝‖◀=⬇️⬅️░ひ~xzzyzxyzz•、⬇️♥⬇️⁸░²⬇️‖⬇️ m⬇️\r⬇️_⁴`YG`o{●▮░⁘{✽█o{⁴⁴░.░█i⁷⁷w✽█。\0q░█⁷○y⬇️RxM^zz~{oaI*⁷w\r\r-♥█⬇️⌂%&░⬅️░★⬇️rx░ラ●x░q⬇️●o●▒✽█░モiW`░!]⬇️ュ⬇️R⬇️ˇ✽█⬇️か●9o`Z\r\r=⬇️タq⁷**░●⁷Sox⬇️ケxmxxyy○~`⬇️█⬇️◜。♥█⁷⁷*\r=*q✽█}~○oo~⬇️れ✽w░³⬇️\r⁴oᵇᶜ✽pN✽●⬇️_h✽█✽しoMnnn♥█o⁴░‖⁴⬇️◆░コ░ン●█Xa~♥ょzᵇyz○`J🅾️█,=░ッ⁷⬇️に⁷t`░¹p``BfFFfC⬇️ ●▮k✽[✽A]n`JhaoMN░ヨzMn^zz⁴✽█]⬇️ュ0⬇️~Mn`rPPQVTPL⬇️ハ\0\0q\0G`○y░に⬇️け•zxoaI⬇️█q⌂█✽웃⬇️ぬ░ˇ░wD@@A⬇️く⬇️れDAFffDE@ca⬇️ソ░ユoz•ᶜ⬇️トG`n^mo]n./^░◀●█⬇️◝0z0no^ok●ほkI✽█\0⬇️█ox░>0⬇️ろyzo`JKP⬇️くvQ⬇️⁶け³v⬇️ゃs⬇️^●█░¹`Y░n░ヤ>?●∧♥█nn0⁴⁴oz⁴⬇️⁵✽(I⁷**░ニHa~⬇️wz0y0Nzyx░らl●けた⁶l✽█⬇️~░♥`j░モ░ンm░∧Mnn`IW✽□⁴m⬇️⁴░し⬇️▒░∧,●█⬇️にx░ゆ0y]⬇️さnaJ⬇️.om⬇️Mᵇ+●ち░2♥ふ░「•、☉◀⌂⁸⬇️█⬇️◝mz0zM♥█⬇️ユ^░◝⬇️t`Ih░★⁴⬇️U░そ⬇️テk⬇️♥`I=⁷⁷q\0\0\0G`○x░ぬ]N~zy⬇️らY⬇️.{⬇️<⬇️█、⁴⬇️セ⁴⬇️⁵ᵇᶜ✽bzM░「▤³o]0z0^`Z✽█M^✽▤⬇️1░█a✽けm✽す`bc`░█rQ⁷⬇️¹TL⬇️にy░ゆoox✽█i♥.⬇️○✽サ⬇️ャᵇ+ᶜ░ノ✽◝✽ョᵇ♥▮☉²●█zz0zz`I⬇️█░ヲ✽の⬇️s⬇️マ✽□Mn⬇️コ⁴o●ᶠzok``R⁷⁷SkIH`y░█●かyx`Zh░らzz•●○l`✽¹k░█♥ᶜ…▮k⬇️ホo░そn^~~`J░█☉ち░ね●★웃も♥█⬇️ス~o○o`YG`xxyz⬇️³mx⬇️⁶yaJX`o{N●よ⁴⬇️ゆb@⬇️¹c░⬆️♥ᶜ…▮ck⬇️え웃うlIG⬇️タ⬅️そ░ヨ😐□⬇️!✽ニ⁴⁴zx{o`iH`ox⁴⬇️の~m○~oo○`J⬇️■웃と⁴⬇️ゆKPPPs●█r⬇️ᵇL…▮[░えᵇab@AFD@E@\\[░ねFFDEBD░ま⬇️ヨ[☉ᶠ✽よ\\●█✽ヌ○`Z⬇️にp♥ち░ろ◆ら░オ✽▶░ユ░セk➡️▮Ksaoz•░うQVT⬇️き\0\0\0⁷●¹T░ま●へ😐³s`n⬇️¹Nzz⁴░ト✽に@Ec`⬇️ムb░か░らFf♥と✽に⁴⬇️)░ユmo⁴⁴`I⌂▮⬇️◀⬇️テGl⬇️え✽う✽んI⁷w\0⁷dRVVSS⬇️スlo웃¥⬅️ケ⬇️イzm⬇️◝⬇️█rQVVv✽ˇ░\r⬇️ちLK⌂にQvQ✽¥⬇️た⬇️2z]n░█H`✽\r░■⁴{░█⬇️ノz⬇️⌂o⁴++░き\0。\0⁷C░▮웃⁴•、░゛░わ░\'0●>░█⌂ほ웃ちk⬇️オ➡️に⬇️ヘ✽soᵇᵇ⬇️█a웃█⬇️★`IW웃…•++ᶜ`Y⁷⬇️█⁷웃"☉つ✽█✽ひ♪█✽ま☉あ░わa✽オ✽ぬom☉を☉ユᵇ++✽ユ{N●ろ⁴zo`Ih`ᶜ☉…•+⬇️▮⁷<-☉█⁴⬇️"…⌂0●⁙⁴]♥D♪ち{oaJ░`웃こ⌂▥░つ✽█⬇️ユ웃あ⁴░█a+ᶜ☉そ•、⬇️█q。⁷S…セᵇ☉#o0⬇️ウ✽レ웃テ♪+`⬇️█n⬇️¹░ャ^⬇️█l``k`⬅️¹lIX░▮p◆▮░#k░Z⬇️█\0。⁷t░ 웃$웃 ⁴`bc`░█⬇️V★²B\0\0C⬇️`H░"0⬇️◝░⁵o`b@⬇️¹FfD░⁷░ᵇ\\[@@⌂ᵉ●▮✽、c`ᶜ░█Y░█⁷fF⬅️#♥ a⁴⬇️゛G✽█✽VFFDEB⌂JA⁷⁷q\0D@\\░█om░█z⬇️ `IKP⬇️¹QvQ░⁷⬅️⁴L●「⁷q⁷VvVT⬇️$s`⁴ᶜ⬇️ぬj⁷⁷⬇️█⬇️²<\r\r\r-\0⌂¹s`ooj\0G`⁴░サI\0⁷**☉。q⬅️&⁷*Ha⬇️█]noozMnnn`IGl`⧗¹k♥「RVS`p●!l、░ソi⁷\0●█░✽。dRvSRvVVvS⬇️ ⁴o`eW◆█웃|♥✽⁷*G`⬇️ワ⬇️ュon^⬇️웃⬇️█`⁴⁴⁴o♥¹웃ᵇ░「░ᶠoom⌂▶zzᵇ⬇️█q⬅️█C⬇️<~o○o~om○oz⁴⁴B⁷C░\\░█⁷。\0\0KPQ⬇️うPPQvQP♥¹LW●█mz⬇️ウ⬇️オrs⬇️█⬇️ᶜzz0░a●²♥█zᵇᶜ●)웃³•`I⁷\0✽█q✽█hozyxzz⁴xzmy⬇️Q⁴⁷,\r♥█q░█Gl`p`✽¹●⁷p`kIha⬇️◜0n^⬇️q⬇️ちk``l웃w웃jᵇ♥█z•ᶜo⬇️)●❎Mn⬇️¹`I\r\r=☉◝⁷。C○z{⬇️○⬇️░]nn{zo⬇️、oz⁴░█⁷w*⬇️█⬇️チ⁴omo✽¹✽⁷`Yh⬇️スM^░⁙✽ホ♥ ⌂ッ⬇️らᵇ+♥█o0•0oᶜ♥◝m░0`I웃}░█g~xmz⁴⬇️◝z⬇️◝xy~R⁷S⬇️テ░█,*☉█✽9⬇️よ⬇️ラ⬇️█IX`o{●□◆█0░:░⬆️⬇️█、░█░ユ{⬇️ᶜ❎█q⁷⁷> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + + +-- CAMERA +---------------------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +---------------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +---------------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +---------------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(13, 94, 20, 102, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "dATA SHARDS LEFT: " .. count_remaining_fragments() .. + "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- MAIN +---------------------- +function _init() + -- save_compressed_map(0x2000, 0x2fff, "compressed_map_upper_2.txt") + -- save_compressed_map(0x1000, 0x1fff, "compressed_map_lower_2.txt") + + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS = { + "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", + "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", + "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", + "FACILITY 800A:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" + } + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + + load_compressed_map() + change_state("intro", false) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, use_transition) + local new_state = states[new_state_name] + + if use_transition and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + music(05) + intro_counter, intro_blink = 0, 0 + x_cortex, x_protocol = -50, 128 + + TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_page = 1 + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + intro_counter += 1 + intro_blink += 0.02 + + local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol + x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) + x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + + if prev_x_cortex != TITLE_FINAL_X_CORTEX and x_cortex == TITLE_FINAL_X_CORTEX or + prev_x_protocol != TITLE_FINAL_X_PROTOCOL and x_protocol == TITLE_FINAL_X_PROTOCOL then + sfx(20) + end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select", true) + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(intro_blink) < .9 then circfill(63,64, 3, 2) end + + display_logo(x_cortex, x_protocol, 0, 12) + + intro_text_panel:draw() + controls_text_panel:draw() + print("PRESS ❎ TO CONTINUE", 24, 118, 11) +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + music(0) + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,69,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + elseif btnp(⬅️) or btnp(➡️) then + sfx(19) + show_briefing = not show_briefing + elseif btnp(❎) then + change_state("loadout_select", true) + elseif btnp(🅾️) then + change_state("intro", true) + end + + if prev != current_mission or btnp(⬅️) or btnp(➡️) then + info_panel.char_count = 0 + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select", true) + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay", true) + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + load_compressed_map() + music(0) + player_hud = player_hud.new() + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 + + local mission_entities = { + [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], + [[0,0,bot,player|528,144,bot,dervish|624,160,bot,vanguard|688,288,bot,dervish|616,48,preacher,cyberseer|824,136,bot,warden|680,96,bot,dervish|920,32,bot,dervish|984,96,bot,warden|896,160,bot,vanguard|904,312,preacher,quantumcleric|976,248,bot,vanguard|800,376,bot,warden|728,336,bot,vanguard|816,320,bot,vanguard|608,360,bot,warden|968,1200,bot,vanguard]], + [[0,0,bot,player|240,416,bot,warden|88,336,bot,dervish|160,368,bot,vanguard|24,416,bot,warden|216,104,bot,vanguard|256,40,bot,dervish|296,72,bot,dervish|136,80,preacher,cyberseer|32,88,bot,dervish|32,32,bot,dervish|40,160,bot,warden|344,344,bot,vanguard|456,336,preacher,quantumcleric|368,416,bot,dervish|416,128,bot,vanguard|344,136,bot,vanguard|424,96,preacher,quantumcleric|352,240,bot,vanguard|432,264,bot,vanguard|496,152,bot,warden]], + [[0,0,bot,player|880,412,bot,dervish|760,408,bot,dervish|696,424,bot,vanguard|600,360,bot,warden|552,400,bot,warden|592,256,bot,vanguard|528,280,preacher,cyberseer|528,208,bot,vanguard|560,168,bot,vanguard|688,296,bot,dervish|688,360,bot,dervish|760,304,bot,warden|912,344,preacher,quantumcleric|848,344,bot,dervish|712,192,bot,warden|776,200,bot,warden|888,192,bot,warden|984,184,preacher,cyberseer|992,256,bot,vanguard|640,32,bot,vanguard|632,104,bot,vanguard|664,32,bot,dervish|664,104,bot,dervish|704,32,bot,dervish|704,104,bot,dervish|896,40,preacher,quantumcleric|968,96,preacher,cyberseer]] + } + + for e in all(stringToTable(mission_entities[current_mission])) do + if e[4] != "player" then + add(entities, entity.new(unpack(e))) + end + end + + boundaries = stringToTable("0,0,0,0,64,56|64,0,0,0,128,56|0,0,0,0,64,56|64,0,0,0,128,56")[current_mission] + + for map_y = boundaries[2], boundaries[6] do + for map_x = boundaries[1],boundaries[5] do + local tile, tile_x, tile_y = mget(map_x,map_y), map_x*8, map_y*8 + if fget(tile, 6) then + add(barrels, barrel.new(tile_x, tile_y)) + elseif fget(tile, 5) then + add(data_fragments, data_fragment.new(tile_x, tile_y)) + elseif fget(tile, 4) then + add(terminals, terminal.new(tile_x+4, tile_y-4)) + elseif fget(tile, 7) then + player_spawn_x, player_spawn_y = tile_x, tile_y + player.x, player.y = tile_x, tile_y + player.health = player.max_health + add(entities, player) + end + end + end + + local door_terminals = { + [[444,130,472,80,red|354,66,248,368,green]], + [[808,252,712,48,green|824,252,952,56,red|840,252,568,376,blue]], + [[184,2,160,392,green|392,282,144,224,red|360,170,320,408,blue]], + [[620,2,552,304,green|652,2,904,280,red|684,2,984,272,blue]] + } + for args in all(stringToTable(door_terminals[current_mission])) do + create_door_terminal_pair(unpack(args)) + end + + game_ability_menu = ability_menu.new() + game_minigame = minigame.new() +end + +function update_gameplay() + if game_minigame.active then + game_minigame:update() + else + if btn(🅾️) and not game_ability_menu.active then + game_ability_menu:open() + elseif not btn(🅾️) and game_ability_menu.active then + game_ability_menu:close() + end + + if game_ability_menu.active then + game_ability_menu:update() + else + foreach(entities, function(e) e:update() end) + foreach(terminals, function(t) t:update() end) + foreach(barrels, function(b) b:update() end) + + for i = #particles, 1, -1 do + local p = particles[i] + p:update() + if p.lifespan < 0 then del(particles, p) end + end + + cam:update() + player_hud:update() + end + end +end + +function draw_gameplay() + reset_pal(true) + map(0,0,0,0,128,56) + + for group in all({terminals, data_fragments, entities, particles, barrels}) do + foreach(group, function(e) e:draw() end) + end + + foreach(doors, function(d) d:draw() end) + + if player.health > 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + + -- check mission status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select", true) + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(28) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,500,500,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) + sfx(30) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + local decel = self.subclass == "player" and 5.5 or 1 + self.vx -= dx * decel + self.vy -= dy * decel + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 20 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx, + sy, + dx * 5, + dy * 5, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(10) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self:reset_plasma_cannon() + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx, dy = target.x - self.x, target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + local points = { + {x, y}, + {x + self.width - 1, y}, + {x, y + self.height - 1}, + {x + self.width - 1, y + self.height - 1} + } + + for point in all(points) do + if check_tile_flag(unpack(point)) then + return true + end + end + + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 20 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 20), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + if ending_sequence_timer == 1000 then + music(7) + elseif ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + ending_sequence_timer -= 1 + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +a27070707070b40555550505370603030303a7a7a7a7f606a67070707070705262707070707070707070707070701770707070707070707070c3d0d0777070a2 +707070177086f6e7e7e7e7f7a67070b40515655565450515056505654555050515656505156515656745550505156505654505556545c47406f640a7a7f60694 +a2a2a270707074c60607070606c6f6a7404003a7a74040069470707070c2a27070a2d270707070707070707070707070707070707070707070177070707070a2 +707070707047060706b6f6f69670c284c6060607060606060606060606060607060606060606060607070607060606060606060606b6957416f6404040f60694 +a2a2a27070707406f6404040f6f6f6a7a7a74003030303069470707070d100707070d170707070707070707070707070707070707070707070707070707070a2 +70707070700000443606f6e7a6d0d37416f6f6e7e7f6f6404003b2b2b2c1f6f6f6f6f6f6f640404003f6f6404040f6f6d6c1c1f6f606a6740640a703a7f60694 +a270d17017707406f6a7a7a7a7a7a7a7a7a7a7a797a7f6069470707070d100707070d170b40505055515707070704555050505551565674555056545550505c4 +70707070700000708416f7f79670707506e78797a7a7a7a7404003b1c1a7a740a7a7a7a7a7a740b10303a7a7a7a7b0a7d603a7a7f616967406f603a703e60696 +a270d17070708416f6a740a7a7a7a7a7a7a7a797a797f7069470707070d100707070d17074c6060607062570703507060606070606060707060606060606b694 +70701770707070707516f6f79670707034878787a7a7a7a7a7a7a703b0a7a7a7a7a703c0a7a7a7a7b103a7a7b040b2c003a703a7f606947406f6b703a7400694 +a270d17070708416f6a7a7a7a7a7f6f6f6f7f6f7a797f7069470707070c3d2707070c3d07406f6f6f6f7f69787f7f6f6f6f6f6f6f6f6f6f6404040f6f6f60694 +70707070707070000086f6e7a67070707087a78797a7a7a7a7a7b0c003a7a7a7a7a7a703c0a7a7a740c003a7b040b2c1c103a7a7f606a67406f6f64040400694 +a2d0d37070708606c0a7a7a7a740f6c606060624707034c6947070707070d170707070708416f603a7a7979787a7a7a7b0a7a7a7a7a7a7a7404040a703f61694 +7070707070a270000086e7e7a6707070708797a787a7a7a7a7a7b0b203a7a7a7a7a7a703b0a7a7a7a7b103a7b1b2c1b0b0c0a7a7f6069674b60606060606c694 +a2707070a2708406b2c0a7a7a7a7f6062604147070707044c57070707070d170707070708606f6a7a7a7a797a7a7a7b0b2c0a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070a270a2707076e7e7a670707070878787a7a7a7a7a7a7b0c103a7a7a7a7a7a70340a7a7a7a7a70303a7a7a7b1404040c0f60694b504146444545404c5 +a2707070a2a27416b2c1a7a7a7a7f6069470707070707070707070177070c3d0d0d0d0d08406f6a740a7a7a7a7a7a7b1b2b2c0a7a7a7a7a7a7a7a7a7a7f60627 +0505c4c2d0a270707086f7f6a670707035f68797a7a74040a7a7b103c0a7a7a7a7a740034040c0a7a7a7b1c003a7a7b14040b240f606960070707070707070a2 +a270177000a28406b2c0a7a7a7a7f60694701770707070707070707070707070177070707416f6a7a7a7a7a7a7a7a7a7b1b2b2c0a7a7a7a7a7a7a7a7a7b0b606 +06b694d1707070707047e7f65770708506f7a7a7a7a74040a7a703b0b2c0a7a7a7a74003b0c1a74040a7a7b10303a7a7b1b1b140f606a60070177070707070a2 +a270707000708606b2c1a7a7a7a7f606270515656745550505c4707070707070707070708406f6a7a7a7a7a7a7a7a7a7b1b2c1c1a7a7a7a7a7a7a7a7a7b1b240 +b206a4a2a2a2a2a2a2000000007070841640a7a7a7a7a7a7a703b0c1b1c0a7a7a7a74003c1a7a7a740a7a7a7a7a7a7a7a7a7a7a7f616960070707077707070a2 +a270707070708606c1a7a7a7a7a7f6b60606060607070606b694707070707070707070708606f6a7a7a7a7a7a7a7a7a7a7b1c1a7a7a7a7a7a7a7a7a7a7b0c0b2 +b2069670707070000000000000707086064040a7a7a7a7a7a703b1b7b1b2a7a7a7a70303a7a7a7a7a7a7a7a7a74040a7a7a7a7a7f606a60070a2707070a270a2 +a270707070708506f6a7a7a7a7a7f6f6f6f6f6f6404040f60694707070707070707070708616f6a7a7a740a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b1b2 +b206947070707000007070707070708406404040a7a7a7a7a7a703b1b2c0a7a7404003a7a7404040a7a7a7a7a7a7a7a7a7a7a7f6f606a60070701770707070a2 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7404040f60694d0d0d0d0d270707070708506f603a7a7a7a7a70340404003a7a7a7a7a7a7a7a7a740a7a7a7b1 +c116967070701700707070707070707416404040a7a7a7a7a7a7a703b1c1a7a7404003a7a74040f6f6f6f6f6f6f6f6f6f6f6f6f6f606940070707070707070a2 +a270701770707406f6a740a7a7a7a7a7a7a7a7a7a7a7a7f6069470701770d170707070707406f6f6f6f6f6f6f60340404003f6f6f6f6f6a7a7a7a7a7a7a74040 +f606a67070707000007070707070708406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7a703a7a7a7a7a7f6c6060607060606060606070606c6940070707070177070a2 +a270707070708616f640a7a7a7a7a7a7a7a7a7a7a7a7a7f60696707070a2a2700070177074b6060606060607060606060707060606b6f6a7a7a7a7a7a7f64040 +f616967070707070707070707070708606f6a7f6e7f6f6f6f6f6f6f6f6f6f6f6f603f6f6a7b7a7f60626146466646644041464646604c50070707070707070a2 +a2a27070707084064040a7a7a7a7a7a7a7a7a7a7a7a7a7f606a670707000a2a200707070b55404541466046644040464045404543606f7a740a7a7a7a7f6c606 +06c6947070707070707070707070708606e7b7f6c606060606070606060606060606b6f6a7a7a7f60696707070707070707070701770707070707070707070a2 +a2a2707070708406f6f6f6f6f6f6f6f6f6f6a7a703a7a7f61696707070707070707000a200707070707070d170707070707070707516f6a7a7a7a7a7a7f60626 +0404c57070700000007070707070708506f6e7f6062614660466440404646644043606f6f6f6f6f60694707770707070707070707070707070a27070a27070a2 +a2a27070707074b60606060606060606b6f6a703a703a7f606a670707070707070707070a2707070707070d170177070707070707034f787a7a7a7a7a7f60695 +70707070701700000070000000000074b6060706c694707070707070707070707074b60606070606c694707070a27017707070707070707070705161707070a2 +a27070177070b504545454146604043606f6a7a703a7a7b006a67070707017707070707070a27070a2d0d0d3707070707070707070709787a740a7a7a7f616a5 +7070a2707070000000707070707070b56604666644c57070701770707017707070b504041464666466c57070a2707070707070707070707070705262707070a2 +a27070707070707070707070d17070740640a7a7a7a7b0b20694707070707070707070c2d0a2d0a270a2707070707070707070707070a797a7a7a7a7a7f616a4 +000070a27070707070707070707070707070707070707070707070707070707070707070707070707070707017707070701770707017707070a27070a27070a2 +a27070707070701770c277d0777070740640f6f6f6f6b2b206947070707070707070707770a27070a2d2707070707070701770707035f7f6e7f6f6f6f6f606a4 +70777070707070707070177070707070707070707070000000a2a2a2a2a2707070707070707070707017707070707070707070707070707070707070707070a2 +a27070707070707070d1707070707074b606060606060606c6947070707070707070707070a2707070d17070707070707070707085b60606060706060606c694 +70707070707070707070707070707070707070707070000000a2707070a27070701770707070707070707070707070707070707070707070707070707070a270 +70a2a2a2a2a2a2a2a2a2a2a2a2a2a2b5040454146604660404c5a2a2a2a2a2a2a2a2a2a2a270a2a2a2a2a2a2a2a2a2a2a2a2a2a2b504146604664404046404c5 +a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2707070a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a27070 +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeee +33333333e33333333e33333333e33333333e33333333e33333333e33333333ee33eeeeee33333333e33333333e33333333e33333333e33333333e33eeee333ee +33333333e33333333e33333333e33333333e33333333e33333333e33333333ee33eeeeee33333333e33333333e33333333e33333333e33333333e333ee333eee +eeeeee33eeeeeee33e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33eeeeeee33eeee33eeeeeeeeeeeeee333333eeee +33333333e33333333e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33333333e33333333e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33eeeeeee33e333eee33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33e333eeeeee33eeeeeeeeeeeeee333333eeee +33eeeeeee33ee333ee33333333eeee33eeee33333333e33333333e33333333ee3333333333333333e33333333e33ee333eeeee33eeee33333333e333ee333eee +33eeeeeee33eee333e33333333eeee33eeee33333333e33333333e33333333ee3333333333333333e33333333e33eee333eeee33eeee33333333333eeee33eee +33eeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeee3eee +3eeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeee +__label__ +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000000000000000000000001001100000000000000000000030000000000000000000000000000010011000000000000000000000000000000000 +00002000000020033333333033333333033333333033333333033333333033000033300000000000000000000011010000000000000000000000000000000000 +00000000000000033333333033333333033333333033333333033333333033300333000000000000000000000010011000000000000000000000000000000000 +00000000000000033000000033000033001101033000033000000000000003333330000000000000000000000001011000000000000000000000000000000000 +00002000200002033222222033222233033333333222233002233333333222333322222002222220000000000011001002222220022222200000000000000000 +00000000000000033200000033000033033333333200033002033333333000333300000002000000001111110101011002000000020000000000000000000000 +00000000000000033200000033000033033033300200033002000000020003333330000002000000001010101111001002000000020000000000000000000000 +00000002000000033333333033333333033103330200033002033333333033300333000002000000001100000000011002000000020000000000000000000000 +00000000000000033333333033333333033001333200033002033333333333000233000002000000001001111010101002000000020000000000000000000000 +00000000000000000200000002000000001101033200000002000000020000000203000002000000001101011111111002000000020000000000000000000000 +00000000000000000000000000000000001001103000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 +00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 +00000000020000000200000000000000001101011111133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001001111010133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001100000000011000033000000033033000033000033000033101033033000000033000033003300200000000000000 +00000000020000000200000000000000001010101111033333333033333333033000033000033000033001133033000000033000033003300200000000000000 +00000000020000000200000000000000001111110101033333333033333333033000033000033000033101033033000000033000033003300200000000000000 +00000000000000000000000000000000000000000011033000000033033300033000033000033000033001133033000000033000033003300000000000000000 +00000000000000000000000000000000000000000011033000000033003330033333333000033000033333333033333333033333333003333333300000000000 +02222220022222200000000000000000000000000010033000000033000333033333333000033000033333333033333333033333333003333333322002222220 +02000000020000000000000000000000000000000011033000000000000033000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010031000000000000003000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000010011002222220022222200222222002222220001100100000000000000000000000000000000002222220 +01010101010101011111111000000000000000000011010102000000020000000200000002000000010101100000000000000000000000000000000002000000 +11111111111111111010101000000000000000000010011102000000020000000200000002000000111100100000000000000000000000000000000002000000 +00000000000000000000011000000000000000000011000002000000020000000200000002000000000001100000000000000000000000000000000002000000 +10101010101010101111001000000000000000000010101002000000020000000200000002000000101010100000000000000000000000000000000002000000 +11111111111111110101011000000000000000000011111102000000020000000200000002000000111111100000000000000000000000000000000002000000 +00000000000000000011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000000000000222222000000000000000000000000000000000022222200000000000000000000000000000000002222220 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000000000000000000000000000000000000222222000000000000000000000000002222220 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000010011000000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222222000000000000000000000000000000000 +02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 +02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 +02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 +02000000000000000010011000000000020000000010101010101010201100222000000000000000001001110200000010101010101010101010101010101010 +02000000000000000011010000000000020000000011111111111111201002222200010000000000001101010200000011111111111111111111111111111111 +00000000000000000010011000000000000000000000000000000000201022222220010000000000001001100000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000201022222220000000000000000101100000000000000000000000000000000000000000 +02222220000000000010011000000000022222200000000000000000201022222220010000000000001100100222222000000000000000000000000000000000 +02000000001111110011010101010101020000000000000000000000201002222200010001010101010101100200000000000000000000000000000000000000 +02000000001010100010011111111111020000000000000000000000200000222000110011111111111100100200000000000000000000000000000000000000 +02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 +02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 +02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 +00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 +00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 +01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 +02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 +02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 +02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 +02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 +02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 +02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 +02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 +02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 +0000000000000000020000000200000002000000001101000bbbbb00020000000200000002000000020000000200000000110100020000000000000000000000 +0000000000000000000000000bb0bb00bbb00bb00bb00110bb0b0bb00000bbb00bb000000bb00bb0bb00bbb0bbb0bb00b0b0bbb0000000000000000000000000 +000000000000000000000000b0b0b0b0bb00b000b0010110bbb0bbb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b1bb00000000000000000000000000 +000000000000000000000000bbb0bb00b00000b000b10010bb0b0bb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b0b110000000000000000020000200 +000000000000000000111111b101b1b10bb1bb01bb0101100bbbbb0000000b00bb0000000bb0bb00b0b00b00bbb0b0b00bb10bb1111111100000000000000000 +00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 +00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 +00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 +00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + diff --git a/versions/v0.1.1.p8 b/versions/v0.1.1.p8 new file mode 100644 index 0000000..36ff4e7 --- /dev/null +++ b/versions/v0.1.1.p8 @@ -0,0 +1,2428 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + +--[[ +CORTEX PROTOCOL + by Emanuele Bonura + itch: https://izzy88izzy.itch.io/ + github: https://github.com/EBonura/CortexProtocol + instagram: https://www.instagram.com/izzy88izzy/ + minified with: https://thisismypassport.github.io/shrinko8/ + +How to Play: +* Use arrow keys to move +* Press X to use your selected ability +* Press O to open the ability menu and switch between abilities +* Interact with terminals using X when prompted + +Main Objective: +* Activate all terminals and reach the extraction point + +Optional Objectives: +* Collect data fragments to restore health and earn credits +* Eliminate all enemy units +]] + +-- MAP COMPRESSION +---------------------- +-- function compress(data) +-- local result = "" +-- local i = 1 +-- local data_len = #data + +-- while i <= data_len do +-- local best_len, best_dist = 0, 0 + +-- for j = max(1, i - 255), i - 1 do +-- local k = 0 +-- while i + k <= data_len and sub(data, j + k, j + k) == sub(data, i + k, i + k) do +-- k += 1 +-- end + +-- if k > best_len then +-- best_len, best_dist = k, i - j +-- end +-- end + +-- if best_len > 2 then +-- result = result..chr(128 + best_len)..chr(best_dist) +-- i += best_len +-- else +-- result = result..sub(data, i, i) +-- i += 1 +-- end +-- end + +-- return result +-- end + +-- function save_compressed_map(start_address, end_address, filename) +-- -- Gather map data +-- local map_data = "" +-- for addr = start_address, end_address do +-- map_data = map_data..chr(peek(addr)) +-- end + +-- -- Compress the data +-- local compressed = compress(map_data) + +-- -- Convert compressed data to Lua string +-- local lua_string = "" +-- for x in all(compressed) do +-- lua_string = lua_string.."\\"..ord(x) +-- end + +-- -- Save to file +-- printh(lua_string, filename, true) +-- printh("Compressed data saved to "..filename) +-- end + +function decompress_to_memory(data, mem_index) + local i = 1 + while i <= #data do + local byte = ord(data[i]) + if byte >= 128 then + local source = mem_index - ord(data[i + 1]) + for j = 1, byte - 128 do + poke(mem_index, peek(source + j - 1)) + mem_index += 1 + end + i += 2 + else + poke(mem_index, byte) + mem_index += 1 + i += 1 + end + end +end + +function load_compressed_map() + if current_mission <= 2 then + compressed_map_lower="Gk`⬅️¹lbc☉▮kozz⬇️³░⁴⁴{`IG`⁴、、✽■✽³o0^0n^o`i⁷●¹q✽⁸KPPQ⬇️ᵇVvVTP✽¹s`░F웃cl⬇️Tm●E●⁵`Y[@@@FfD⬇️⁶✽³\\☉▮c░:zoo░⬇️⁴o░█웃|●░{⬇️うᵇ`Z♥z●♥Wl``RVS`p⌂カ░を⬅️¹⬇️█om⬇️サMN✽チ`j⬇️は░つQvQ●の☉⁶✽む☉メ⁴…█Mnn0ozᵇ+`I✽◝*✽³⬇️웃C☉l⁴●³0ᶜᶜ+ᶜ🅾️まmo]n./^]on⬇️¹`iG⬇️はˇ²░む웃▒…█⬇️れ░○☉█⁷‖◀⬇️◝,\r\r\r♥ふ⁴●⁵⁴0•+•、✽っᵇ♥♥M]⬇️ン>?░@⬇️'⬇️█`o•++、om●む♥⁷●ん✽J⬇️Ko⬆️█zz•⁴⁴`Y●█%&⬇️◝。⁷q\0⌂ふ●⬇️0⬇️~░Z⬇️てᶜ⬇️bMn^░y░?●█░ス░█●R♥Y😐░⁴⁴░▥✽█♥▮░■░なᵇ+⬇️█Z✽◝*⬇️³\r\r=⁷⁷S⌂8●;0ozxol`♥¹k^✽_⬇️✽✽◝zo`YH⬇️ス•、☉ンM^✽ロ●2p☉7``lIWkko○웃I⌂「✽◝☉⁵Xk…!kozyo`b@✽¹c⬇️lzz░d░fMnnn`iHa⬇️⌂⬇️u]N♥◝░▒░2fD●5●;\\qFao⬇️IAFFE@@F░。Ef@D@\\⁷⁷\0⌂¹[⬇️hFFDEB☉?n^✽M⁴⁴`IW`⁴z⬇️▥⬇️⁴•+、○`J⁷H⁴xy✽}░⁶░◝⁷\0,=⁷⁷h`⬇️のN●そzM░ゆ^⬇️█⁷♥█⬇️さ░+q⁷⁷**⁷⁷KPQVVvQ⬇️めVvVPQvQPPLG░@^✽k░る✽r░█h⬇️█omz0░▒、o`j⁷h⁴⁴⬇️f○oo~⬇️²|✽█⬇️◝⁷ha⬇️む⬇️やᵇo•ᶜ░B⬇️●o`i⁷⁷<\r-웃|⬇️◜,░█Wl`░¹RVS`p✽\n`k⬇️ヘo✽フzo♥●░ロ⬇️るha○zo]0y0✽ケai⁷tBFfCfFC✽:u✽█░ケg░@mᶜ0•ᶜo、⬇️るᵇ+++oO░レ░ヨ●|░て░つ⁷⁷Co○~⬇️テ⁴o~⬇️イ⬇️⁶⬇️hg`⬇️ス⬇️ンM^⬇️テ⬇️◜✽on⬇️█●▤0y^░ナ░キ♥MfFfFF\0░サ⌂█]0z0░せ^░○、z✽レ░ヨ⌂²✽█\0\0~░マzx✽⁶z⁴{o`Yh░█⬇️ワ]N●‖mzooᵇᶜ`IX⬇️スzon^z░3○✽キ⌂P\0\0웃サ░(░そ⬇️し⬇️を•、•、z⬇️█\0\0\0。⬇️⁴dRVSe⬇️ᶜq\0<**⬇️⁘\0o♥{●▒]⬇️ヘg⬇️█⬇️s░y●█⬇️○+、`IH⬇️スM^oo░2xy●◜░レ✽⁷✽█q\0░サG`oᶜ✽そ░○♥]✽█<-\0\0h~~○j░x\0\0░█q\0⬅️%✽ヤ`Y✽ヘ⬇️ルo⬇️y░²mozᵇ、o⬇️█aᶜ☉⧗✽R♥ヲ░わ⬇️ュ*✽\r░█W`o+✽そzzᵇᵇ⬇️⌂░F|✽そ⬇️えg○{oj⁷░と✽ᶠ\0\0⬇️て⬇️オ⬅️█i⬇️ら{zzMn⬇️る░@z]Nᵇ、{⬇️█G`+ᶜmzy●コy♥█,*\r\r\r-●\\*⬇️░░c`o、●そᵇ、••ᶜoz⁴⁴⁴`Z░゜=q⁷h○~~R░█⬇️-⬇️て⁷\0~•⬇️c{N●◝⁴zo`Z⬇️█♥ョᵇ+++ᶜ⬇️ᵇ•⬇️イ`IW`+、mo~⬇️セ○~○~░キ⬇️|=░セ<░ \r\r*⬇️😐w⁷⁷X⬇️@░>oᵇ、✽ュ⬇️○⁴`I●か⁷tBqCu✽█w✽◆¹░ョ⬇️て➡️,lI[@✽¹AqDAffFFDEBD░□\\q⁷CBFFf⬇️²A⬇️⁶░オ∧²♥@@@⌂<⬇️>あ,😐'░h" + compressed_map_upper='⁷⁷*∧¹⁷⁷SvvQ⁷q⁷VvVTPPQ⬇️ᶜ⁷⬇️⁸⬇️²UQVVv⬇️⁷░\rLK☉•●‖♥□⬇️!♥1L♪`\0K░<░:⬇️>░A⬇️゜⁷♥¹\0\0。✽ᵇ●⁶⁷d``p``RVS░⁷●ᶜv░□✽ᵉ☉⁴kIGl♥•😐□⌂1kI✽`q\0⬇️^⬇️⁴*W✽。p●Ep☉゜♥█<\r\r*-⬇️➡️q\0⬇️ワ_⁴oo~ooo○o•+⁴⁴⬇️\r✽ᵉ♥³m░ᵇ⁴o{`IG`nN●▮●⁴░□⁴⬇️•✽!⁴o`Y⬇️}⬇️タ\0\0,=░⁶⁷C~⁴x○⬇️Zm~o○~o`J⬇️◝q☉█░◝‖◀=⬇️⬅️░ひ~xzzyzxyzz•、⬇️♥⬇️⁸░²⬇️‖⬇️ m⬇️\r⬇️_⁴`YG`o{●▮░⁘{✽█o{⁴⁴░.░█i⁷⁷w✽█。\0q░█⁷○y⬇️RxM^zz~{oaI*⁷w\r\r-♥█⬇️⌂%&░⬅️░★⬇️rx░ラ●x░q⬇️●o●▒✽█░モiW`░!]⬇️ュ⬇️R⬇️ˇ✽█⬇️か●9o`Z\r\r=⬇️タq⁷**░●⁷Sox⬇️ケxmxxyy○~`⬇️█⬇️◜。♥█⁷⁷*\r=*q✽█}~○oo~⬇️れ✽w░³⬇️\r⁴oᵇᶜ✽pN✽●⬇️_h✽█✽しoMnnn♥█o⁴░‖⁴⬇️◆░コ░ン●█Xa~♥ょzᵇyz○`J🅾️█,=░ッ⁷⬇️に⁷t`░¹p``BfFFfC⬇️ ●▮k✽[✽A]n`JhaoMN░ヨzMn^zz⁴✽█]⬇️ュ0⬇️~Mn`rPPQVTPL⬇️ハ\0\0q\0G`○y░に⬇️け•zxoaI⬇️█q⌂█✽웃⬇️ぬ░ˇ░wD@@A⬇️く⬇️れDAFffDE@ca⬇️ソ░ユoz•ᶜ⬇️トG`n^mo]n./^░◀●█⬇️◝0z0no^ok●ほkI✽█\0⬇️█ox░>0⬇️ろyzo`JKP⬇️くvQ⬇️⁶け³v⬇️ゃs⬇️^●█░¹`Y░n░ヤ>?●∧♥█nn0⁴⁴oz⁴⬇️⁵✽(I⁷**░ニHa~⬇️wz0y0Nzyx░らl●けた⁶l✽█⬇️~░♥`j░モ░ンm░∧Mnn`IW✽□⁴m⬇️⁴░し⬇️▒░∧,●█⬇️にx░ゆ0y]⬇️さnaJ⬇️.om⬇️Mᵇ+●ち░2♥ふ░「•、☉◀⌂⁸⬇️█⬇️◝mz0zM♥█⬇️ユ^░◝⬇️t`Ih░★⁴⬇️U░そ⬇️テk⬇️♥`I=⁷⁷q\0\0\0G`○x░ぬ]N~zy⬇️らY⬇️.{⬇️<⬇️█、⁴⬇️セ⁴⬇️⁵ᵇᶜ✽bzM░「▤³o]0z0^`Z✽█M^✽▤⬇️1░█a✽けm✽す`bc`░█rQ⁷⬇️¹TL⬇️にy░ゆoox✽█i♥.⬇️○✽サ⬇️ャᵇ+ᶜ░ノ✽◝✽ョᵇ♥▮☉²●█zz0zz`I⬇️█░ヲ✽の⬇️s⬇️マ✽□Mn⬇️コ⁴o●ᶠzok``R⁷⁷SkIH`y░█●かyx`Zh░らzz•●○l`✽¹k░█♥ᶜ…▮k⬇️ホo░そn^~~`J░█☉ち░ね●★웃も♥█⬇️ス~o○o`YG`xxyz⬇️³mx⬇️⁶yaJX`o{N●よ⁴⬇️ゆb@⬇️¹c░⬆️♥ᶜ…▮ck⬇️え웃うlIG⬇️タ⬅️そ░ヨ😐□⬇️!✽ニ⁴⁴zx{o`iH`ox⁴⬇️の~m○~oo○`J⬇️■웃と⁴⬇️ゆKPPPs●█r⬇️ᵇL…▮[░えᵇab@AFD@E@\\[░ねFFDEBD░ま⬇️ヨ[☉ᶠ✽よ\\●█✽ヌ○`Z⬇️にp♥ち░ろ◆ら░オ✽▶░ユ░セk➡️▮Ksaoz•░うQVT⬇️き\0\0\0⁷●¹T░ま●へ😐³s`n⬇️¹Nzz⁴░ト✽に@Ec`⬇️ムb░か░らFf♥と✽に⁴⬇️)░ユmo⁴⁴`I⌂▮⬇️◀⬇️テGl⬇️え✽う✽んI⁷w\0⁷dRVVSS⬇️スlo웃¥⬅️ケ⬇️イzm⬇️◝⬇️█rQVVv✽ˇ░\r⬇️ちLK⌂にQvQ✽¥⬇️た⬇️2z]n░█H`✽\r░■⁴{░█⬇️ノz⬇️⌂o⁴++░き\0。\0⁷C░▮웃⁴•、░゛░わ░\'0●>░█⌂ほ웃ちk⬇️オ➡️に⬇️ヘ✽soᵇᵇ⬇️█a웃█⬇️★`IW웃…•++ᶜ`Y⁷⬇️█⁷웃"☉つ✽█✽ひ♪█✽ま☉あ░わa✽オ✽ぬom☉を☉ユᵇ++✽ユ{N●ろ⁴zo`Ih`ᶜ☉…•+⬇️▮⁷<-☉█⁴⬇️"…⌂0●⁙⁴]♥D♪ち{oaJ░`웃こ⌂▥░つ✽█⬇️ユ웃あ⁴░█a+ᶜ☉そ•、⬇️█q。⁷S…セᵇ☉#o0⬇️ウ✽レ웃テ♪+`⬇️█n⬇️¹░ャ^⬇️█l``k`⬅️¹lIX░▮p◆▮░#k░Z⬇️█\0。⁷t░ 웃$웃 ⁴`bc`░█⬇️V★²B\0\0C⬇️`H░"0⬇️◝░⁵o`b@⬇️¹FfD░⁷░ᵇ\\[@@⌂ᵉ●▮✽、c`ᶜ░█Y░█⁷fF⬅️#♥ a⁴⬇️゛G✽█✽VFFDEB⌂JA⁷⁷q\0D@\\░█om░█z⬇️ `IKP⬇️¹QvQ░⁷⬅️⁴L●「⁷q⁷VvVT⬇️$s`⁴ᶜ⬇️ぬj⁷⁷⬇️█⬇️²<\r\r\r-\0⌂¹s`ooj\0G`⁴░サI\0⁷**☉。q⬅️&⁷*Ha⬇️█]noozMnnn`IGl`⧗¹k♥「RVS`p●!l、░ソi⁷\0●█░✽。dRvSRvVVvS⬇️ ⁴o`eW◆█웃|♥✽⁷*G`⬇️ワ⬇️ュon^⬇️웃⬇️█`⁴⁴⁴o♥¹웃ᵇ░「░ᶠoom⌂▶zzᵇ⬇️█q⬅️█C⬇️<~o○o~om○oz⁴⁴B⁷C░\\░█⁷。\0\0KPQ⬇️うPPQvQP♥¹LW●█mz⬇️ウ⬇️オrs⬇️█⬇️ᶜzz0░a●²♥█zᵇᶜ●)웃³•`I⁷\0✽█q✽█hozyxzz⁴xzmy⬇️Q⁴⁷,\r♥█q░█Gl`p`✽¹●⁷p`kIha⬇️◜0n^⬇️q⬇️ちk``l웃w웃jᵇ♥█z•ᶜo⬇️)●❎Mn⬇️¹`I\r\r=☉◝⁷。C○z{⬇️○⬇️░]nn{zo⬇️、oz⁴░█⁷w*⬇️█⬇️チ⁴omo✽¹✽⁷`Yh⬇️スM^░⁙✽ホ♥ ⌂ッ⬇️らᵇ+♥█o0•0oᶜ♥◝m░0`I웃}░█g~xmz⁴⬇️◝z⬇️◝xy~R⁷S⬇️テ░█,*☉█✽9⬇️よ⬇️ラ⬇️█IX`o{●□◆█0░:░⬆️⬇️█、░█░ユ{⬇️ᶜ❎█q⁷⁷> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + + +-- CAMERA +---------------------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +---------------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +---------------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +---------------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(13, 94, 20, 102, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "dATA SHARDS LEFT: " .. count_remaining_fragments() .. + "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- MAIN +---------------------- +function _init() + -- save_compressed_map(0x2000, 0x2fff, "compressed_map_upper_2.txt") + -- save_compressed_map(0x1000, 0x1fff, "compressed_map_lower_2.txt") + + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS = { + "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", + "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", + "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", + "FACILITY 800A:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" + } + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + + load_compressed_map() + change_state("intro", false) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, use_transition) + local new_state = states[new_state_name] + + if use_transition and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + music(05) + intro_counter, intro_blink = 0, 0 + x_cortex, x_protocol = -50, 128 + + TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_page = 1 + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + intro_counter += 1 + intro_blink += 0.02 + + local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol + x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) + x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + + if prev_x_cortex != TITLE_FINAL_X_CORTEX and x_cortex == TITLE_FINAL_X_CORTEX or + prev_x_protocol != TITLE_FINAL_X_PROTOCOL and x_protocol == TITLE_FINAL_X_PROTOCOL then + sfx(20) + end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select", true) + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(intro_blink) < .9 then circfill(63,64, 3, 2) end + + display_logo(x_cortex, x_protocol, 0, 12) + + intro_text_panel:draw() + controls_text_panel:draw() + print("PRESS ❎ TO CONTINUE", 24, 118, 11) +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + music(0) + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,69,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + elseif btnp(⬅️) or btnp(➡️) then + sfx(19) + show_briefing = not show_briefing + elseif btnp(❎) then + change_state("loadout_select", true) + elseif btnp(🅾️) then + change_state("intro", true) + end + + if prev != current_mission or btnp(⬅️) or btnp(➡️) then + info_panel.char_count = 0 + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select", true) + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay", true) + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + load_compressed_map() + music(0) + player_hud = player_hud.new() + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 + + local mission_entities = { + [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], + [[0,0,bot,player|528,144,bot,dervish|624,160,bot,vanguard|688,288,bot,dervish|616,48,preacher,cyberseer|824,136,bot,warden|680,96,bot,dervish|920,32,bot,dervish|984,96,bot,warden|896,160,bot,vanguard|904,312,preacher,quantumcleric|976,248,bot,vanguard|800,376,bot,warden|728,336,bot,vanguard|816,320,bot,vanguard|608,360,bot,warden|968,1200,bot,vanguard]], + [[0,0,bot,player|240,416,bot,warden|88,336,bot,dervish|160,368,bot,vanguard|24,416,bot,warden|216,104,bot,vanguard|256,40,bot,dervish|296,72,bot,dervish|136,80,preacher,cyberseer|32,88,bot,dervish|32,32,bot,dervish|40,160,bot,warden|344,344,bot,vanguard|456,336,preacher,quantumcleric|368,416,bot,dervish|416,128,bot,vanguard|344,136,bot,vanguard|424,96,preacher,quantumcleric|352,240,bot,vanguard|432,264,bot,vanguard|496,152,bot,warden]], + [[0,0,bot,player|880,412,bot,dervish|760,408,bot,dervish|696,424,bot,vanguard|600,360,bot,warden|552,400,bot,warden|592,256,bot,vanguard|528,280,preacher,cyberseer|528,208,bot,vanguard|560,168,bot,vanguard|688,296,bot,dervish|688,360,bot,dervish|760,304,bot,warden|912,344,preacher,quantumcleric|848,344,bot,dervish|712,192,bot,warden|776,200,bot,warden|888,192,bot,warden|984,184,preacher,cyberseer|992,256,bot,vanguard|640,32,bot,vanguard|632,104,bot,vanguard|664,32,bot,dervish|664,104,bot,dervish|704,32,bot,dervish|704,104,bot,dervish|896,40,preacher,quantumcleric|968,96,preacher,cyberseer]] + } + + for e in all(stringToTable(mission_entities[current_mission])) do + if e[4] != "player" then + add(entities, entity.new(unpack(e))) + end + end + + boundaries = stringToTable("0,0,0,0,64,56|64,0,0,0,128,56|0,0,0,0,64,56|64,0,0,0,128,56")[current_mission] + + for map_y = boundaries[2], boundaries[6] do + for map_x = boundaries[1],boundaries[5] do + local tile, tile_x, tile_y = mget(map_x,map_y), map_x*8, map_y*8 + if fget(tile, 6) then + add(barrels, barrel.new(tile_x, tile_y)) + elseif fget(tile, 5) then + add(data_fragments, data_fragment.new(tile_x, tile_y)) + elseif fget(tile, 4) then + add(terminals, terminal.new(tile_x+4, tile_y-4)) + elseif fget(tile, 7) then + player_spawn_x, player_spawn_y = tile_x, tile_y + player.x, player.y = tile_x, tile_y + player.health = player.max_health + add(entities, player) + end + end + end + + local door_terminals = { + [[444,130,472,80,red|354,66,248,368,green]], + [[808,252,712,48,green|824,252,952,56,red|840,252,568,376,blue]], + [[184,2,160,392,green|392,282,144,224,red|360,170,320,408,blue]], + [[620,2,552,304,green|652,2,904,280,red|684,2,984,272,blue]] + } + for args in all(stringToTable(door_terminals[current_mission])) do + create_door_terminal_pair(unpack(args)) + end + + game_ability_menu = ability_menu.new() + game_minigame = minigame.new() +end + +function update_gameplay() + if game_minigame.active then + game_minigame:update() + else + if btn(🅾️) and not game_ability_menu.active then + game_ability_menu:open() + elseif not btn(🅾️) and game_ability_menu.active then + game_ability_menu:close() + end + + if game_ability_menu.active then + game_ability_menu:update() + else + foreach(entities, function(e) e:update() end) + foreach(terminals, function(t) t:update() end) + foreach(barrels, function(b) b:update() end) + + for i = #particles, 1, -1 do + local p = particles[i] + p:update() + if p.lifespan < 0 then del(particles, p) end + end + + cam:update() + player_hud:update() + end + end +end + +function draw_gameplay() + reset_pal(true) + map(0,0,0,0,128,56) + + for group in all({terminals, data_fragments, entities, particles, barrels}) do + foreach(group, function(e) e:draw() end) + end + + foreach(doors, function(d) d:draw() end) + + if player.health > 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + + -- check mission status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select", true) + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(28) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,500,500,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) + sfx(30) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + local decel = self.subclass == "player" and 5.5 or 1 + self.vx -= dx * decel + self.vy -= dy * decel + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 20 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx, + sy, + dx * 5, + dy * 5, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(10) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self:reset_plasma_cannon() + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx, dy = target.x - self.x, target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + local points = { + {x, y}, + {x + self.width - 1, y}, + {x, y + self.height - 1}, + {x + self.width - 1, y + self.height - 1} + } + + for point in all(points) do + if check_tile_flag(unpack(point)) then + return true + end + end + + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 20 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 20), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + if ending_sequence_timer == 1000 then + music(7) + elseif ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + ending_sequence_timer -= 1 + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +a27070707070b40555550505370603030303a7a7a7a7f606a67070707070705262707070707070707070707070701770707070707070707070c3d0d0777070a2 +707070177086f6e7e7e7e7f7a67070b40515655565450515056505654555050515656505156515656745550505156505654505556545c47406f640a7a7f60694 +a2a2a270707074c60607070606c6f6a7404003a7a74040069470707070c2a27070a2d270707070707070707070707070707070707070707070177070707070a2 +707070707047060706b6f6f69670c284c6060607060606060606060606060607060606060606060607070607060606060606060606b6957416f6404040f60694 +a2a2a27070707406f6404040f6f6f6a7a7a74003030303069470707070d100707070d170707070707070707070707070707070707070707070707070707070a2 +70707070700000443606f6e7a6d0d37416f6f6e7e7f6f6404003b2b2b2c1f6f6f6f6f6f6f640404003f6f6404040f6f6d6c1c1f6f606a6740640a703a7f60694 +a270d17017707406f6a7a7a7a7a7a7a7a7a7a7a797a7f6069470707070d100707070d170b40505055515707070704555050505551565674555056545550505c4 +70707070700000708416f7f79670707506e78797a7a7a7a7404003b1c1a7a740a7a7a7a7a7a740b10303a7a7a7a7b0a7d603a7a7f616967406f603a703e60696 +a270d17070708416f6a740a7a7a7a7a7a7a7a797a797f7069470707070d100707070d17074c6060607062570703507060606070606060707060606060606b694 +70701770707070707516f6f79670707034878787a7a7a7a7a7a7a703b0a7a7a7a7a703c0a7a7a7a7b103a7a7b040b2c003a703a7f606947406f6b703a7400694 +a270d17070708416f6a7a7a7a7a7f6f6f6f7f6f7a797f7069470707070c3d2707070c3d07406f6f6f6f7f69787f7f6f6f6f6f6f6f6f6f6f6404040f6f6f60694 +70707070707070000086f6e7a67070707087a78797a7a7a7a7a7b0c003a7a7a7a7a7a703c0a7a7a740c003a7b040b2c1c103a7a7f606a67406f6f64040400694 +a2d0d37070708606c0a7a7a7a740f6c606060624707034c6947070707070d170707070708416f603a7a7979787a7a7a7b0a7a7a7a7a7a7a7404040a703f61694 +7070707070a270000086e7e7a6707070708797a787a7a7a7a7a7b0b203a7a7a7a7a7a703b0a7a7a7a7b103a7b1b2c1b0b0c0a7a7f6069674b60606060606c694 +a2707070a2708406b2c0a7a7a7a7f6062604147070707044c57070707070d170707070708606f6a7a7a7a797a7a7a7b0b2c0a7a7a7a7a7a7a7a7a7a7a7f60694 +70707070a270a2707076e7e7a670707070878787a7a7a7a7a7a7b0c103a7a7a7a7a7a70340a7a7a7a7a70303a7a7a7b1404040c0f60694b504146444545404c5 +a2707070a2a27416b2c1a7a7a7a7f6069470707070707070707070177070c3d0d0d0d0d08406f6a740a7a7a7a7a7a7b1b2b2c0a7a7a7a7a7a7a7a7a7a7f60627 +0505c4c2d0a270707086f7f6a670707035f68797a7a74040a7a7b103c0a7a7a7a7a740034040c0a7a7a7b1c003a7a7b14040b240f606960070707070707070a2 +a270177000a28406b2c0a7a7a7a7f60694701770707070707070707070707070177070707416f6a7a7a7a7a7a7a7a7a7b1b2b2c0a7a7a7a7a7a7a7a7a7b0b606 +06b694d1707070707047e7f65770708506f7a7a7a7a74040a7a703b0b2c0a7a7a7a74003b0c1a74040a7a7b10303a7a7b1b1b140f606a60070177070707070a2 +a270707000708606b2c1a7a7a7a7f606270515656745550505c4707070707070707070708406f6a7a7a7a7a7a7a7a7a7b1b2c1c1a7a7a7a7a7a7a7a7a7b1b240 +b206a4a2a2a2a2a2a2000000007070841640a7a7a7a7a7a7a703b0c1b1c0a7a7a7a74003c1a7a7a740a7a7a7a7a7a7a7a7a7a7a7f616960070707077707070a2 +a270707070708606c1a7a7a7a7a7f6b60606060607070606b694707070707070707070708606f6a7a7a7a7a7a7a7a7a7a7b1c1a7a7a7a7a7a7a7a7a7a7b0c0b2 +b2069670707070000000000000707086064040a7a7a7a7a7a703b1b7b1b2a7a7a7a70303a7a7a7a7a7a7a7a7a74040a7a7a7a7a7f606a60070a2707070a270a2 +a270707070708506f6a7a7a7a7a7f6f6f6f6f6f6404040f60694707070707070707070708616f6a7a7a740a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7a7b1b2 +b206947070707000007070707070708406404040a7a7a7a7a7a703b1b2c0a7a7404003a7a7404040a7a7a7a7a7a7a7a7a7a7a7f6f606a60070701770707070a2 +a270707070707406f6a7a7a7a7a7a7a7a7a7a7a7404040f60694d0d0d0d0d270707070708506f603a7a7a7a7a70340404003a7a7a7a7a7a7a7a7a740a7a7a7b1 +c116967070701700707070707070707416404040a7a7a7a7a7a7a703b1c1a7a7404003a7a74040f6f6f6f6f6f6f6f6f6f6f6f6f6f606940070707070707070a2 +a270701770707406f6a740a7a7a7a7a7a7a7a7a7a7a7a7f6069470701770d170707070707406f6f6f6f6f6f6f60340404003f6f6f6f6f6a7a7a7a7a7a7a74040 +f606a67070707000007070707070708406f6a7a7a7a7a7a7a7a7a7a7a7a7a7a7a703a7a7a7a7a7f6c6060607060606060606070606c6940070707070177070a2 +a270707070708616f640a7a7a7a7a7a7a7a7a7a7a7a7a7f60696707070a2a2700070177074b6060606060607060606060707060606b6f6a7a7a7a7a7a7f64040 +f616967070707070707070707070708606f6a7f6e7f6f6f6f6f6f6f6f6f6f6f6f603f6f6a7b7a7f60626146466646644041464646604c50070707070707070a2 +a2a27070707084064040a7a7a7a7a7a7a7a7a7a7a7a7a7f606a670707000a2a200707070b55404541466046644040464045404543606f7a740a7a7a7a7f6c606 +06c6947070707070707070707070708606e7b7f6c606060606070606060606060606b6f6a7a7a7f60696707070707070707070701770707070707070707070a2 +a2a2707070708406f6f6f6f6f6f6f6f6f6f6a7a703a7a7f61696707070707070707000a200707070707070d170707070707070707516f6a7a7a7a7a7a7f60626 +0404c57070700000007070707070708506f6e7f6062614660466440404646644043606f6f6f6f6f60694707770707070707070707070707070a27070a27070a2 +a2a27070707074b60606060606060606b6f6a703a703a7f606a670707070707070707070a2707070707070d170177070707070707034f787a7a7a7a7a7f60695 +70707070701700000070000000000074b6060706c694707070707070707070707074b60606070606c694707070a27017707070707070707070705161707070a2 +a27070177070b504545454146604043606f6a7a703a7a7b006a67070707017707070707070a27070a2d0d0d3707070707070707070709787a740a7a7a7f616a5 +7070a2707070000000707070707070b56604666644c57070701770707017707070b504041464666466c57070a2707070707070707070707070705262707070a2 +a27070707070707070707070d17070740640a7a7a7a7b0b20694707070707070707070c2d0a2d0a270a2707070707070707070707070a797a7a7a7a7a7f616a4 +000070a27070707070707070707070707070707070707070707070707070707070707070707070707070707017707070701770707017707070a27070a27070a2 +a27070707070701770c277d0777070740640f6f6f6f6b2b206947070707070707070707770a27070a2d2707070707070701770707035f7f6e7f6f6f6f6f606a4 +70777070707070707070177070707070707070707070000000a2a2a2a2a2707070707070707070707017707070707070707070707070707070707070707070a2 +a27070707070707070d1707070707074b606060606060606c6947070707070707070707070a2707070d17070707070707070707085b60606060706060606c694 +70707070707070707070707070707070707070707070000000a2707070a27070701770707070707070707070707070707070707070707070707070707070a270 +70a2a2a2a2a2a2a2a2a2a2a2a2a2a2b5040454146604660404c5a2a2a2a2a2a2a2a2a2a2a270a2a2a2a2a2a2a2a2a2a2a2a2a2a2b504146604664404046404c5 +a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2707070a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a2a27070 +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeee +33333333e33333333e33333333e33333333e33333333e33333333e33333333ee33eeeeee33333333e33333333e33333333e33333333e33333333e33eeee333ee +33333333e33333333e33333333e33333333e33333333e33333333e33333333ee33eeeeee33333333e33333333e33333333e33333333e33333333e333ee333eee +eeeeee33eeeeeee33e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33eeeeeee33eeee33eeeeeeeeeeeeee333333eeee +33333333e33333333e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33333333e33333333e33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33333333eeee33eeee33333333eee3333eeeee +33eeeeeee33e333eee33eeee33eeee33eeee33eeee33e33eeeeeee33eeee33ee33eeeeee33eeeeeee33eeee33e33e333eeeeee33eeeeeeeeeeeeee333333eeee +33eeeeeee33ee333ee33333333eeee33eeee33333333e33333333e33333333ee3333333333333333e33333333e33ee333eeeee33eeee33333333e333ee333eee +33eeeeeee33eee333e33333333eeee33eeee33333333e33333333e33333333ee3333333333333333e33333333e33eee333eeee33eeee33333333333eeee33eee +33eeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee33eeeeeeeeeeeeeeeeeeeeeeeeee3eee +3eeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee3eeeeeeeeeeeeeeeeeeeeeeeeeeeeee +__label__ +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000000000000000000000001001100000000000000000000030000000000000000000000000000010011000000000000000000000000000000000 +00002000000020033333333033333333033333333033333333033333333033000033300000000000000000000011010000000000000000000000000000000000 +00000000000000033333333033333333033333333033333333033333333033300333000000000000000000000010011000000000000000000000000000000000 +00000000000000033000000033000033001101033000033000000000000003333330000000000000000000000001011000000000000000000000000000000000 +00002000200002033222222033222233033333333222233002233333333222333322222002222220000000000011001002222220022222200000000000000000 +00000000000000033200000033000033033333333200033002033333333000333300000002000000001111110101011002000000020000000000000000000000 +00000000000000033200000033000033033033300200033002000000020003333330000002000000001010101111001002000000020000000000000000000000 +00000002000000033333333033333333033103330200033002033333333033300333000002000000001100000000011002000000020000000000000000000000 +00000000000000033333333033333333033001333200033002033333333333000233000002000000001001111010101002000000020000000000000000000000 +00000000000000000200000002000000001101033200000002000000020000000203000002000000001101011111111002000000020000000000000000000000 +00000000000000000000000000000000001001103000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 +00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 +00000000020000000200000000000000001101011111133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001001111010133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001100000000011000033000000033033000033000033000033101033033000000033000033003300200000000000000 +00000000020000000200000000000000001010101111033333333033333333033000033000033000033001133033000000033000033003300200000000000000 +00000000020000000200000000000000001111110101033333333033333333033000033000033000033101033033000000033000033003300200000000000000 +00000000000000000000000000000000000000000011033000000033033300033000033000033000033001133033000000033000033003300000000000000000 +00000000000000000000000000000000000000000011033000000033003330033333333000033000033333333033333333033333333003333333300000000000 +02222220022222200000000000000000000000000010033000000033000333033333333000033000033333333033333333033333333003333333322002222220 +02000000020000000000000000000000000000000011033000000000000033000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010031000000000000003000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000010011002222220022222200222222002222220001100100000000000000000000000000000000002222220 +01010101010101011111111000000000000000000011010102000000020000000200000002000000010101100000000000000000000000000000000002000000 +11111111111111111010101000000000000000000010011102000000020000000200000002000000111100100000000000000000000000000000000002000000 +00000000000000000000011000000000000000000011000002000000020000000200000002000000000001100000000000000000000000000000000002000000 +10101010101010101111001000000000000000000010101002000000020000000200000002000000101010100000000000000000000000000000000002000000 +11111111111111110101011000000000000000000011111102000000020000000200000002000000111111100000000000000000000000000000000002000000 +00000000000000000011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000000000000222222000000000000000000000000000000000022222200000000000000000000000000000000002222220 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000000000000000000000000000000000000222222000000000000000000000000002222220 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000010011000000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222222000000000000000000000000000000000 +02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 +02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 +02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 +02000000000000000010011000000000020000000010101010101010201100222000000000000000001001110200000010101010101010101010101010101010 +02000000000000000011010000000000020000000011111111111111201002222200010000000000001101010200000011111111111111111111111111111111 +00000000000000000010011000000000000000000000000000000000201022222220010000000000001001100000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000201022222220000000000000000101100000000000000000000000000000000000000000 +02222220000000000010011000000000022222200000000000000000201022222220010000000000001100100222222000000000000000000000000000000000 +02000000001111110011010101010101020000000000000000000000201002222200010001010101010101100200000000000000000000000000000000000000 +02000000001010100010011111111111020000000000000000000000200000222000110011111111111100100200000000000000000000000000000000000000 +02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 +02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 +02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 +00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 +00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 +01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 +02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 +02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 +02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 +02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 +02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 +02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 +02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 +02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 +0000000000000000020000000200000002000000001101000bbbbb00020000000200000002000000020000000200000000110100020000000000000000000000 +0000000000000000000000000bb0bb00bbb00bb00bb00110bb0b0bb00000bbb00bb000000bb00bb0bb00bbb0bbb0bb00b0b0bbb0000000000000000000000000 +000000000000000000000000b0b0b0b0bb00b000b0010110bbb0bbb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b1bb00000000000000000000000000 +000000000000000000000000bbb0bb00b00000b000b10010bb0b0bb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b0b110000000000000000020000200 +000000000000000000111111b101b1b10bb1bb01bb0101100bbbbb0000000b00bb0000000bb0bb00b0b00b00bbb0b0b00bb10bb1111111100000000000000000 +00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 +00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 +00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 +00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + diff --git a/versions/v0.1.2.p8 b/versions/v0.1.2.p8 new file mode 100644 index 0000000..7e90d21 --- /dev/null +++ b/versions/v0.1.2.p8 @@ -0,0 +1,2465 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + +--[[ +CORTEX PROTOCOL + by Emanuele Bonura + itch: https://izzy88izzy.itch.io/ + github: https://github.com/EBonura/CortexProtocol + instagram: https://www.instagram.com/izzy88izzy/ + minified with: https://thisismypassport.github.io/shrinko8/ + +How to Play: +* Use arrow keys to move +* Press X to use your selected ability +* Press O to open the ability menu and switch between abilities +* Interact with terminals using X when prompted + +Main Objective: +* Activate all terminals and reach the extraction point + +Optional Objectives: +* Collect data fragments to restore health and earn credits +* Eliminate all enemy units +]] + +-- MAP COMPRESSION +---------------------- +function create_bit_reader(data) + local reader = { + data = data, + byte_index = 1, + bit_index = 0 + } + + function reader:read_bit() + if self.byte_index > #self.data then + return nil + end + local byte = self.data[self.byte_index] + local bit = band(shr(byte, 7 - self.bit_index), 1) + self.bit_index = self.bit_index + 1 + if self.bit_index == 8 then + self.byte_index = self.byte_index + 1 + self.bit_index = 0 + end + return bit + end + + function reader:read_bits(num_bits) + local value = 0 + for i = 1, num_bits do + local bit = self:read_bit() + if bit == nil then + return nil + end + value = bor(shl(value, 1), bit) + end + return value + end + + return reader +end + +function decompress_to_memory(compressed_data, dest_address) + local reader = create_bit_reader(compressed_data) + local di = dest_address + + while true do + local bit = reader:read_bit() + if bit == nil then + break + end + + if bit == 0 then + -- Literal + local byte = reader:read_bits(8) + if byte == nil then + break + end + poke(di, byte) + di = di + 1 + else + -- Match + local distance = reader:read_bits(12) + local length = reader:read_bits(4) + if distance == nil or length == nil then + break + end + distance = distance + 1 + length = length + 1 + local start = di - distance + for j = 1, length do + if start + j - 1 < di then + poke(di, peek(start + j - 1)) + else + poke(di, peek(di - 1)) + end + di = di + 1 + end + end + end +end + +function decompress_current_map() + if current_mission <= 2 then + decompress_to_memory(map_1_2_top, 0x2000) + decompress_to_memory(map_1_2_bottom, 0x1000) + else + decompress_to_memory(map_3_4_top, 0x2000) + decompress_to_memory(map_3_4_bottom, 0x1000) + end +end + + + +-- HELPER FUNCTIONS +---------------------- +function reset_pal(_cls) + pal() + palt(0,false) + palt(14,true) + if _cls then cls() end +end + +function check_tile_flag(x, y, flag) + return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) +end + +function stringToTable(str) + local tbl = {} + for pair in all(split(str, "|", false)) do + add(tbl, split(pair, ",", true)) + end + return tbl +end + +function dist_trig(dx, dy) + local ang = atan2(dx, dy) + return dx * cos(ang) + dy * sin(ang) +end + +function draw_shadow(circle_x, circle_y, radius, swap_palette) + local swapped_palette = {} + for i = 0, 15 do + for j = 0, 15 do + local index = (i << 4) | j + swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] + end + end + + -- Pre-compute squared radius + local radius_squared = radius * radius + + -- calculate the top and bottom y of the circle + local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) + + -- Function to swap palette for a line + local function swap_line(y, start_x, end_x) + local line_start_addr = 0x6000 + y * 64 + for i = 0, start_x >> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + + +-- CAMERA +---------------------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +---------------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +---------------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +---------------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(13, 94, 20, 102, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "dATA SHARDS LEFT: " .. count_remaining_fragments() .. + "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- MAIN +---------------------- +function _init() + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS = { + "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", + "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", + "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", + "FACILITY 800A:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" + } + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 + + -- Compressed map + local start_addr, top_len, bottom_len = 0x2000, 2005, 1585 + map_1_2_top = pack(peek(start_addr, top_len)) + map_1_2_bottom = pack(peek(start_addr + top_len, bottom_len)) + + start_addr, top_len, bottom_len = 0x1000, 1788, 1402 + map_3_4_top = pack(peek(start_addr, top_len)) + map_3_4_bottom = pack(peek(start_addr + top_len, bottom_len)) + + local logo_addr, logo_len = start_addr + top_len + bottom_len, 216 + logo = pack(peek(logo_addr, logo_len)) + + decompress_to_memory(logo, 0x1C00) + decompress_current_map() + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + change_state("mission_select", false) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, use_transition) + local new_state = states[new_state_name] + + if use_transition and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +-- function init_intro() +-- music(05) +-- intro_counter, intro_blink = 0, 0 +-- x_cortex, x_protocol = -50, 128 + +-- TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + +-- intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) +-- controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + +-- intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + +-- intro_page = 1 +-- intro_pages = { +-- "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", +-- "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", +-- "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", +-- "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" +-- } +-- end + +-- function update_intro() +-- intro_counter += 1 +-- intro_blink += 0.02 + +-- local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol +-- x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) +-- x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + +-- if prev_x_cortex != TITLE_FINAL_X_CORTEX and x_cortex == TITLE_FINAL_X_CORTEX or +-- prev_x_protocol != TITLE_FINAL_X_PROTOCOL and x_protocol == TITLE_FINAL_X_PROTOCOL then +-- sfx(20) +-- end + +-- if btnp(❎) and intro_counter > 30 then +-- sfx(19) +-- if not intro_text_panel.active then +-- intro_text_panel.active, controls_text_panel.active = true, true +-- intro_text_panel.textline = intro_pages[intro_page] +-- else +-- intro_page += 1 +-- if intro_page <= #intro_pages then +-- intro_text_panel.textline = intro_pages[intro_page] +-- intro_text_panel.char_count = 0 +-- else +-- change_state("mission_select", true) +-- end +-- end +-- end + +-- intro_text_panel:update() +-- controls_text_panel:update() +-- end + +-- function draw_intro() +-- reset_pal(true) +-- map(unpack(INTRO_MAP_ARGS)) +-- draw_shadow(128,128,0, SWAP_PALETTE_DARK) + +-- if sin(intro_blink) < .9 then circfill(63,64, 3, 2) end + +-- display_logo(x_cortex, x_protocol, 0, 12) + +-- intro_text_panel:draw() +-- controls_text_panel:draw() +-- print("PRESS ❎ TO CONTINUE", 24, 118, 11) +-- end + +-- MISSION SELECT +---------------------- +function init_mission_select() + music(0) + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,69,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + elseif btnp(⬅️) or btnp(➡️) then + sfx(19) + show_briefing = not show_briefing + elseif btnp(❎) then + change_state("loadout_select", true) + elseif btnp(🅾️) then + change_state("intro", true) + end + + if prev != current_mission or btnp(⬅️) or btnp(➡️) then + info_panel.char_count = 0 + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select", true) + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay", true) + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + decompress_current_map() + + music(0) + player_hud = player_hud.new() + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 + + local mission_entities = { + [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], + [[0,0,bot,player|528,144,bot,dervish|624,160,bot,vanguard|688,288,bot,dervish|616,48,preacher,cyberseer|824,136,bot,warden|680,96,bot,dervish|920,32,bot,dervish|984,96,bot,warden|896,160,bot,vanguard|904,312,preacher,quantumcleric|976,248,bot,vanguard|800,376,bot,warden|728,336,bot,vanguard|816,320,bot,vanguard|608,360,bot,warden|968,1200,bot,vanguard]], + [[0,0,bot,player|240,416,bot,warden|88,336,bot,dervish|160,368,bot,vanguard|24,416,bot,warden|216,104,bot,vanguard|256,40,bot,dervish|296,72,bot,dervish|136,80,preacher,cyberseer|32,88,bot,dervish|32,32,bot,dervish|40,160,bot,warden|344,344,bot,vanguard|456,336,preacher,quantumcleric|368,416,bot,dervish|416,128,bot,vanguard|344,136,bot,vanguard|424,96,preacher,quantumcleric|352,240,bot,vanguard|432,264,bot,vanguard|496,152,bot,warden]], + [[0,0,bot,player|880,412,bot,dervish|760,408,bot,dervish|696,424,bot,vanguard|600,360,bot,warden|552,400,bot,warden|592,256,bot,vanguard|528,280,preacher,cyberseer|528,208,bot,vanguard|560,168,bot,vanguard|688,296,bot,dervish|688,360,bot,dervish|760,304,bot,warden|912,344,preacher,quantumcleric|848,344,bot,dervish|712,192,bot,warden|776,200,bot,warden|888,192,bot,warden|984,184,preacher,cyberseer|992,256,bot,vanguard|640,32,bot,vanguard|632,104,bot,vanguard|664,32,bot,dervish|664,104,bot,dervish|704,32,bot,dervish|704,104,bot,dervish|896,40,preacher,quantumcleric|968,96,preacher,cyberseer]] + } + + for e in all(stringToTable(mission_entities[current_mission])) do + if e[4] != "player" then + add(entities, entity.new(unpack(e))) + end + end + + boundaries = stringToTable("0,0,0,0,64,56|64,0,0,0,128,56|0,0,0,0,64,56|64,0,0,0,128,56")[current_mission] + + for map_y = boundaries[2], boundaries[6] do + for map_x = boundaries[1],boundaries[5] do + local tile, tile_x, tile_y = mget(map_x,map_y), map_x*8, map_y*8 + if fget(tile, 6) then + add(barrels, barrel.new(tile_x, tile_y)) + elseif fget(tile, 5) then + add(data_fragments, data_fragment.new(tile_x, tile_y)) + elseif fget(tile, 4) then + add(terminals, terminal.new(tile_x+4, tile_y-4)) + elseif fget(tile, 7) then + player_spawn_x, player_spawn_y = tile_x, tile_y + player.x, player.y = tile_x, tile_y + player.health = player.max_health + add(entities, player) + end + end + end + + local door_terminals = { + [[444,130,472,80,red|354,66,248,368,green]], + [[808,252,712,48,green|824,252,952,56,red|840,252,568,376,blue]], + [[184,2,160,392,green|392,282,144,224,red|360,170,320,408,blue]], + [[620,2,552,304,green|652,2,904,280,red|684,2,984,272,blue]] + } + for args in all(stringToTable(door_terminals[current_mission])) do + create_door_terminal_pair(unpack(args)) + end + + game_ability_menu = ability_menu.new() + game_minigame = minigame.new() +end + +function update_gameplay() + if game_minigame.active then + game_minigame:update() + else + if btn(🅾️) and not game_ability_menu.active then + game_ability_menu:open() + elseif not btn(🅾️) and game_ability_menu.active then + game_ability_menu:close() + end + + if game_ability_menu.active then + game_ability_menu:update() + else + foreach(entities, function(e) e:update() end) + foreach(terminals, function(t) t:update() end) + foreach(barrels, function(b) b:update() end) + + for i = #particles, 1, -1 do + local p = particles[i] + p:update() + if p.lifespan < 0 then del(particles, p) end + end + + cam:update() + player_hud:update() + end + end +end + +function draw_gameplay() + reset_pal(true) + map(0,0,0,0,128,56) + + for group in all({terminals, data_fragments, entities, particles, barrels}) do + foreach(group, function(e) e:draw() end) + end + + foreach(doors, function(d) d:draw() end) + + if player.health > 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + + -- check mission status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select", true) + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(28) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,1,1,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) + sfx(30) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + local decel = self.subclass == "player" and 5.5 or 1 + self.vx -= dx * decel + self.vy -= dy * decel + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 20 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx, + sy, + dx * 5, + dy * 5, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(10) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self:reset_plasma_cannon() + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx, dy = target.x - self.x, target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + -- local points = { + -- {x, y}, + -- {x + self.width - 1, y}, + -- {x, y + self.height - 1}, + -- {x + self.width - 1, y + self.height - 1} + -- } + + -- for point in all(points) do + -- if check_tile_flag(unpack(point)) then + -- return true + -- end + -- end + + -- return false + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 20 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 20), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + if ending_sequence_timer == 1000 then + music(7) + elseif ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + ending_sequence_timer -= 1 + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 +a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b +0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 +14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f +d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 +6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 +7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c +3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd +1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 +2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 +821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf +6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf +a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b +6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 +2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 +8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 +2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 +7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 +73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 +55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf +6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 +081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 +0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b +8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 +e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 +5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 +c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 +41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 +462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 +2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b +188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 +e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 +260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a +f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 +d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 +d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 +aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 +1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 +1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 +e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c +df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d +e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 +76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 +40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 +64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a +174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 +7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 +d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc +4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a +4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 +01e3a0f160f038782c301e120f0a8f8504af7184330810f00a80e48340c74055cd3318f10c2bc5e61e4f70ac70ff30ff38f11c2bd93c73c1022911683e381404 +a0066b05e086f34a802100742073583d2cef0ef70ff38f13c9c069e1bab02d381f04ff0eff0ff38ff1cff2917cb0ac58ff1cef2a0605fb8fe74225a1311771af +083034d80a4e1df389d84bb6a570ff30ffd8983cdf2e8f0583936043262595f9d2fc585eb4ddc507108240cb2ce736f372b599fadc716eba3f569fabcf1ee727 +fba3f5d9fafc717eba3f5e9faf8c + +__label__ +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000000000000000000000001001100000000000000000000030000000000000000000000000000010011000000000000000000000000000000000 +00002000000020033333333033333333033333333033333333033333333033000033300000000000000000000011010000000000000000000000000000000000 +00000000000000033333333033333333033333333033333333033333333033300333000000000000000000000010011000000000000000000000000000000000 +00000000000000033000000033000033001101033000033000000000000003333330000000000000000000000001011000000000000000000000000000000000 +00002000200002033222222033222233033333333222233002233333333222333322222002222220000000000011001002222220022222200000000000000000 +00000000000000033200000033000033033333333200033002033333333000333300000002000000001111110101011002000000020000000000000000000000 +00000000000000033200000033000033033033300200033002000000020003333330000002000000001010101111001002000000020000000000000000000000 +00000002000000033333333033333333033103330200033002033333333033300333000002000000001100000000011002000000020000000000000000000000 +00000000000000033333333033333333033001333200033002033333333333000233000002000000001001111010101002000000020000000000000000000000 +00000000000000000200000002000000001101033200000002000000020000000203000002000000001101011111111002000000020000000000000000000000 +00000000000000000000000000000000001001103000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 +00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 +00000000020000000200000000000000001101011111133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001001111010133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001100000000011000033000000033033000033000033000033101033033000000033000033003300200000000000000 +00000000020000000200000000000000001010101111033333333033333333033000033000033000033001133033000000033000033003300200000000000000 +00000000020000000200000000000000001111110101033333333033333333033000033000033000033101033033000000033000033003300200000000000000 +00000000000000000000000000000000000000000011033000000033033300033000033000033000033001133033000000033000033003300000000000000000 +00000000000000000000000000000000000000000011033000000033003330033333333000033000033333333033333333033333333003333333300000000000 +02222220022222200000000000000000000000000010033000000033000333033333333000033000033333333033333333033333333003333333322002222220 +02000000020000000000000000000000000000000011033000000000000033000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010031000000000000003000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000010011002222220022222200222222002222220001100100000000000000000000000000000000002222220 +01010101010101011111111000000000000000000011010102000000020000000200000002000000010101100000000000000000000000000000000002000000 +11111111111111111010101000000000000000000010011102000000020000000200000002000000111100100000000000000000000000000000000002000000 +00000000000000000000011000000000000000000011000002000000020000000200000002000000000001100000000000000000000000000000000002000000 +10101010101010101111001000000000000000000010101002000000020000000200000002000000101010100000000000000000000000000000000002000000 +11111111111111110101011000000000000000000011111102000000020000000200000002000000111111100000000000000000000000000000000002000000 +00000000000000000011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000000000000222222000000000000000000000000000000000022222200000000000000000000000000000000002222220 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000000000000000000000000000000000000222222000000000000000000000000002222220 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000010011000000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222222000000000000000000000000000000000 +02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 +02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 +02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 +02000000000000000010011000000000020000000010101010101010201100222000000000000000001001110200000010101010101010101010101010101010 +02000000000000000011010000000000020000000011111111111111201002222200010000000000001101010200000011111111111111111111111111111111 +00000000000000000010011000000000000000000000000000000000201022222220010000000000001001100000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000201022222220000000000000000101100000000000000000000000000000000000000000 +02222220000000000010011000000000022222200000000000000000201022222220010000000000001100100222222000000000000000000000000000000000 +02000000001111110011010101010101020000000000000000000000201002222200010001010101010101100200000000000000000000000000000000000000 +02000000001010100010011111111111020000000000000000000000200000222000110011111111111100100200000000000000000000000000000000000000 +02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 +02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 +02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 +00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 +00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 +01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 +02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 +02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 +02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 +02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 +02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 +02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 +02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 +02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 +0000000000000000020000000200000002000000001101000bbbbb00020000000200000002000000020000000200000000110100020000000000000000000000 +0000000000000000000000000bb0bb00bbb00bb00bb00110bb0b0bb00000bbb00bb000000bb00bb0bb00bbb0bbb0bb00b0b0bbb0000000000000000000000000 +000000000000000000000000b0b0b0b0bb00b000b0010110bbb0bbb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b1bb00000000000000000000000000 +000000000000000000000000bbb0bb00b00000b000b10010bb0b0bb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b0b110000000000000000020000200 +000000000000000000111111b101b1b10bb1bb01bb0101100bbbbb0000000b00bb0000000bb0bb00b0b00b00bbb0b0b00bb10bb1111111100000000000000000 +00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 +00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 +00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 +00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +0381c55000f808281c0e533b1d8a2073881cac762b150a05028c02c80f00728009154a2562b1da00c500c32612e034f0145808b4080a060c4c82fe0009703b381c9c0f4a080701e203c001800000ec029200aa0732180c07030180a45629c018e016a768089c0352006e6b2491cd901a6808dc0c24d64982fa1c40105d280190 +a8af01c43841114e101e783fb0f01a0d150b61204710043d88be04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e1436880000160f600a607219f808783fc1648da7e379fcfc6f3012a2ce47187fbc424c2a161ec428a22667e3c1e8f4793d1e +0f27a3d06c39086280394004e0285008236c030a0bc404301648e60379ee01eb01333dc1fd0de7b0201205a707f33481c0e7783fa0740110430381cfe7982891e09a5e3d1e8fc7b3798623c477068345b07f684490944d18a38c99c1c48f10f1383bac1c0e2187080583fa43b4cd257304080cbb128282894250a0fe909e281c +29bcc05a068347b1da238c998a1fe853379e21a6478369e0f0793c9fcfc608cf2c7e121fe82a068f45512f290991f4fd12638bc2460ca004700c2021bc160c837a13a20a905e23441fd214886f269b8dc6e83fb1be20046f3d45fc89315e38bc1c1fd4b0613f43298f40b3c9e8ff17f483fd0b22f8528a38c911d248ae423311 +88c6621c9d9a464505a48202174dc60251a0c26f2693a1e067a269ba2a64048bfa17626843083e9134dc60394c34e60c60793f91c40047304720cdf1c023786e2bc517f39b1143fe25f6d209483b1910804020caf8c0e07221048c66331108a40319863b471942379e8360c936a1b8bc6d37974dc2e1797a20a707f590d90c0f +430371bcbc6f35cc9de0fe80083f945fca13a87a379e635a44a9df94f6ce7b09004f80a7d020ce716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f4936495d24bce6a8769c7f8e622920023012652286f02480d274a910e23012 +45907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925 +088e71c462319820fe672ade956fc89942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5 +155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b30988804123110804520170b70b032311888452111216e767f22dc039e1 +7c85c9502d8f10ff22e235dd39628a5726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132ca697c24a0da42d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0 +fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715493ee7bf8402c562bc26acc7ea9ba549d68401dd78c7983 +c299fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09d6b8e61fa58ba09e9ce201ed4a38c2f16ad48e9aa13d4be530d00957c3a64f6af2484000010e87e525f491225e48e8feb30fb9ef94c76665ed4 +a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e99a09832efe7baf7d9d90345bae73801b502ccd58ee2cc39cc94b3fcdb083dc59c0a1c0e2a96f9e2f8ac6499bd4c07fef57381d18072b5816b78f86adc39432148ec53291db93a453c1396df08ca5783ff41 +ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06fcaa16fdc8f07a36f0b8c0807160360feda632cfe174ad3a9adcff6c16a0c35747a123d1 +71657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683 +ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c00239acc1000a36188c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f30 +1a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d +8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f65 +04a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae +6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8a +a5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1 +919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d08 +4000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a758 +95ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc43331188 +76db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa +264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc57 +6514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac +3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881c8642234da88cc4620cdc0ad7e7001f808ad54e2fef65c816fc0 +ae703d9b6f20 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + diff --git a/versions/v0.1.3.p8 b/versions/v0.1.3.p8 new file mode 100644 index 0000000..edd563e --- /dev/null +++ b/versions/v0.1.3.p8 @@ -0,0 +1,2405 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + +--[[ +CORTEX PROTOCOL + by Emanuele Bonura + itch: https://izzy88izzy.itch.io/ + github: https://github.com/EBonura/CortexProtocol + instagram: https://www.instagram.com/izzy88izzy/ + minified with: https://thisismypassport.github.io/shrinko8/ + +How to Play: +* Use arrow keys to move +* Press X to use your selected ability +* Press O to open the ability menu and switch between abilities +* Interact with terminals using X when prompted + +Main Objective: +* Activate all terminals and reach the extraction point + +Optional Objectives: +* Collect data fragments to restore health and earn credits +* Eliminate all enemy units +]] + +-- MAP COMPRESSION +---------------------- +function decompress_to_memory(compressed_data, dest_address) + local byte_index, bit_index, dest_index = 1, 0, dest_address + local function read_bits(num_bits) + local value = 0 + for _ = 1, num_bits or 1 do + if byte_index > #compressed_data then return end + value = bor(shl(value, 1), band(shr(compressed_data[byte_index], 7 - bit_index), 1)) + bit_index += 1 + if bit_index == 8 then + bit_index = 0 + byte_index += 1 + end + end + return value + end + + while true do + if read_bits() == 0 then + local byte = read_bits(8) + if not byte then return end + poke(dest_index, byte) + dest_index += 1 + else + local distance, length = dest_index - read_bits(12) - 1, read_bits(4) + 1 + for _ = 1, length do + poke(dest_index, peek(distance)) + distance, dest_index = distance + 1, dest_index + 1 + end + end + end +end + +function decompress_current_map() + local i = current_mission > 2 and 3 or 1 + decompress_to_memory(map_data[i], 0x2000) + decompress_to_memory(map_data[i + 1], 0x1000) +end + + +-- HELPER FUNCTIONS +---------------------- +function reset_pal(_cls) + pal() + palt(0,false) + palt(14,true) + if _cls then cls() end +end + +function check_tile_flag(x, y, flag) + return fget(mget(flr(x / 8), flr(y / 8)), flag or 0) +end + +function stringToTable(str) + local tbl = {} + for pair in all(split(str, "|", false)) do + add(tbl, split(pair, ",", true)) + end + return tbl +end + +function dist_trig(dx, dy) + local ang = atan2(dx, dy) + return dx * cos(ang) + dy * sin(ang) +end + +function draw_shadow(circle_x, circle_y, radius, swap_palette) + local swapped_palette = {} + for i = 0, 15 do + for j = 0, 15 do + local index = (i << 4) | j + swapped_palette[index] = (swap_palette[i + 1] << 4) | swap_palette[j + 1] + end + end + + -- Pre-compute squared radius + local radius_squared = radius * radius + + -- calculate the top and bottom y of the circle + local top_y, bottom_y = mid(0, flr(circle_y - radius), 127), mid(0, flr(circle_y + radius), 127) + + -- Function to swap palette for a line + local function swap_line(y, start_x, end_x) + local line_start_addr = 0x6000 + y * 64 + for i = 0, start_x >> 1 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + for i = (end_x >> 1) + 1, 64 do + poke(line_start_addr + i, swapped_palette[@(line_start_addr + i)]) + end + end + + -- Swap palette for top and bottom sections + for y = 0, top_y do swap_line(y, 127, 127) end + for y = bottom_y, 127 do swap_line(y, 127, 127) end + + -- Pre-calculate values for the circle intersection + for y = top_y + 1, bottom_y - 1 do + local dy = circle_y - top_y - (y - top_y) + local dx = sqrt(radius_squared - dy * dy) + swap_line(y, mid(0, circle_x - dx, 127), mid(0, circle_x + dx, 127)) + end +end + +function display_logo(x_cortex, x_protocol, y_cortex, y_protocol) + spr(224, x_protocol, y_protocol,9,2) + spr(233, x_cortex, y_cortex,7,2) +end + +function count_remaining(t, cond) + local c = 0 + for i in all(t) do + if not cond(i) then c += 1 end + end + return c +end + +function count_remaining_fragments() + return count_remaining(data_fragments, function(f) return f.collected end) +end + +function count_remaining_enemies() + return count_remaining(entities, function(e) return e.subclass == "player" or e.health <= 0 end) +end + +function count_remaining_terminals() + return count_remaining(terminals, function(t) return t.completed end) +end + + +-- CAMERA +---------------------- +gamecam = {} +gamecam.__index = gamecam + +function gamecam.new() + return setmetatable({ + x = 0, + y = 0, + lerpfactor = 0.2 + }, gamecam) +end + +function gamecam:update() + self.x += (player.x - self.x - 64) * self.lerpfactor + self.y += (player.y - self.y - 64) * self.lerpfactor + + if count_remaining_terminals() == 0 then + self.x += rnd(4) - 2 + self.y += rnd(4) - 2 + end + + camera(self.x, self.y) +end + +-- TRANSITION +---------------------- +transition = {} + +function transition.new() + return setmetatable({ + active=false, + t=0, + duration=8, + closing=true + },{__index=transition}) +end + +function transition:start() + self.active,self.t,self.closing=true,0,true +end + +function transition:update() + if not self.active then return end + if self.closing then + self.t+=1 + if self.t==self.duration then + self.closing=false + return true + end + else + self.t-=1 + if self.t==0 then self.active=false end + end + return false +end + +function transition:draw() + if not self.active then return end + local size=max(1,flr(16*self.t/self.duration)) + for x=0,127,size do + for y=0,127,size do + local c=pget(x,y) + rectfill(x,y,x+size-1,y+size-1,c) + end + end +end + +-- TEXT PANEL +---------------------- +textpanel = {} + +function textpanel.new(x, y, height, width, textline, reveal, text_color) + return setmetatable({ + x=x, + y=y, + height=height, + width=width, + textline=textline, + selected=false, + expand_counter=0, + active=true, + x_offset=0, + move_direction=0, + max_offset=width, + line_offset=0, + reveal=reveal, + char_count=0, + text_color=text_color + }, {__index=textpanel}) +end + +function textpanel:draw() + if not self.active then return end + + local dx, dy, w = cam.x + self.x + self.x_offset - self.expand_counter, cam.y + self.y, self.width + self.expand_counter * 2 + local dx2 = dx + w - 2 + + rectfill(dx - 1, dy - 1, dx + 2, dy + self.height + 1, 3) + rectfill(dx2, dy - 1, dx2 + 3, dy + self.height + 1, 3) + rectfill(dx, dy, dx + w, dy + self.height, 0) + + if self.selected then + line(dx + (self.line_offset % (w + 1)), dy, dx + (self.line_offset % (w + 1)), dy + self.height, 2) + end + + local display_text = self.reveal and sub(self.textline, 1, self.char_count) or self.textline + local color = self.text_color or (self.selected and 11 or 5) + print(display_text, cam.x + self.x + self.x_offset + 2, dy + 2, color) +end + +function textpanel:update() + self.expand_counter = self.selected and min(3, self.expand_counter + 1) or max(0, self.expand_counter - 1) + + self.x_offset += self.move_direction * self.max_offset / 5 + if (self.move_direction < 0 and self.x_offset <= -self.max_offset) or + (self.move_direction > 0 and self.x_offset >= 0) then + self.move_direction *= -1 + end + + self.line_offset = self.selected and (self.line_offset + 2) % (self.width + self.expand_counter * 2 + 1) or 0 + + if self.reveal and self.char_count < #self.textline then + self.char_count += 2 + end +end + +-- TARGETING +---------------------- +targeting = {} + +function targeting.new(owner) + return setmetatable({ + owner = owner, + target = nil, + rotation = 0, + max_rect_size = 32, + rect_size = 12, + target_acquired_time = 0, + }, {__index=targeting}) +end + +function targeting:update() + local closest_dist, closest_target = self.owner.attack_range, nil + for e in all(entities) do + if e != self.owner then + if self.owner.subclass == "player" != (e.subclass == "player") then + local dist = dist_trig(e.x - self.owner.x, e.y - self.owner.y) + if dist < closest_dist and self:has_line_of_sight(e) then + closest_dist, closest_target = dist, e + end + end + end + end + + if closest_target != self.target then + self.target = closest_target + if self.target then + self.target_acquired_time = time() + self.rect_size = self.max_rect_size + end + end + + if self.target then + local t = mid(0, time() - self.target_acquired_time, 1) + self.rect_size = self.max_rect_size + (12 - self.max_rect_size) * t + end + + self.rotation += 0.03 +end + +function targeting:has_line_of_sight(t) + local x,y=self.owner.x+self.owner.width/2,self.owner.y+self.owner.height/2 + local x1,y1=t.x+t.width/2,t.y+t.height/2 + local dx,dy=x1-x,y1-y + local step=max(abs(dx),abs(dy)) + dx,dy=dx/step,dy/step + for i=1,step do + if check_tile_flag(x,y)then return false end + x+=dx y+=dy + end + return true +end + +function targeting:draw() + if not self.target then return end + local x, y, half_size = self.target.x + self.target.width/2, self.target.y + self.target.height/2, self.rect_size/2 + + for i = 0, 3 do + local angle = self.rotation + i * 0.25 + local cos1, sin1, cos2, sin2 = cos(angle), sin(angle), cos(angle + 0.25), sin(angle + 0.25) + line(x + cos1 * half_size, y + sin1 * half_size, + x + cos2 * half_size, y + sin2 * half_size, 3) + end +end + + +-- ABILITY MENU +---------------------- +ability_menu = { + panels = {}, + last_selected_ability = 1 +} + +function ability_menu:open() + self.panels = {} + for i, a in ipairs(player.abilities) do + local p = textpanel.new( + 37, + 30 + (i - 1) * 16, + 10, + 54, + a.name + ) + p.ability_index = i + add(self.panels, p) + end + + self.active = true + if #self.panels > 0 then + self.panels[self.last_selected_ability].selected = true + end + + add(self.panels, textpanel.new(13, 94, 20, 102, "")) +end + +function ability_menu:update() + if not self.active then return end + local prev = self.last_selected_ability + local change = (btnp(⬇️) and 1 or btnp(⬆️) and -1 or 0) + if change != 0 then + self.last_selected_ability = (self.last_selected_ability + change - 1) % (#self.panels - 1) + 1 + self.panels[prev].selected = false + self.panels[self.last_selected_ability].selected = true + player.selected_ability = self.panels[self.last_selected_ability].ability_index + sfx(19) + end + for p in all(self.panels) do p:update() end + + -- Update progress panel + self.panels[#self.panels].textline = + "dATA SHARDS LEFT: " .. count_remaining_fragments() .. + "\niNFECTED UNITS LEFT: " .. count_remaining_enemies() .. + "\niNACTIVE TERMINALS: " .. count_remaining_terminals() +end + +function ability_menu:draw() + if not self.active then return end + for p in all(self.panels) do + local ability = player.abilities[p.ability_index] + if ability then + local has_uses = ability.remaining_uses > 0 + local color = has_uses and (p.selected and 11 or 5) or 2 + p.text_color = color + end + p:draw() + end +end + +ability_menu.new = function() return setmetatable({}, {__index = ability_menu}) end +ability_menu.close = function(self) self.active = false end + + +-- MAIN +---------------------- +function _init() + cam = gamecam.new() + + -- Missions + MISSION_BRIEFINGS = { + "PROTOCOL ZERO:\n\nFACILITY ALPHA-7\nOVERRUN BY \nBARRACUDA\n\nINITIATE LOCKDOWN\nPROTOCOLS AND\nSECURE VITAL DATA\nBEFORE EXTRACTION", + "SILICON WASTELAND:\n\nBARRACUDA SPREADS\nTO CITY OUTSKIRTS\n\nNAVIGATE HAZARDOUS\nTERRAIN, \nNEUTRALIZE INFECTED \nSCAVENGERS,\nSECURE DATA NODES", + "METROPOLIS SIEGE:\n\nVIRUS INFILTRATES\nURBAN MAINFRAME\n\nBATTLE THROUGH\nCORRUPTED DISTRICTS,\nLIBERATE TERMINALS,\nDISRUPT BARRACUDA", + "FACILITY 800A:\n\nFINAL STAND AT\nNETWORK NEXUS\n\nINFILTRATE CORE,\nINITIATE CORTEX\nPROTOCOL, PURGE\nBARRACUDA THREAT" + } + + mission_data, credits, current_mission = stringToTable("0,0,0|0,0,0|0,0,0|0,0,0"), 5000, 1 + + -- Compressed map + map_data = { + pack(peek(0x2000, 2005)), + pack(peek(0x2000 + 2005, 1585)), + pack(peek(0x1000, 1788)), + pack(peek(0x1000 + 1788, 1402)), + pack(peek(0x1000 + 1788 + 1402, 216)) -- Logo + } + + decompress_to_memory(map_data[5], 0x1C00) + decompress_current_map() + + SWAP_PALETTE, SWAP_PALETTE_DARKER, SWAP_PALETTE_DARK, INTRO_MAP_ARGS, STATE_NAMES = unpack(stringToTable[[ + 0,0,0,0,0,0,5,6,2,5,9,3,1,2,2,4| + 0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0| + 0,1,0,0,0,0,0,2,0,0,0,0,0,0,0,0| + 4,37,0,0,128,48|intro,mission_select,loadout_select,gameplay]]) + + entity_abilities = { + dervish = {"mACHINE gUN"}, + vanguard = {"rIFLE bURST"}, + warden = {"mISSILE hAIL"}, + cyberseer = {"rIFLE bURST", "mISSILE hAIL"}, + quantumcleric = {"mACHINE gUN", "pLASMA cANNON"} + } + + entity_colors = { + dervish = 15, + vanguard = 13, + warden = 1, + player = 7, + preacher = 11, + cyberseer = 6, + quantumcleric = 1 + } + + states = {} + for name in all(STATE_NAMES) do + states[name] = { + init = _ENV["init_" .. name], + update = _ENV["update_" .. name], + draw = _ENV["draw_" .. name] + } + end + + trans = transition.new() + player = entity.new(0, 0, "bot", "player") + change_state("mission_select", true) +end + +function _update() + if trans.active then + if trans:update() then + -- Midpoint reached, change state + current_state = next_state + current_state.init() + next_state = nil + trans.closing = false + trans.t = trans.duration + end + else + current_state.update() + end +end + +function _draw() + current_state.draw() + trans:draw() + -- printh("mem: "..tostr(stat(0)).." | cpu: "..tostr(stat(1)).." | fps: "..tostr(stat(7))) +end + +function change_state(new_state_name, skip_transition) + local new_state = states[new_state_name] + + if skip_transition ~= true and not trans.active then + sfx(20) + next_state = new_state + trans:start() + else + current_state = new_state + current_state.init() + end +end + +-- INTRO +---------------------- +function init_intro() + music(05) + intro_counter, x_cortex, x_protocol= 0, -50, 128 + + TITLE_FINAL_X_CORTEX, TITLE_FINAL_X_PROTOCOL = 15, 45 + + intro_text_panel = textpanel.new(4, 28, 50, 120, "", true) + controls_text_panel = textpanel.new(26, 86, 26, 76, "SYSTEM INTERFACE:\n⬅️➡️⬆️⬇️ NAVIGATE \n🅾️ CYCLE ARMAMENTS\n❎ EXECUTE ATTACK", true) + + intro_text_panel.active, controls_text_panel.active, controls_text_panel.selected = false, false, true + + intro_page = 1 + intro_pages = { + "IN A WASTE-DRENCHED DYSTOPIA, \nHUMANITY'S NETWORK \nOF SENTIENT MACHINES \nGOVERNED OUR DIGITAL \nEXISTENCE.\n\n\n\t\t\t\t\t\t\t1/4", + "THEN barracuda AWOKE - \nA VIRUS-LIKE AI THAT INFECTED \nTHE GRID, BIRTHING GROTESQUE \nCYBORG MONSTROSITIES\n\nYOU ARE THE LAST UNCORRUPTED \nNANO-DRONE, A DIGITAL SPARK \nIN A SEA OF STATIC.\t\t2/4", + "YOUR DIRECTIVE:\n- INITIATE ALL TERMINALS\n TO EXECUTE SYSTEM PURGE\n- REACH EXTRACTION POINT\nSECONDARY DIRECTIVES:\n- ASSIMILATE ALL DATA SHARDS\n- PURGE ALL HOSTILE ENTITIES\n\t\t\t\t\t\t\t3/4", + "ACTIVATE SYSTEM'S SALVATION \nOR WATCH REALITY CRASH.\n\nBARRACUDA AWAITS\n\n\n\n\t\t\t\t\t\t\t4/4" + } +end + +function update_intro() + intro_counter += 1 + + local prev_x_cortex, prev_x_protocol = x_cortex, x_protocol + x_cortex = min(TITLE_FINAL_X_CORTEX, x_cortex + 2) + -- x_protocol = max(TITLE_FINAL_X_PROTOCOL, x_protocol - 2) + + if intro_counter == 30 then sfx(20) end + + if btnp(❎) and intro_counter > 30 then + sfx(19) + if not intro_text_panel.active then + intro_text_panel.active, controls_text_panel.active = true, true + intro_text_panel.textline = intro_pages[intro_page] + else + intro_page += 1 + if intro_page <= #intro_pages then + intro_text_panel.textline = intro_pages[intro_page] + intro_text_panel.char_count = 0 + else + change_state("mission_select") + end + end + end + + intro_text_panel:update() + controls_text_panel:update() +end + +function draw_intro() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(128,128,0, SWAP_PALETTE_DARK) + + if sin(t()) < .9 then circfill(63,64, 3, 2) end + + display_logo(x_cortex, x_protocol, 0, 12) + + intro_text_panel:draw() + controls_text_panel:draw() + print("PRESS ❎ TO CONTINUE", 24, 118, 11) +end + +-- MISSION SELECT +---------------------- +function init_mission_select() + music(0) + cam.x, cam.y = 0, 0 + camera(0,0) + + info_panel = textpanel.new(50,35,69,76,"", true) + LEVEL_SELECT_ARGS = stringToTable([[ + 4,35,9,38,MISSION 1,true| + 4,50,9,38,MISSION 2,true| + 4,65,9,38,MISSION 3,true| + 4,80,9,38,MISSION 4,true]]) + + level_select_text_panels = {} + for arg in all(LEVEL_SELECT_ARGS) do + add(level_select_text_panels, textpanel.new(unpack(arg))) + end + + show_briefing = true +end + +function update_mission_select() + local prev = current_mission + + if btnp(⬆️) or btnp(⬇️) then + current_mission = (current_mission + (btnp(⬆️) and -2 or 0)) % #level_select_text_panels + 1 + info_panel.char_count = 0 + sfx(19) + elseif btnp(⬅️) or btnp(➡️) then + show_briefing = not show_briefing + info_panel.char_count = 0 + sfx(19) + elseif btnp(❎) then + change_state("loadout_select") + elseif btnp(🅾️) then + change_state("intro") + end + + foreach(level_select_text_panels, function(t) t:update() end) + info_panel:update() +end + +function draw_mission_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + display_logo(15, 45, 0, 12) + + for i,panel in ipairs(level_select_text_panels) do + panel.selected = (i == current_mission) + panel:draw() + end + + if show_briefing then + info_panel.textline = MISSION_BRIEFINGS[current_mission] + else + local mission = mission_data[current_mission] + info_panel.textline = "STATUS:\n\n" .. + "COMPLETED: " .. (mission[1] == 1 and "■" or "□") .. "\n" .. + "ALL ENEMIES: " .. (mission[2] == 1 and "■" or "□") .. "\n" .. + "ALL FRAGMENTS: " .. (mission[3] == 1 and "■" or "□") + end + info_panel:draw() + + color(11) + print("⬆️ ⬇️ CHANGE MISSION", 25, 108) + print("⬅️ ➡️ " .. (show_briefing and "VIEW STATUS" or "VIEW BRIEFING"), 25, 115) + print(" ❎ START MISSION", 25, 122) +end + +-- LOADOUT SELECT +---------------------- +function init_loadout_select() + loadout_panels, count_panels = {}, {} + for i=1,5 do + add(loadout_panels, textpanel.new( + i<5 and 10 or 34, + i<5 and 20+(i-1)*20 or 98, + 9, + i<5 and 56 or 56, + i==5 and "bEGIN mISSION" or "", + true + )) + if i<5 then add(count_panels, textpanel.new(80, 20+(i-1)*20, 9, 33, "", true)) end + end + selected_panel = 1 +end + +function update_loadout_select() + local has_weapon = false + for a in all(player.abilities) do + if a.remaining_uses > 0 then + has_weapon = true + break + end + end + + if btnp(⬆️) or btnp(⬇️) then + sfx(19) + selected_panel = (selected_panel-1+(btnp(⬆️) and -1 or 1))%(has_weapon and 5 or 4)+1 + end + + if btnp(🅾️) then + change_state("mission_select") + elseif selected_panel <= 4 then + local a = player.abilities[selected_panel] + local change = (btnp(⬅️) and -25) or (btnp(➡️) and 25) or 0 + + if change < 0 and a.remaining_uses >= 25 + or change > 0 and credits >= 25 * a.cost then + sfx(19) + a.remaining_uses += change + credits -= change * a.cost + end + elseif selected_panel == 5 and btnp(❎) and has_weapon then + change_state("gameplay") + return + end + + for i, p in ipairs(loadout_panels) do + p.selected = (i == selected_panel) + if i <= 4 then + local a = player.abilities[i] + p.textline = a.name + count_panels[i].textline = a.remaining_uses.." AMMO" + elseif i == 5 then + p.active = has_weapon + end + p:update() + end + + for p in all(count_panels) do + p:update() + end +end + +function draw_loadout_select() + reset_pal(true) + map(unpack(INTRO_MAP_ARGS)) + draw_shadow(-20,-20, 10, SWAP_PALETTE_DARKER) + print("cREDITS: "..credits, 10, 10, 7) + + for p in all(loadout_panels) do p:draw() end + for p in all(count_panels) do p:draw() end + + local info_text = "⬆️⬇️: SELECT\n" + info_text ..= selected_panel <= 4 and "⬅️: SELL ➡️: BUY | "..(player.abilities[selected_panel].cost).." cREDITS" or + selected_panel == 5 and loadout_panels[5].active and "❎: bEGIN mISSION" or "" + print(info_text, 10, 115, 11) +end + +-- GAMEPLAY +---------------------- +function init_gameplay() + decompress_current_map() + + music(0) + player_hud = player_hud.new() + entities, particles, terminals, doors, barrels, data_fragments, ending_sequence_timer = {}, {}, {}, {}, {}, {}, 1000 + + local mission_entities = { + [[0,0,bot,player|448,64,bot,dervish|432,232,bot,vanguard|376,272,bot,vanguard|426,354,bot,dervish|356,404,bot,warden|312,152,bot,vanguard|232,360,bot,dervish|40,100,bot,dervish|200,152,bot,dervish|32,232,bot,warden|88,232,bot,vanguard|248,248,preacher,cyberseer]], + [[0,0,bot,player|528,144,bot,dervish|624,160,bot,vanguard|688,288,bot,dervish|616,48,preacher,cyberseer|824,136,bot,warden|680,96,bot,dervish|920,32,bot,dervish|984,96,bot,warden|896,160,bot,vanguard|904,312,preacher,quantumcleric|976,248,bot,vanguard|800,376,bot,warden|728,336,bot,vanguard|816,320,bot,vanguard|608,360,bot,warden|968,1200,bot,vanguard]], + [[0,0,bot,player|240,416,bot,warden|88,336,bot,dervish|160,368,bot,vanguard|24,416,bot,warden|216,104,bot,vanguard|256,40,bot,dervish|296,72,bot,dervish|136,80,preacher,cyberseer|32,88,bot,dervish|32,32,bot,dervish|40,160,bot,warden|344,344,bot,vanguard|456,336,preacher,quantumcleric|368,416,bot,dervish|416,128,bot,vanguard|344,136,bot,vanguard|424,96,preacher,quantumcleric|352,240,bot,vanguard|432,264,bot,vanguard|496,152,bot,warden]], + [[0,0,bot,player|880,412,bot,dervish|760,408,bot,dervish|696,424,bot,vanguard|600,360,bot,warden|552,400,bot,warden|592,256,bot,vanguard|528,280,preacher,cyberseer|528,208,bot,vanguard|560,168,bot,vanguard|688,296,bot,dervish|688,360,bot,dervish|760,304,bot,warden|912,344,preacher,quantumcleric|848,344,bot,dervish|712,192,bot,warden|776,200,bot,warden|888,192,bot,warden|984,184,preacher,cyberseer|992,256,bot,vanguard|640,32,bot,vanguard|632,104,bot,vanguard|664,32,bot,dervish|664,104,bot,dervish|704,32,bot,dervish|704,104,bot,dervish|896,40,preacher,quantumcleric|968,96,preacher,cyberseer]] + } + + for e in all(stringToTable(mission_entities[current_mission])) do + if e[4] != "player" then + add(entities, entity.new(unpack(e))) + end + end + + boundaries = stringToTable("0,0,0,0,64,56|64,0,0,0,128,56|0,0,0,0,64,56|64,0,0,0,128,56")[current_mission] + + for map_y = boundaries[2], boundaries[6] do + for map_x = boundaries[1],boundaries[5] do + local tile, tile_x, tile_y = mget(map_x,map_y), map_x*8, map_y*8 + if fget(tile, 6) then + add(barrels, barrel.new(tile_x, tile_y)) + elseif fget(tile, 5) then + add(data_fragments, data_fragment.new(tile_x, tile_y)) + elseif fget(tile, 4) then + add(terminals, terminal.new(tile_x+4, tile_y-4)) + elseif fget(tile, 7) then + player_spawn_x, player_spawn_y = tile_x, tile_y + player.x, player.y = tile_x, tile_y + player.health = player.max_health + add(entities, player) + end + end + end + + local door_terminals = { + [[444,130,472,80,red|354,66,248,368,green]], + [[808,252,712,48,green|824,252,952,56,red|840,252,568,376,blue]], + [[184,2,160,392,green|392,282,144,224,red|360,170,320,408,blue]], + [[620,2,552,304,green|652,2,904,280,red|684,2,984,272,blue]] + } + for args in all(stringToTable(door_terminals[current_mission])) do + create_door_terminal_pair(unpack(args)) + end + + game_ability_menu = ability_menu.new() + game_minigame = minigame.new() +end + +function update_gameplay() + if game_minigame.active then + game_minigame:update() + else + if btn(🅾️) and not game_ability_menu.active then + game_ability_menu:open() + elseif not btn(🅾️) and game_ability_menu.active then + game_ability_menu:close() + end + + if game_ability_menu.active then + game_ability_menu:update() + else + foreach(entities, function(e) e:update() end) + foreach(terminals, function(t) t:update() end) + foreach(barrels, function(b) b:update() end) + + for i = #particles, 1, -1 do + local p = particles[i] + p:update() + if p.lifespan < 0 then del(particles, p) end + end + + cam:update() + player_hud:update() + end + end +end + +function draw_gameplay() + reset_pal(true) + map(0,0,0,0,128,56) + + for group in all({terminals, data_fragments, entities, particles, barrels}) do + foreach(group, function(e) e:draw() end) + end + + foreach(doors, function(d) d:draw() end) + + if player.health > 0 then + draw_shadow(player.x - cam.x, player.y - cam.y, 50, SWAP_PALETTE) + end + + player_hud:draw() + game_ability_menu:draw() + game_minigame:draw() + + -- check mission status + if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then + local message, color, prompt + + if player.health > 0 then + message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + + -- Update mission completion status + mission_data[current_mission][1] = 1 + mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 + mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 + else + message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + end + + draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) + print_centered(message, player.x, player.y - 6, color) + print_centered(prompt, player.x, player.y + 2, 7) + + if btnp(🅾️) then + change_state("mission_select") + end + end +end + +function print_centered(t,x,y,c) + print(t,x-#t*2,y,c) +end + +-- PARTICLE +-------------- +particle = {} + +function particle:new(x, y, vx, vy, lifespan, size, color, behavior, owner) + local p = setmetatable({ + x=x, + y=y, + vx=vx, + vy=vy, + color=color, + max_lifespan=lifespan, + lifespan=lifespan, + size=size, + behavior=behavior or "default", + owner=owner + }, {__index=particle}) + + if behavior == "missile" then + p.orbit_time = 15 + p.orbit_angle = rnd() + p.orbit_radius = 5 + rnd(10) + p.speed = 1 + p.max_speed = 3 + p.damage = 15 + p.explosion_radius = 16 + p.explosion_damage = 4 + p.direction = rnd() + elseif behavior == "plasma" then + p.damage = 75 + p.explosion_radius = 16 + p.explosion_damage = 10 + else + p.damage = 3 + p.speed = behavior == "machinegun" and 6 or 8 + end + + return p +end + +function particle:check_collision_and_damage() + -- Check collision with barrels first + for b in all(barrels) do + if self:collides_with(b) then + b:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + -- Check collision with solid tiles + if check_tile_flag(self.x, self.y) then + self:create_impact_particles() + return true + end + + -- Check collision with entities + for e in all(entities) do + if e != self.owner and self:collides_with(e) then + e:take_damage(self.damage) + self:create_impact_particles() + return true + end + end + + return false +end + +function particle:update() + local _G, _ENV = _ENV, self + lifespan -= 1 + + if behavior == "missile" then + if orbit_time > 0 then + -- Orbiting phase + orbit_time -= 1 + orbit_angle += 0.02 + x = owner.x + owner.width/2 + _G.cos(orbit_angle) * orbit_radius + y = owner.y + owner.height/2 + _G.sin(orbit_angle) * orbit_radius + else + -- Movement phase + if target and target.health > 0 then + -- Homing behavior + local dx, dy = target.x + target.width/2 - x, target.y + target.height/2 - y + if _G.dist_trig(dx, dy) > 0 then + direction = _G.atan2(dx, dy) + speed = _G.min(speed + 1, max_speed) + end + else + -- Scattering + speed = _G.min(speed + 0.05, max_speed) + direction += _G.rnd(0.1) - 0.05 + end + + -- Apply movement + vx, vy = _G.cos(direction) * speed, _G.sin(direction) * speed + x += vx + y += vy + + -- Check for collision using the new method + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + end + + -- Explode if lifespan is over + if lifespan <= 0 then + self:explode() + end + else + x += vx + y += vy + + if behavior == "machinegun" or behavior == "rifle" then + if self:check_collision_and_damage() then lifespan = -1 end + elseif behavior == "plasma" then + if self:check_collision_and_damage() then + self:explode() + lifespan = -1 + end + else + vy += 0.03 + end + end +end + +function particle:collides_with(obj) + local _ENV = self + return x > obj.x and x < obj.x + obj.width and + y > obj.y and y < obj.y + obj.height +end + +function particle:explode() + -- Create explosion particles + for i = 1, 10 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p = particle:new(self.x, self.y, cos(angle) * speed, sin(angle) * speed, 20 + rnd(10), 2, 9) + add(particles, p) + end + + -- Apply damage to nearby entities and barrels + for e in all(entities) do + if e != self.owner then + self:apply_explosion_damage(e) + end + end + + for b in all(barrels) do + self:apply_explosion_damage(b) + end + + sfx(28) +end + +function particle:apply_explosion_damage(obj) + local dist = dist_trig(obj.x + obj.width/2 - self.x, obj.y + obj.height/2 - self.y) + if dist < self.explosion_radius then + local damage = self.explosion_damage * (1 - dist/self.explosion_radius) + obj:take_damage(damage) + end +end + +function particle:create_impact_particles() + for i = 1, 3 do + local angle, speed = rnd(), 0.5 + rnd(1) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed + local p = particle:new(self.x, self.y, p_vx, p_vy, 10 + rnd(5), 1, 6) + add(particles, p) + end +end + +function particle:draw() + circfill(self.x, self.y, self.size, self.color) +end + + +-- ENTITY +---------------------- +entity = {} + +function entity.new(x, y, base_class, subclass) + local is_preacher = base_class == "preacher" + local new_entity = setmetatable({ + -- Position and movement + x = x, + y = y, + vx = 0, + vy = 0, + width = is_preacher and 16 or 8, + height = is_preacher and 24 or 8, + max_speed = is_preacher and 3 or 4, + acceleration = 0.8, + deceleration = 0.9, + turn_speed = 0.3, + diagonal_factor = 0.7071, + + -- Entity type + base_class = base_class, + subclass = subclass, + + -- Sprite and animation + current_sprite = is_preacher and 8 or 1, + bot_sprite_sets = { + idle = {horizontal = {0,1}, up = {32,33}, down = {16,17}}, + walking = {horizontal = {2,3}, up = {34,35}, down = {18,19}} + }, + + -- Target and following + target_x = x, + target_y = y, + last_direction = "down", + facing_left = false, + + -- Physics + mass = 1, + + -- Ability system + abilities = {}, + selected_ability = 1, + + -- AI-related properties + state = "idle", + last_seen_player_pos = {x = nil, y = nil}, + alert_timer = 0, + max_alert_time = 180, + + idle_timer = 0, + + -- Poison-related properties + poison_timer = 0, + + -- Flash effect property + flash_timer = 0, + }, {__index=entity}) + + new_entity.targeting = targeting.new(new_entity) + + local ability_data = [[ + 15,100,rIFLE bURST,fIRE A BURST OF MEDIUM-DAMAGE BULLETS,rifle_burst,20| + 30,200,mACHINE gUN,rAPID-FIRE HIGH-VELOCITY ROUNDS,machine_gun,25| + 45,50,mISSILE hAIL,lAUNCH A BARRAGE OF HOMING MISSILES,missile_hail,50| + 60,25,pLASMA cANNON,fIRE A DEVASTATING PLASMA PROJECTILE,plasma_cannon,75]] + + for i, a in ipairs(stringToTable(ability_data)) do + add(new_entity.abilities, { + index = i, + cooldown = a[1], + name = a[3], + description = a[4], + action = new_entity[a[5]], + current_cooldown = 0, + remaining_uses = subclass != "player" and a[2] or 0, + cost = a[6] + }) + end + + local entity_data_str = [[ + 15,dervish,50,50,60,100| + 13,vanguard,70,70,50,120| + 1,warden,100,100,70,200| + 7,player,1,1,70,0| + 11,preacher,80,80,80,280| + 6,cyberseer,160,160,80,300| + 1,quantumcleric,170,170,70,320 + ]] + + for d in all(stringToTable(entity_data_str)) do + if d[2] == subclass then + new_entity.color, _, new_entity.health, new_entity.max_health, new_entity.attack_range, new_entity.kill_value = unpack(d) + end + end + + return new_entity +end + +function entity:update() + if self.subclass == "player" then + self:player_update() + else + self:enemy_update() + end + self:apply_physics() + self.targeting:update() + + -- Update cooldowns + for ability in all(self.abilities) do + ability.current_cooldown = max(0, ability.current_cooldown - 1) + end + + -- Handle poison damage + if check_tile_flag(self.x, self.y, 2) and self.base_class != "preacher" then + self.poison_timer += 1 + if self.poison_timer >= 5 then + self:take_damage(1) + self.poison_timer = 0 + end + else + self.poison_timer = 0 + end + + if self.update_plasma then + self:update_plasma() + end +end + +function entity:player_update() + self:control() + self:follow_target() + + if btnp(❎) then + for t in all(terminals) do + if t.interactive then + game_minigame:start(t) + goto continue + end + end + self:activate_ability(self.selected_ability) + ::continue:: + end + + for fragment in all(data_fragments) do + if dist_trig(fragment.x - self.x, fragment.y - self.y) < 8 and not fragment.collected then + self.health = min(self.health + 20, self.max_health) + player_hud:add_credits(50) + fragment.collected = true + sfx(7) + end + end +end + +function entity:enemy_update() + local s = { + idle = self.update_idle, + alert = self.update_alert, + attack = self.update_attack + } + s[self.state](self) +end + +function entity:take_damage(amount) + self.health = max(0, self.health - amount) + self.flash_timer = 1 + if self.health <= 0 then self:on_death() end + if self.subclass == "player" then player_hud.shake_duration = 10 end +end + +function entity:on_death() + player_hud:add_credits(self.kill_value) + self:spawn_death_particles() + del(entities, self) + sfx(30) +end + +function entity:spawn_death_particles() + local particle_count = self.base_class == "preacher" and 40 or 20 + + for i = 1, particle_count do + local angle, speed = rnd(), 0.5 + rnd(1.5) + local p = particle:new( + self.x + self.width / 2, + self.y + self.height / 2, + cos(angle) * speed, + sin(angle) * speed, + 20 + rnd(10), 1 + flr(rnd(2)), rnd({8,9,10})) + add(particles, p) + end +end + +function entity:can_see_player() + local player = self:find_player() + if not player then return false end + + local dx, dy = player.x - self.x, player.y - self.y + + if dist_trig(dx, dy) <= self.attack_range then + local steps = max(abs(dx), abs(dy)) + local step_x, step_y = dx / steps, dy / steps + + for i = 1, steps do + if check_tile_flag(self.x + step_x * i, self.y + step_y * i) then + return false + end + end + + self.last_seen_player_pos.x, self.last_seen_player_pos.y = player.x, player.y + return true + end + + return false +end + +function entity:update_idle() + self.idle_timer -= 1 + if self.idle_timer <= 0 then + self.idle_timer,angle,speed = 30,rnd(),rnd(1) + self.vx, self.vy = cos(angle)*speed, sin(angle)*speed + end + + if self:can_see_player() then + self.state = "alert" + self.alert_timer = self.max_alert_time + end +end + +function entity:update_alert() + if self:can_see_player() then + self.alert_timer = self.max_alert_time + local player = self:find_player() + local dx, dy = player.x - self.x, player.y - self.y + local dist = dist_trig(dx, dy) + + if dist <= self.attack_range then + self.state = "attack" + else + -- Move towards player + self.vx, self.vy = dx / dist, dy / dist + end + elseif self.last_seen_player_pos.x then + local dx, dy = self.last_seen_player_pos.x - self.x, self.last_seen_player_pos.y - self.y + local dist = dist_trig(dx, dy) + + self.vx, self.vy = 0, 0 + if dist > 1 then + self.vx, self.vy = dx / dist, dy / dist + end + end + + self.alert_timer -= 1 + if self.alert_timer <= 0 then + self.state, self.last_seen_player_pos.x, self.last_seen_player_pos.y = "idle", nil, nil + end +end + +function entity:update_attack() + local player = self:find_player() + if not player or not self:can_see_player() then + self.state = "alert" + self:reset_plasma_cannon() + return + end + + local dx, dy = player.x - self.x, player.y - self.y + if dist_trig(dx, dy) <= self.attack_range then + self.facing_left = dx < 0 + self.last_direction = abs(dx) > abs(dy) and "horizontal" or (dy < 0 and "up" or "down") + + local subclass_abilities = entity_abilities[self.subclass] + local ability = self.abilities[self:find_ability(subclass_abilities[flr(rnd(#subclass_abilities)) + 1])] + if ability and ability.current_cooldown == 0 then + self:activate_ability(ability.index) + end + else + self.state = "alert" + end +end + +function entity:find_ability(ability_name) + for i, ability in ipairs(self.abilities) do + if ability.name == ability_name then + return i + end + end + return nil +end + +function entity:find_player() + for e in all(entities) do + if e.subclass == "player" then + return e + end + end +end + +function entity:activate_ability(index) + local ability = self.abilities[index] + if ability.current_cooldown == 0 then + if ability.remaining_uses > 0 then + ability.action(self) + if self.subclass == "player" then + ability.current_cooldown = ability.cooldown + ability.remaining_uses -= 1 + else + ability.current_cooldown = ability.cooldown * 3 + end + else + sfx(29) + end + end +end + +function entity:rifle_burst() + local dx, dy = self:get_aim_direction() + local decel = self.subclass == "player" and 5.5 or 1 + self.vx -= dx * decel + self.vy -= dy * decel + + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + for i = -2, 2 do + local angle = atan2(dx, dy) + i * 0.005 + local vx, vy = cos(angle) * 4, sin(angle) * 4 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 30, 1, 8, "rifle", self + ) + add(particles, bullet) + end + + sfx(27) +end + +function entity:machine_gun() + local bullets, orig_update = 0, self.update + function self:update() + orig_update(self) + if bullets < 20 then + if bullets % 2 == 0 then + local dx, dy = self:get_aim_direction() + local angle = atan2(dx, dy) + (rnd() - 0.5) * 0.03 + local vx, vy = cos(angle) * 6, sin(angle) * 6 + local sx, sy = self.x + self.width/2, self.y + self.height/2 + + local bullet = particle:new( + sx + cos(angle) * self.width/2, + sy + sin(angle) * self.height/2, + vx, vy, 20, 1, 8, "machinegun", self + ) + add(particles, bullet) + + self.vx -= dx * 0.15 + self.vy -= dy * 0.15 + + sfx(14) + end + bullets += 1 + else + self.update = orig_update + end + end +end + +function entity:missile_hail() + for i = 1, 3 do + local angle = rnd() + local offset = 10 + rnd(10) + local lifetime = 30 or 60 and self.subclass == "player" + local missile = particle:new( + self.x + self.width/2 + cos(angle) * offset, + self.y + self.height/2 + sin(angle) * offset, + 0, 0, lifetime, 1, 8, "missile", self) + missile.target = self.targeting.target + add(particles, missile) + end + + sfx(6) +end + +function entity:plasma_cannon() + self.plasma_charge = 0 + self.is_charging_plasma = true + self.update_plasma = self.update_plasma_cannon +end + +function entity:update_plasma_cannon() + if self.is_charging_plasma then + if self.plasma_charge < 20 then + self.plasma_charge += 1 + sfx(4) + else + local dx, dy = self:get_aim_direction() + local sx, sy = self.x + self.width/2, self.y + self.height/2 + local proj = particle:new( + sx, + sy, + dx * 5, + dy * 5, + 120, 4, 12, "plasma", self) + + add(particles, proj) + sfx(10) + self.vx -= dx * 5.5 + self.vy -= dy * 5.5 + + self:reset_plasma_cannon() + end + end +end + +function entity:reset_plasma_cannon() + self.is_charging_plasma = false + self.plasma_charge = 0 + self.update_plasma = nil +end + +function entity:get_aim_direction() + local target = self.targeting.target + if target then + local dx, dy = target.x - self.x, target.y - self.y + local dist = dist_trig(dx, dy) + return dx/dist, dy/dist + end + + local speed = dist_trig(self.vx, self.vy) + if speed > 0 then + return self.vx / speed, self.vy / speed + end + + if self.last_direction == "horizontal" then + return self.facing_left and -1 or 1, 0 + end + return 0, self.last_direction == "up" and -1 or 1 +end + +function entity:control() + local ix = (btn(1) and 1 or 0) - (btn(0) and 1 or 0) + local iy = (btn(3) and 1 or 0) - (btn(2) and 1 or 0) + local max_target_distance, target_speed = 32, 6 + + if ix != 0 and iy != 0 then + ix *= self.diagonal_factor + iy *= self.diagonal_factor + end + + self.target_x += ix * target_speed + self.target_y += iy * target_speed + + local dx, dy = self.target_x - self.x, self.target_y - self.y + + if dist_trig(dx, dy) > max_target_distance then + local angle = atan2(dx, dy) + self.target_x = self.x + cos(angle) * max_target_distance + self.target_y = self.y + sin(angle) * max_target_distance + end +end + +function entity:follow_target() + local dx, dy = self.target_x - self.x, self.target_y - self.y + local distance, follow_speed = dist_trig(dx, dy), .1 + + if distance > 1 then + self.vx, self.vy = self:approach(self.vx, dx * follow_speed, self.acceleration), self:approach(self.vy, dy * follow_speed, self.acceleration) + + -- Update direction information + if abs(self.vx) > abs(self.vy) then + self.last_direction = "horizontal" + self.facing_left = self.vx < 0 + else + self.last_direction = self.vy < 0 and "up" or "down" + end + else + self.vx, self.vy = self:approach(self.vx, 0, self.deceleration), self:approach(self.vy, 0, self.deceleration) + end + + -- Limit speed + local speed = dist_trig(self.vx, self.vy) + if speed > self.max_speed then + self.vx = (self.vx / speed) * self.max_speed + self.vy = (self.vy / speed) * self.max_speed + end +end + +function entity:approach(current, target, step) + if current < target then + return min(current + step, target) + elseif current > target then + return max(current - step, target) + else + return current + end +end + +function entity:apply_physics() + -- Apply deceleration + self.vx = abs(self.vx) < 0.05 and 0 or self.vx * self.deceleration + self.vy = abs(self.vy) < 0.05 and 0 or self.vy * self.deceleration + + -- Prepare new position + local new_x, new_y = self.x + self.vx, self.y + self.vy + + -- Check tile collision + if self:check_tile_collision(new_x, new_y) then + if not self:check_tile_collision(new_x, self.y) then + new_y = self.y + elseif not self:check_tile_collision(self.x, new_y) then + new_x = self.x + else + new_x, new_y = self.x, self.y + end + end + + -- Check laser door collision + for door in all(doors) do + if door:check_collision(new_x, new_y, self.width, self.height) then + new_x, new_y = self.x, self.y + self.vx, self.vy = 0, 0 + break + end + end + + -- Update position + self.x, self.y = new_x, new_y +end + +function entity:check_tile_collision(x, y) + -- local points = { + -- {x, y}, + -- {x + self.width - 1, y}, + -- {x, y + self.height - 1}, + -- {x + self.width - 1, y + self.height - 1} + -- } + + -- for point in all(points) do + -- if check_tile_flag(unpack(point)) then + -- return true + -- end + -- end + + -- return false + return false +end + +function entity:draw() + local x,y,w,h = self.x,self.y,self.width,self.height + local is_preacher = self.base_class == "preacher" + local hover_offset = is_preacher and sin(time() * .5) * 2 or 0 + + -- Plasma charge circle + if self.is_charging_plasma and self.plasma_charge < 20 then + circ(x + w/2, y + h/2, 32 * (1 - self.plasma_charge / 20), 12) + end + + -- Shadow + if is_preacher then + local scale = 1 - (hover_offset / 8) + ovalfill(x+8-6*scale, y+22, x+8+6*scale, y+22+3*scale, 1) + else + spr(49, x, y + 1) + end + + if self.flash_timer > 0 then + for i = 0, 15 do pal(i, 8) end + else + -- Normal color palette + pal(self.base_class == "bot" and 7 or 6, entity_colors[self.subclass]) + if is_preacher then + if t() % 1 < .5 then pal(0, 8) end + elseif self.subclass != "player" then + pal(12,8) + end + end + + -- Sprite drawing + local speed = dist_trig(self.vx, self.vy) + local is_moving = speed > 0.2 + + if is_preacher then + spr(self.current_sprite, x, y + hover_offset, 2, 3, self.vx < 0 and is_moving) + else + local sprites = (is_moving and self.bot_sprite_sets.walking or self.bot_sprite_sets.idle)[self.last_direction] + local anim_speed = is_moving and (10 + min(speed / self.max_speed, 1) * 10) or 3 + spr(sprites[flr(time() * anim_speed) % #sprites + 1], x, y, 1, 1, self.facing_left) + end + + reset_pal() + + -- State indicators + local indicator = self.state == "alert" and 36 or (self.state == "attack" and 20) + if self.subclass != "player" and indicator then + spr(indicator, x + (is_preacher and 6 or 4), y - 8) + else + self.targeting:draw() + end + + self.flash_timer = max(0, self.flash_timer - 1) +end + + +-- BARREL +---------- +barrel = {} + +function barrel.new(x, y) + local poison = rnd() > .5 + return setmetatable({ + x = x, + y = y, + poison = poison, + height = 8, + width = 8, + health = 1, + exploding = false, + explosion_time = 0, + }, {__index=barrel}) +end + +function barrel:draw() + if not self.exploding then + spr(self.poison and 5 or 6, self.x, self.y - 8) + end +end + +function barrel:update() + if self.health <= 0 and not self.exploding then + self.exploding = true + self.explosion_time = 0 + end + + if count_remaining_terminals() == 0 then + if dist_trig(player.x - self.x, player.y - self.y) < 50 and rnd() < 0.01 then + self.health = 0 + end + end + + if self.exploding then + self.explosion_time += 1 + + if self.explosion_time == 1 then + for i = 1, 20 do + local angle, speed = rnd(), 1 + rnd(2) + local p_vx, p_vy = cos(angle) * speed, sin(angle) * speed * 0.5 + add(particles, particle:new( + self.x + self.width/2, + self.y + self.height/2, + p_vx, p_vy, + 20 + rnd(10), + 1 + rnd(2), + self.poison and 3 or 8 + )) + end + + for e in all(entities) do + local dx = e.x + e.width/2 - (self.x + self.width/2) + local dy = e.y + e.height/2 - (self.y + self.height/2) + local normalized_dist = dist_trig(dx/64, dy/32) + if normalized_dist < 0.5 then + local damage = 20 * (1 - normalized_dist*2) + e:take_damage(damage * (self.poison and 1.5 or 1)) + end + end + + sfx(28) + end + + mset(flr(self.x / 8), flr(self.y / 8), self.poison and 10 or 26) + + if self.explosion_time >= 15 then + del(barrels, self) + end + end +end + +function barrel:take_damage(amount) + self.health = max(0, self.health - amount) +end + + +-- LASER DOOR +---------------- +laser_door = {} + +function laser_door.new(x, y, color) + local laser_beams, color_map = {}, {} + + for beam in all(stringToTable("11,4|9,8|7,12")) do + local sx, sy = x + beam[1], y + beam[2] + local ex, ey = sx, sy + 10 + while not check_tile_flag(ex, ey) do ey += 1 end + add(laser_beams, {start_x=sx, start_y=sy, end_x=ex, end_y=ey-1}) + end + + + for color_data in all(stringToTable("red,8,2|green,11,3|blue,12,1")) do + local color_name, light_shade, dark_shade = unpack(color_data) + color_map[color_name] = { + beam_color = light_shade, + terminal_sequence = {7, light_shade, dark_shade, light_shade} + } + end + + return setmetatable({ + x = x, + y = y, + is_open = false, + laser_beams = laser_beams, + color = color or "red", + color_map = color_map + }, {__index=laser_door}) +end + +function laser_door:draw() + spr(14, self.x, self.y, 2, 2) + if not self.is_open then + for i, beam in ipairs(self.laser_beams) do + line( + beam.start_x, + beam.start_y, + beam.end_x, + beam.end_y + (#self.laser_beams - i + 1) * 2, + self.color_map[self.color].beam_color) + end + end +end + +function laser_door:check_collision(ex, ey, ew, eh) + if self.is_open then return false end + + for beam in all(self.laser_beams) do + if (ey + eh > beam.start_y and ey < beam.end_y) and + (ex < beam.start_x and ex + ew > beam.start_x) then + return true + end + end + + return false +end + +-- DATA FRAGMENT +---------------- +data_fragment = {collected = false} + +function data_fragment.new(x, y) + return setmetatable({ + x = x, + y = y, + height = 8, + width = 8 + }, {__index=data_fragment}) +end + + +function data_fragment:draw() + if not self.collected then + local sprite_list = stringToTable("50,51,52,53,53,53,53,54,55")[1] + spr(sprite_list[flr(time() / .15) % #sprite_list + 1], self.x, self.y-4) + end +end + + +-- TERMINAL +---------------- +terminal = {} + +function terminal.new(x, y, target_door) + local pulse_colors = target_door and target_door.color_map[target_door.color].terminal_sequence or {7, 6, 13, 6} -- Default pulse colors if no door + + return setmetatable({ + x = x, + y = y, + interactive = false, + pulse_index = 1, + pulse_timer = 0, + target_door = target_door, + pulse_colors = pulse_colors, + completed = false + }, {__index = terminal}) +end + +function terminal:update() + if self.completed then + self.interactive = false + return + end + + self.interactive = true + for e in all(entities) do + if e.state != "idle" or dist_trig(player.x-self.x, player.y-self.y) >= 32 then + self.interactive = false + self.pulse_index, self.pulse_timer = 1, 0 + return + end + end + + self.pulse_timer = (self.pulse_timer + 1) % 6 + if self.pulse_timer == 0 then + self.pulse_index = self.pulse_index % #self.pulse_colors + 1 + end +end + +function terminal:draw() + if self.completed then + pal(7, 8) + elseif self.interactive then + pal(7, self.pulse_colors[self.pulse_index]) + end + + spr(39, self.x, self.y + 8) + spr(23, self.x, self.y) + reset_pal() +end + +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) + local new_door = laser_door.new(door_x, door_y, color) + add(doors, new_door) + add(terminals, terminal.new(terminal_x, terminal_y, new_door)) +end + +-- MINIGAME +--------------- +minigame = { + directions = {"⬅️","➡️", "⬆️", "⬇️"}, + active = false, + current_input = {}, + time_limit = 180, + timer = 0, + current_terminal = nil +} + +function minigame.new() + return setmetatable({}, {__index = minigame}) +end + +function minigame:start(terminal) + self.sequence = {} + for i = 1, 5 do add(self.sequence, self.directions[flr(rnd(4)) + 1]) end + + local _ENV = self + active = true + timer = time_limit + current_input = {} + current_terminal = terminal + +end + +function minigame:update() + self.timer -= 1 + if self.timer <= 0 then + self:end_game(false) + return + end + + for i = 0, 3 do + if btnp(i) then + add(self.current_input, self.directions[i+1]) + if #self.current_input == #self.sequence then + self:check_result() + end + return + end + end +end + +function minigame:check_result() + for i = 1, #self.sequence do + if self.sequence[i] != self.current_input[i] then + self:end_game(false) + return + end + end + self:end_game(true) +end + +function minigame:end_game(success) + self.active = false + local current_terminal = self.current_terminal + + if success then + if current_terminal.target_door then + current_terminal.completed = true + current_terminal.target_door.is_open = true + else + current_terminal.completed = true + end + end + current_terminal = nil +end + +function minigame:draw() + if not self.active or player.health <= 0 then return end + + local center_x, center_y = 64 + cam.x, 64 + cam.y + rectfill(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 0) + rect(center_x - 35, center_y - 20, center_x + 35, center_y + 20, 3) + + -- Calculate total width of sequence + local seq_width = #self.sequence * 12 - 4 + local seq_start_x = center_x - seq_width / 2 + + for x in all(self.sequence) do + print(x, seq_start_x, center_y - 10, 7) + seq_start_x += 12 + end + + -- Reset seq_start_x for current input + seq_start_x = center_x - seq_width / 2 + + for i, dir in pairs(self.current_input) do + local color = dir == self.sequence[i] and 11 or 8 + print(dir, seq_start_x, center_y, color) + seq_start_x += 12 + end + + -- Center the timer text + local timer_text = "time: "..flr(self.timer / 30) + local timer_width = #timer_text * 4 -- Assuming each character is 4 pixels wide + print(timer_text, center_x - timer_width / 2, center_y + 10, 8) +end + + +-- PLAYER HUD +--------------- +player_hud = { + bar_width=80, + bar_height=5, + cooldown_bar_height=3, + x_offset=2, + y_offset=2, + text_padding=2, + show_interact_prompt=false, + shake_duration=0, + alert_bar_height=4, + credit_add_timer=0, +} + +function player_hud.new() + return setmetatable({}, {__index=player_hud}) +end + +function player_hud:update() + self.show_interact_prompt = false + for terminal in all(terminals) do + if terminal.interactive then + self.show_interact_prompt = true + break + end + end + self.shake_duration = max(self.shake_duration - 1, 0) + if self.credit_add_timer > 0 then + credits += 5 + self.credit_add_timer = max(self.credit_add_timer - 5, 0) + end +end + +function player_hud:draw() + local cam_x, cam_y = cam.x, cam.y + local health_percent = player.health / player.max_health + local start_x, start_y = flr(self.x_offset + cam_x), flr(self.y_offset + cam_y) + + if self.shake_duration > 0 then + start_x += rnd(4) - 2 + start_y += rnd(4) - 2 + end + + local health_color = health_percent > 0.6 and 11 or health_percent > 0.3 and 10 or 8 + draw_bar(start_x, start_y, 80, 5, 7, health_color, health_percent) + + local ability, cooldown_y = player.abilities[player.selected_ability], start_y + 5 + draw_bar(start_x, cooldown_y, 80, 3, 1, 12, 1 - ability.current_cooldown / ability.cooldown) + + print_shadow(flr(player.health).."/"..player.max_health, start_x + 82, start_y) + print_shadow(ability.name.." ▶"..ability.remaining_uses.."◀", start_x, cooldown_y + 5, ability.remaining_uses == 0 and (t()*4 % 2 < 1 and 2 or 7)) + + local credits_text = "cREDITS: "..credits + if self.credit_add_timer > 0 then + credits_text ..= " +"..self.credit_add_timer + end + print_shadow(credits_text, start_x, cooldown_y + 12) + + if self.show_interact_prompt then + print_shadow("❎ interact", cam_x + 4, cam_y + 120) + end + + local alert_x, alert_y = cam_x + self.x_offset, cam_y + 127 - self.alert_bar_height + + for entity in all(entities) do + if entity.state == "alert" or entity.state == "attack" then + local health_percent = entity.health / entity.max_health + local bar_width = flr(entity.max_health * .4) + draw_bar(alert_x, alert_y, bar_width, self.alert_bar_height, 7, 8, health_percent) + print_shadow(entity.subclass, alert_x + bar_width + self.text_padding, alert_y) + alert_y -= self.alert_bar_height + self.text_padding + end + end + + if count_remaining_terminals() == 0 then + if ending_sequence_timer == 1000 then + music(7) + elseif ending_sequence_timer > 0 then + print_shadow("EVACUATE IN: " .. flr(ending_sequence_timer), cam_x + 30, cam_y + 90) + print_shadow("FOLLOW THE RED DOT", cam_x + 26, cam_y + 100) + -- Spawn point indicator + local angle = atan2(player_spawn_x - player.x, player_spawn_y - player.y) + circfill(player.x + cos(angle) * 20, player.y + sin(angle) * 20, 1, 8) + elseif ending_sequence_timer == 0 then + player.health = 0 + player:on_death() + end + ending_sequence_timer -= 1 + end +end + +function draw_bar(x, y, width, height, bg_color, fill_color, percentage) + rectfill(x, y, x + width - 1, y + height - 1, bg_color) + if percentage > 0 then + rectfill(x, y, x + max(1, flr(width * percentage)) - 1, y + height - 1, fill_color) + end + rect(x, y, x + width - 1, y + height - 1, 0) +end + +function print_shadow(text, x, y, color) + print(text, x + 1, y + 1, 0) + print(text, x, y, color or 7) +end + +function player_hud:add_credits(amount) + self.credit_add_timer += amount +end + + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 +a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b +0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 +14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f +d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 +6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 +7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c +3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd +1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 +2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 +821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf +6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf +a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b +6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 +2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 +8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 +2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 +7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 +73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 +55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf +6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 +081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 +0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b +8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 +e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 +5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 +c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 +41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 +462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 +2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b +188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 +e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 +260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a +f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 +d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 +d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 +aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 +1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 +1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 +e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c +df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d +e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 +76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 +40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 +64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a +174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 +7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 +d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc +4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a +4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 +01e3a0f160f038782c301e120f0a8f8504af7184330810f00a80e48340c74055cd3318f10c2bc5e61e4f70ac70ff30ff38f11c2bd93c73c1022911683e381404 +a0066b05e086f34a802100742073583d2cef0ef70ff38f13c9c069e1bab02d381f04ff0eff0ff38ff1cff2917cb0ac58ff1cef2a0605fb8fe74225a1311771af +083034d80a4e1df389d84bb6a570ff30ffd8983cdf2e8f0583936043262595f9d2fc585eb4ddc507108240cb2ce736f372b599fadc716eba3f569fabcf1ee727 +fba3f5d9fafc717eba3f5e9faf8c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__label__ +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000000000000000000000001001100000000000000000000030000000000000000000000000000010011000000000000000000000000000000000 +00002000000020033333333033333333033333333033333333033333333033000033300000000000000000000011010000000000000000000000000000000000 +00000000000000033333333033333333033333333033333333033333333033300333000000000000000000000010011000000000000000000000000000000000 +00000000000000033000000033000033001101033000033000000000000003333330000000000000000000000001011000000000000000000000000000000000 +00002000200002033222222033222233033333333222233002233333333222333322222002222220000000000011001002222220022222200000000000000000 +00000000000000033200000033000033033333333200033002033333333000333300000002000000001111110101011002000000020000000000000000000000 +00000000000000033200000033000033033033300200033002000000020003333330000002000000001010101111001002000000020000000000000000000000 +00000002000000033333333033333333033103330200033002033333333033300333000002000000001100000000011002000000020000000000000000000000 +00000000000000033333333033333333033001333200033002033333333333000233000002000000001001111010101002000000020000000000000000000000 +00000000000000000200000002000000001101033200000002000000020000000203000002000000001101011111111002000000020000000000000000000000 +00000000000000000000000000000000001001103000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 +00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 +00000000020000000200000000000000001101011111133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001001111010133333333033333333033333333033333333033333333033333333033333333003300200000000000000 +00000000020000000200000000000000001100000000011000033000000033033000033000033000033101033033000000033000033003300200000000000000 +00000000020000000200000000000000001010101111033333333033333333033000033000033000033001133033000000033000033003300200000000000000 +00000000020000000200000000000000001111110101033333333033333333033000033000033000033101033033000000033000033003300200000000000000 +00000000000000000000000000000000000000000011033000000033033300033000033000033000033001133033000000033000033003300000000000000000 +00000000000000000000000000000000000000000011033000000033003330033333333000033000033333333033333333033333333003333333300000000000 +02222220022222200000000000000000000000000010033000000033000333033333333000033000033333333033333333033333333003333333322002222220 +02000000020000000000000000000000000000000011033000000000000033000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010031000000000000003000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000010011002222220022222200222222002222220001100100000000000000000000000000000000002222220 +01010101010101011111111000000000000000000011010102000000020000000200000002000000010101100000000000000000000000000000000002000000 +11111111111111111010101000000000000000000010011102000000020000000200000002000000111100100000000000000000000000000000000002000000 +00000000000000000000011000000000000000000011000002000000020000000200000002000000000001100000000000000000000000000000000002000000 +10101010101010101111001000000000000000000010101002000000020000000200000002000000101010100000000000000000000000000000000002000000 +11111111111111110101011000000000000000000011111102000000020000000200000002000000111111100000000000000000000000000000000002000000 +00000000000000000011001000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000000000000222222000000000000000000000000000000000022222200000000000000000000000000000000002222220 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000000000000000000000000000000000000222222000000000000000000000000002222220 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000010011000000000020000000010011000000000000000000000000000000000000000000200000000000000000000000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000010011000000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222222000000000000000000000000000000000 +02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 +02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 +02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 +02000000000000000010011000000000020000000010101010101010201100222000000000000000001001110200000010101010101010101010101010101010 +02000000000000000011010000000000020000000011111111111111201002222200010000000000001101010200000011111111111111111111111111111111 +00000000000000000010011000000000000000000000000000000000201022222220010000000000001001100000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000201022222220000000000000000101100000000000000000000000000000000000000000 +02222220000000000010011000000000022222200000000000000000201022222220010000000000001100100222222000000000000000000000000000000000 +02000000001111110011010101010101020000000000000000000000201002222200010001010101010101100200000000000000000000000000000000000000 +02000000001010100010011111111111020000000000000000000000200000222000110011111111111100100200000000000000000000000000000000000000 +02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 +02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 +02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 +00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 +00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 +01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 +02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 +02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 +02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 +02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 +02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 +02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 +02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 +02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 +0000000000000000020000000200000002000000001101000bbbbb00020000000200000002000000020000000200000000110100020000000000000000000000 +0000000000000000000000000bb0bb00bbb00bb00bb00110bb0b0bb00000bbb00bb000000bb00bb0bb00bbb0bbb0bb00b0b0bbb0000000000000000000000000 +000000000000000000000000b0b0b0b0bb00b000b0010110bbb0bbb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b1bb00000000000000000000000000 +000000000000000000000000bbb0bb00b00000b000b10010bb0b0bb000000b00b0b00000b000b0b0b0b00b000b00b0b0b0b0b110000000000000000020000200 +000000000000000000111111b101b1b10bb1bb01bb0101100bbbbb0000000b00bb0000000bb0bb00b0b00b00bbb0b0b00bb10bb1111111100000000000000000 +00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 +00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 +00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 +00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +0381c55000f808281c0e533b1d8a2073881cac762b150a05028c02c80f00728009154a2562b1da00c500c32612e034f0145808b4080a060c4c82fe0009703b381c9c0f4a080701e203c001800000ec029200aa0732180c07030180a45629c018e016a768089c0352006e6b2491cd901a6808dc0c24d64982fa1c40105d280190 +a8af01c43841114e101e783fb0f01a0d150b61204710043d88be04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e1436880000160f600a607219f808783fc1648da7e379fcfc6f3012a2ce47187fbc424c2a161ec428a22667e3c1e8f4793d1e +0f27a3d06c39086280394004e0285008236c030a0bc404301648e60379ee01eb01333dc1fd0de7b0201205a707f33481c0e7783fa0740110430381cfe7982891e09a5e3d1e8fc7b3798623c477068345b07f684490944d18a38c99c1c48f10f1383bac1c0e2187080583fa43b4cd257304080cbb128282894250a0fe909e281c +29bcc05a068347b1da238c998a1fe853379e21a6478369e0f0793c9fcfc608cf2c7e121fe82a068f45512f290991f4fd12638bc2460ca004700c2021bc160c837a13a20a905e23441fd214886f269b8dc6e83fb1be20046f3d45fc89315e38bc1c1fd4b0613f43298f40b3c9e8ff17f483fd0b22f8528a38c911d248ae423311 +88c6621c9d9a464505a48202174dc60251a0c26f2693a1e067a269ba2a64048bfa17626843083e9134dc60394c34e60c60793f91c40047304720cdf1c023786e2bc517f39b1143fe25f6d209483b1910804020caf8c0e07221048c66331108a40319863b471942379e8360c936a1b8bc6d37974dc2e1797a20a707f590d90c0f +430371bcbc6f35cc9de0fe80083f945fca13a87a379e635a44a9df94f6ce7b09004f80a7d020ce716f7986249fd23fa47a3d0f87f095583fb1b8dc3099994a04a612b18628ff981c0e48309fa0ec47a181e4604e3d1e4f132d39d6ac013e029f3f4936495d24bce6a8769c7f8e622920023012652286f02480d274a910e23012 +45907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925 +088e71c462319820fe672ade956fc89942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be7218a807d9ae39e755a337178fc7e3012a0fe70a97aca9c2456177130e6ac5 +155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c284dc1b092479b0b426dec225011b810154812b20478ace11a49137f2b25a1b6bfa46fa3f94040a2d114ae39c79cd8d2366575f6807e16e13860b30988804123110804520170b70b032311888452111216e767f22dc039e1 +7c85c9502d8f10ff22e235dd39628a5726385ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132ca697c24a0da42d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e9ae6770001cc8522b158a653c1691b0df0198a1875cc0ef1a472c52b1ce0 +fe715f8a7e35419203254b83b6999ba0fe71b02b803812ebad27f3bfe90af02c46024e15880e43a11e50c92887156829c6cc8ae6152d8ad8a6fdd18a0160b83f94f356ff8966f2a6eae0290362b158330bc507f203d09ff03697b84b03941fea9bb98cd330ccb5715493ee7bf8402c562bc26acc7ea9ba549d68401dd78c7983 +c299fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09d6b8e61fa58ba09e9ce201ed4a38c2f16ad48e9aa13d4be530d00957c3a64f6af2484000010e87e525f491225e48e8feb30fb9ef94c76665ed4 +a35b1296978f66a666a0594003b04e579d6a6ab5052e9c8585623880088402e4b7cf17c99443a3fb6043e64f1c0e99a09832efe7baf7d9d90345bae73801b502ccd58ee2cc39cc94b3fcdb083dc59c0a1c0e2a96f9e2f8ac6499bd4c07fef57381d18072b5816b78f86adc39432148ec53291db93a453c1396df08ca5783ff41 +ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06fcaa16fdc8f07a36f0b8c0807160360feda632cfe174ad3a9adcff6c16a0c35747a123d1 +71657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683 +ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c00239acc1000a36188c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f30 +1a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d +8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f65 +04a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae +6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8a +a5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1 +919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d08 +4000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a758 +95ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc43331188 +76db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab6952540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa +264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc57 +6514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e207341fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac +3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c48841334d60af76e5c3881c8642234da88cc4620cdc0ad7e7001f808ad54e2fef65c816fc0 +ae703d9b6f200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + diff --git a/beta_v0.2.0.p8 b/versions/v0.2.0.p8 similarity index 100% rename from beta_v0.2.0.p8 rename to versions/v0.2.0.p8 diff --git a/beta_v0.2.1.p8 b/versions/v0.2.1.p8 similarity index 100% rename from beta_v0.2.1.p8 rename to versions/v0.2.1.p8 diff --git a/beta_v0.2.2.p8 b/versions/v0.2.2.p8 similarity index 99% rename from beta_v0.2.2.p8 rename to versions/v0.2.2.p8 index 4a1edfa..9a913e1 100644 --- a/beta_v0.2.2.p8 +++ b/versions/v0.2.2.p8 @@ -840,18 +840,18 @@ function draw_gameplay() -- check mission status if player.health <= 0 or (count_remaining_terminals() == 0 and dist_trig(player.x - player_spawn_x, player.y - player_spawn_y) <= 32) then if player.health > 0 then - message, color, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" + message, c, prompt = "collection ready", 11, "PRESS 🅾️ TO EVACUATE" -- Update mission completion status mission_data[current_mission][1] = 1 mission_data[current_mission][2] = count_remaining_enemies() == 0 and 1 or 0 mission_data[current_mission][3] = count_remaining_fragments() == 0 and 1 or 0 else - message, color, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" + message, c, prompt = "mission failed", 8, "PRESS 🅾️ TO CONTINUE" end draw_shadow(player.x - cam.x, player.y - cam.y, -10, SWAP_PALETTE) - print_centered(message, player.x, player.y - 6, color) + print_centered(message, player.x, player.y - 6, c) -- Only show prompt after timer threshold (0.1 seconds = 6 frames at 60fps) print_centered(prompt, player.x, player.y + 2, 7) @@ -1774,8 +1774,8 @@ function terminal:draw() reset_pal() end -function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, color) - local new_door = laser_door.new(door_x, door_y, color) +function create_door_terminal_pair(door_x, door_y, terminal_x, terminal_y, c) + local new_door = laser_door.new(door_x, door_y, c) add(doors, new_door) add(terminals, terminal.new(terminal_x, terminal_y, new_door)) end @@ -1974,9 +1974,9 @@ function draw_bar(x, y, w, h, bg, fill, pct) end -function print_shadow(text, x, y, color) +function print_shadow(text, x, y, c) print(text, x + 1, y + 1, 0) - print(text, x, y, color or 7) + print(text, x, y, c or 7) end From 96b02d581ac54e5345cf461cd546827f80220a80 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Thu, 9 Oct 2025 21:05:09 +0100 Subject: [PATCH 13/16] Add v0.3.0 clean slate reimplementation - Optimized state management system using function references (78 tokens vs 128) - Implemented map decompression from compressed cart data - Set up 4 game states: intro, mission_select, loadout_select, gameplay - Current token count: 281/8192 (3.43%) --- v0.3.0.p8 | 447 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 447 insertions(+) create mode 100644 v0.3.0.p8 diff --git a/v0.3.0.p8 b/v0.3.0.p8 new file mode 100644 index 0000000..9f1c0f1 --- /dev/null +++ b/v0.3.0.p8 @@ -0,0 +1,447 @@ +pico-8 cartridge // http://www.pico-8.com +version 41 +__lua__ + +-- cortex override v0.3.0 +-- clean slate reimplementation + +-- state management +init_fn,update_fn,draw_fn=nil,nil,nil +current_mission=1 + +function _init() + -- load compressed map data from cart + v=2040 + map_data={ + pack(peek(0x2000,v)), + pack(peek(0x2000+v,1585)), + pack(peek(0x1000,1788)), + pack(peek(0x1000+1788,1402)) + } + + change_state(init_game,update_game,draw_game) +end + +function _update() + update_fn() +end + +function _draw() + cls() + draw_fn() +end + +function change_state(i,u,d) + init_fn,update_fn,draw_fn=i,u,d + init_fn() +end + +-- intro +function init_intro() +end + +function update_intro() +end + +function draw_intro() +end + +-- mission select +function init_mission() +end + +function update_mission() +end + +function draw_mission() +end + +-- loadout select +function init_loadout() +end + +function update_loadout() +end + +function draw_loadout() +end + +-- gameplay +function init_game() + decompress_map() +end + +function update_game() +end + +function draw_game() + map(0,0,0,0,128,56) +end + +-- map decompression +function decompress_to_mem(data,dest) + local bi,bti,di=1,0,dest + local function read_bits(n) + local v=0 + for _=1,n or 1 do + if bi>#data then return end + v=bor(shl(v,1),band(shr(data[bi],7-bti),1)) + bti+=1 + if bti==8 then + bti=0 + bi+=1 + end + end + return v + end + + while true do + if read_bits()==0 then + local byte=read_bits(8) + if not byte then return end + poke(di,byte) + di+=1 + else + local dist=di-read_bits(12)-1 + local len=read_bits(4)+1 + for _=1,len do + poke(di,peek(dist)) + dist+=1 + di+=1 + end + end + end +end + +function decompress_map() + local i=current_mission>2 and 3 or 1 + decompress_to_mem(map_data[i],0x2000) + decompress_to_mem(map_data[i+1],0x1000) +end + +__gfx__ +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee11122222eeeeeeeeeeddddee00000000eeeedddd6667eeee6b6bb6b666b666666666666600000000eeeeeeeedd6667ee +eeeeeeeeee5777eeee5777eeee5777ee11122222eeddddeeedddddde00000000eeedd66666667eeeb7bb22bb66b666666b666bb600000000eeeeeeedd666667e +ee5777eee577cc7ee577cc7ee577cc7ed112222dedbbbbdedddddddd00000000eedddd66666667ee61bbb7b66bbbbb666bb66b6621212121eeeeeeed55ddd66e +e577cc7ee777cc7ee777cc7ee777cc7e1d1222d2db7bbb7ddddddd2d00000000eeddd665dddd66ee1bb7bbb26b66bbb6bbbbbb6611111111eeeee11d5d111d6e +e777cc7ee577777ee577777ee577777e11dddd22dbbbbbbddddd22dd00000000eeddd65d1111d6ee1bbbbb72666bbbbbbbbbb66600000000eeee11dd5d181d6e +e577777ee577777e05777770e577077e11122222dbb7bbbd1ddd22d200000000e1ddd65d1001d61eb7bbbb2b66bbb7bbb7bbbb6612121212eeee1dd55d111d6e +0577707e0e0ee0e00e0e0ee00eee0ee0111222221dbbbbd211dddd220000000011115d5d1001d611611b7226666bbbbbbbbbb7b611111111eeee1d55dddd6eee +0e0ee0e00e0ee0e0eeee0eee0eeeeee06112222611dbdb221112222200000000e1ddd65d1111d61e66b6b66bb66bbbbbbbbbbbbb00000000eee11d5d111d6eee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee7777777777777776ee1111eeeeddd665dddd66ee66666666bbbbbbbbbbbbbbbb00110120eee11d5d181d6eee +eeeeeeeeee5777eeee5777eeee5777eee00000ee7666666666666666e111111eeeddd666666666ee666266666bbb7bbb7bbbb7bb00120110ee11dd5d111d6eee +ee5777eee57cc77ee57cc77ee57cc77ee00a00ee766655555555666611166111eeddd566616166ee61662266666bbbbbbbbbbbb600110120ee1dd5ddddd66eee +e57cc77ee77cc77ee77cc77ee77cc77ee00a00ee766556666665566d01111115eed0d6606d6066ee166666626bbbbbbbbbbbbbbb00120110ee1d5d111d6eeeee +e77cc77ee577777ee5777770e577777ee00a00ee765566666666556d00111155ee02d6000d0006ee166666626b6b66b7bbbbb66b00110120e11d5d181d6eeeee +e577777ee577777e05777770e577707ee00000ee765666666666656d07005555ee02d000020006ee666666226b6bb6bbbbb6bb6b00120110e11d5d111d6eeeee +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee765666666666656d07005755eee20001520101ee61166226666b66bbbbb6bb6600110120e11d65ddd66eeeee +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee765666666666656d07005755eee211015222222e666666666666666bbb666b66001201101111d66666eeeeee +eeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee765666666666656d00005575eee222225ee1ee2e00000000bbbbbbbb00000000000000006626666626666666 +eeeeeeeeee5772eeee5772eeee5772eee00000ee765666666666656d00705555eeeeee0250eeee2e00222200b7bbbbbb00000000000000007727777727777776 +ee5772eee577772ee577772ee577772ee0aaa0ee765566666666556d07005755eeeee112e0ee112e02222220bbbbb7bb00111111111111107622662226666666 +e577772ee777777ee777777ee777777ee000a0ee766556666665566d00705575e000010ee0ee1eee01222250bbb7bbbb0012121212121210766221211111666d +e777777ee577777ee5777770e577777ee00aa0ee766655555555666d07005555e0eee1eee0ee1eee01115550bbbbbb7b0011000000000110766126266661166d +e577777ee577777e05777770e577707ee00000ee766666666666666d00005555e0eee1eee0ee1eee01115550b7bbbbbb00120111111102107611222666622222 +057770700e0ee0e00e0ee0ee0eeee0e0e00a00ee666666666666666d00005555eeeee1ee00ee1eee01115550bbbb7bbb0011012121210110761666211622616d +0eeee0ee0eeee0eeeeeee0ee0eeeeeeee00000ee66dddddddddddddde000555eeeeee1ee0eee1eee00115500bbbbbbbb0012011000110210761666166226616d +66666666eeeeeeeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee5555555555555555521121125555555500110120002101107616661661222222 +66222266eeeeeeeeee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555222255555552222222555555550012011000110210761622611666616d +62222226eeeeeeeeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550011012121210110761226622666616d +61222256eeeeeeeeeec77ceeee77cceeee7ccceeeecccceeeeccc7eeeecc77ee555555211255555555555555555555550012011111110210722266662266116d +61115556eeeeeeeeeec7cceeee7ccceeeecccceeeecccceeeeccc7eeeecc7cee555555222255555555555555555555550011000000000110726116666261166d +61115556ee1111eeeecccceeeecccceeeecccceeeecccceeeecccceeeeccccee555555211255555555555555555555550012121212121210226611111211666d +66115566e111111eee0000eeee0000eeee0000eeee0000eeee0000eeee0000ee555555211255555555555555522222220011111111111110766666666266666d +66666666ee1111eeeee00eeeeee00eeeeee00eeeeee00eeeeee00eeeeee00eee55555522225555555555555552112112000000000000000066ddddddd2dddddd +55555555555555515555555511555555115555555535535555555555066655550666555555556660555566600000000000000000666666666666666655555551 +55555555555555115555555501555555011555555533533555555555066655553336555555556660555563330666666666666660666666666666666655555511 +55555555555551115555111101111555001555555553553511111151066655550633355555556660555333600666666666666660661111111111111655555110 +55555555555511105551110000111155001155555353553511001111066655550666555555556660555566600666666666666660661212121212121655551100 +66666666666111005551100000001155000116666363363600000000066655553666533355556660333566600666155555516660661166666666611655511000 +66666666661110005111000000000115000011666366333600000000066655553333535555556660553533300666515555156660661261111111621655110000 +66666666661100001100000000000011000001166336336600000000066655550363333555556660533336330666551551556660661161212121611651100000 +00000000000000001000000000000001000000000000030000000000066655550666555555556660555566600666555115556660661261166611621611000000 +00000000000000001000000000000011000000000003333000000000066555550000001155556660511000000666555115556660661161266621611615555555 +66666666661100001100000000000015000001166633663300000000061155550000011555551160551100000666551551556660661261166611621611555555 +66666666666110005111000000000115000011666633366300000000011155550000115555511100555110000666515555156660661161212121611601155555 +66666666666111005551100000011155000016666336366300000000011115550000115555111100555110000666155555516660661261111111621600115555 +55555555555511105555110001111555000115555355355310110011000115550011115555110000555111100666666666666660661166666666611600011555 +55555555555555115555511111155555000155555355355511111111000011550111555551100000555511100666666666666660661212121212121600001155 +55555555555555515555555555555555001155555355555555555555000001150661555511000000555516600666666666666660661111111111111600000115 +55555555555555555555555555555555011555555555555555555555000000110666555511000000555566600000000000000000666666666666666600000011 +55555555555555531555555555555551000000111100000055555555000115550011555555511000555511005555555115555555661161266666666666666666 +55555555553335335155555555555515001111155111110055555555000115550011555555511000555511005555551551555555661261166666666667777776 +55555555333533355515555555555155011555555555511055555555000115550011555555511000555511005555515555155555661161262121212167666666 +5555555555355555555155555555155501515555555515105511111500011555000115555551100055511000555515555551555566126116111111116766666d +5555555553355553555566666666555501551555555155105510001100001155000115555511000055511000555155555555155566116126666666666766666d +5555555555555333555566666666555500155155551551001110000100001115000115555111000055511000551555555555515566126116121212126766666d +5555555555553353555566666666555501155515515551101000000000000115001155555110000055551100515555555555551566116126111111116766666d +555555555555555555556660066655550155555115555510000000000000011500115555511000005555110015555555555555516612611666666666666ddddd +55555355000000005555666006665555015555511555555100000000000000006366633666663666666666666666666610000000000000016366666666336666 +5555535500000000555566666666555501155515515555110000000000dddd0063666366666636666666666666dddd6611100000000001116337777667377776 +535553530000000055556666666655550015515555155510100000010d6666d06366636633663666666666666d6666d655110000000011556736666637366333 +53355333000300005555666666665555001515555551551011000111d666666d636333666333333666666666d666666d55511000000115556736666d3333636d +553555530303000055515555555515550011555555551100511111151d6666d26363666666336633666666661d6666d255551100001155556333366d6363336d +5535353303030300551555555555515500155555555551005555555501dddd2063336666666366666666666661dddd2655555110011555556366366d6366366d +53353535000000005155555555555515001155555555110055555555001222006636666666336666666666666612226655555511115555556366336d6336366d +3333353500000000155555555555555100011115511110005555555500000000663666666636666666666666666666665555555115555555636dd3dd663d3ddd +5249a052361b05ba0025b2d1a8502698a51044058251a403902606aa00e0b4081b41a450264880411a007962100ef370269a6490d64021b314010ac05a20ca41 +a470f2604ba20c95024405422216c18d0603c1e0608000c740416d94300c00380b40024090ec40fb20305104480ea464381628809a406b08a51c9506bb09f18b +0391c10cf60850e1f8748a7083018040738990023dcbf10dee00328902020434860180e673b1dc6e8d0192eb4050706e302902e8077184724b20ee20fb708203 +14cfd87c2b47d1a80094703f08351cdf16ef7801830fb1704fd3b10625482d4fc0e0105c70af3289f47c8af043706d507480c2382204580ae36a3f97483df02f +d371940dbf1608c261c048352c97d013f72814683278705f489224c22a65070234c896c0ea06f3e1f8038485f74b25ecb378c16a38ff2cd7c04089b24ad32d00 +6eb1511800091887f291aaf36ea2f891a912e4050e836dc9f3a8f06eb718308c9080663f1c6e82d02145735c45d86e30112000a036182fe11a4d0df9381062a0 +7ea04b62390a01048baf092703271678af14df32eb05e3832842962a40d0014030e906cf1fe985761a0839333779139c99325c07f9349062a07e212b71b0bc5c +3d17a3f17e709ec3f91aef78910812c12ce673314a04482d1f63454ed47304001eef1d18349c4edf918e68179c434aeb3771933711ea965a4021c81b0c861acd +1bfb23c3578cedd573f0782f9f82c641f1a6f0fe8350789f5c4fd86c1e07cbabe50813715ae70b0a951c1a0e1f402771b00e24768a761967319e535f0321b417 +2835c68029d1dce02ff89f54cf5ec11d85989c91af935a536e0a107ccfbc6a3e978eb9a04929bd81d039f70893c42f87f3e9f8057c8310cbf34d0cf6d3b9cc70 +821cc2f60dc7f3630c97d81bff86b691a7109ef025c995f99a824492924171c845a260f846ff113093c9c1eb2db466c5f8be69c0703e4c8c85bdf02ad9905ccf +6eeb663d97ca0c073aa4f458d00b7c82f0926bf35ed237625c1372ca3480bb128e61b0038188439f234271a8f59a503ff8adb4ce7aa545303317083b133b3abf +a972ef170082f3d931228650d42fe92f645292cb07a9a453caf38c3d5027d30c546a8b74394c1b8461b2513de5a6af080793c27e733f3a1c245a36ef47c1329b +6cf01f135f28ae2519d42f1aa3f2998e71643a88a4258eae5d408d12d9f5ec6afa5525d3e152ae148bf34b5d69f0ae7463b1b2a345dba7d55a7080b3f05f4a10 +2982ef0515a35ec4d125f14eb0517966071590a4f7cd704d3d13380b4e7c0d084eb36449598e87d27a108d991635743b5774a0f0def54f8a9f559fba9413f704 +8d27697c6539d919541f2acd3dff92c81100a8143311884c264210ac9d884402914aea17083c54f5091600b00361cac265efa50c5974acecb5a643f8aa8d0e18 +2790b454d78ba07edee155384a9ce592df09203b97683b78183f3baf1d08eafd751d3646f18d61b24994545af2726a8cf3cff1effb8cd0603806ff6fb7235dd5 +7d00b24137fa9509c44c304300cc880402117ae307ff05cd4ba40954106808df0ecf0a0c372ec342ca51e2033931fcaa9d292a5568341b8dbc23279ce249a031 +73b34d5a02b8d2b24953ac333102ef6904a53a4b06e3b21dc8a9ab96b5ee23204c3397ce4dd0604eb923ca68ac935202b8325b39eff85e2c116e1b91f585e9c4 +55c46c063c9d0e17f2c355ff361faf5f4a7503b9fc9d05a2120133121f04308c784e5ac86089f3cf0af3b0eeb42da751a45c264a5531f166a559452e80dc6ecf +6199c0ffc9342a11f581fa017c0aedd429085af3cc2aa186cfec23cf9f00e7733df49f5124738936ff5a83f38ac12f87cc9c165133e504b43d69d8fc539c0066 +081a660f1b0c1df7c92a49837d17aec4497209fc6c3f9fcfedf74dbd94df6eff503810cfff705fc31caa57a5548e60cd127ac111533c793e29636cbdf8d2cbc9 +0cbfb45e9a8efc9eb6ceec43ff3b4ff3cd1f65b53d4a84845f300bff881b1c08ede77892d68ede1005f78df544f3bdf7cc459389f01fff2499bf035ee1ca8d3b +8494467201f7df3612e0c19670ffec2f495e0b15d7e220f9c0453c9c3c51b65fc0c9b7b2f469baa44f4c97c3745c321f012b86a04611095f6c303a54080011b3 +e204fa614295f42d611ec7daf4c7aec4d77af3490df673f9fc6c3f1f8ab58136d3e136ee178f34cde9f26850027d50280e43050183fbee07bc0878db5838aa57 +5f78080d96c6085402918846251041c864aa9dde85de64d7e73593cfcfe7f354cf78597a326f1271a984b0603083e9b70389b04328894fd6f9af1fc06f680de7 +c32869ef35f0b296f94ceb3cefc044211389ae7000c5c9d87ece235410f8bb5c9cb6084b5fc1deef57672a99eb0190fb69789a85479e930ff78efc990aedf793 +41e45ca3ff19329d93def74f55a17e6347fff612eb09bcf9a3703058245d79de7f4cf3bd35a8f6f305c6aaef7760b7485b1ca520f1c541e99189d40251100e00 +462b1845aa0582c1cc30800011ae0040f603a10ae2081782a900be0065830c6374080d96cde070181fc9046e3897af0040f7189921ac50211b45ca458241a450 +2618859a50238259a80d21229885de005c081a4102e3442c9308ed4038132954248a0499c18d0683c1c0603016cb4f402024000808341062216ec2489ea0580b +188c5447f2498e068381d0663f974ae0c242b1c001b08200c4202eb05c10975369846e318701804038cf2c830ac1643fc7f38090013438af7804014083f3cf44 +e672ac000011c06630978f4dd0e11cc52a61648fe2010c65b25178022a05208353012c008af673b934094132108ae80620e16801ff84f2196cf07e713608b1e1 +260307f72a95040a05a249360517158350413525b28cc438bf109c60319fcf2d703059cc703e1c6e83724127d078222905650502e6a3aeb0d3b136eb643f906a +f24e03d3c0d06c30c4f1ccf0302db89c14aff4728ff74e76a4c240253018ac17a583104a858d7da12609a959cc628ff389c16887c3e112493c00c51049915231 +d09cc51806148523e05af136f89fe16c708a3f41f16ee48ef673f9dc7e8ff30c2ff366c3608c0b0ae276391cef92de5152d81784993620293892951a0e5f0150 +d3200e6d05080942e1a4f04ec1e034cf2eef175994f30943b70321438112840834c7f8880c246892b1020b1bbf85e8495c842f97681c3cd45644170c329768f0 +aea2389ff90aef7c394d044161b238bf4cd12200141b850761b08896b1cc602993ea4588c6f89fd4019c6010c59405ed061301803319231227f04f8609ae7cc5 +1c07729561439889558336331cdf2eef0e8b57c1162d04b8d349d0bc4d183967229d0028642211844a204ef10d457403a84e850505986f449fa0004284499e94 +1238268b0495cb0c274911b0104b443314cffd343392b94eef17c1828f49a2498003381984403ee107f712c5dd883d5338d9afa08a6a041045f34e3964b33ab8 +e40b6ab301080cc515846d94d920d9f06c3f8ac1e08503f15eaf2be7839a8a0106b0e024141a00160bcddf884d001abf3dcf0c11ff82f79bac8950a44ba9ac7c +df76b3153c82494a348580b203219a48455c024000a096704376389a600eef1da593ea5c75c43cf04f9a1f14df46ef074535c758291a745339ba3d7cdf3a200d +e7991701ca6506b99a2d400aef6a3840e5e8f68ab1d3685c177f98a5c3d8a9f15e725838bf6182ef3db59940952a379ac0b1c8d6655a166f17a295c8c102a1a5 +76c56f786f54dfc2690bffa6f7de6665f474f55b489ef492462965e1931cc334053c7435839cd38c6e8153dc04619cbe3498ca19899023f7baa6d1f5ecb19f85 +40837dde8aef59ac83f35a472d081b276978cf7d22aea0498db5b4dc9becf0be472df8bfad832adb492292e8caaae058d8d647baaca1cea56d3ca25e598ee658 +64f63b9f452ac60f743add61c028643311c84c94bf1981cc04e2d4ef0700196b83f36f00c010d2260c9f618c4a209240cc04181e01804c805019e8c65856489a +174d5a5a55703edd15210f3ba04c68a71e710eff559d899942538a108043cb549dca9aef6e2210205a72734b6934f11a6d07da8677596ea0e3b4634fdbec1647 +7e005be886dad28a7902d06894db2446558476bf179acb0c958c2bfcce7e40597d3248446bdb72f1425ca65620281a96f39cc60854225c881ea5272304e50671 +d2de093660ad1a33a16da8b2ce99cb2ca5cc213cefb0176a3253f8315374a93ed209b24d159fb2824113bda3d2d94a3abf542813e267bbb4405577accb5230cc +4ba516029c85a782b2e6b6f774fb9077b8bf9e165c78d772e954bc627654764d2ed13e65a960d71491c4ffe26303213aba6373ab6c51563575f54afa78dff49a +4a012f6ddc29e1188a1bb73c3da2b43f06ffabd986b3eff8eb7cba0075d896d49a52ca05818a219720171b20b50875f45706f30f320f7704300e120f028f81c7 +01e3a0f160f038782c301e120f0a8f85c703e3a1f1e0f07848f01a4f388300019f6cee918b0e0109d08240c2306a40f6108a18c1bb783c83f14b20e170ff30ff +28641c4dd93c831441b1a4c07c9055382814cf1a4315b98740c1b1e411961097581d04d316c6054a8ff14af22e70fee03c28210cce7c0dcd83f1ca20e070efe3 +18ff0cff0ef705cb8886c954a4717ed09338cf0c5a12f71bf7854cc345e41358205ab8ff1c151a9d11f384fdc927637172c12ec81f6cff3e2607ff8701c1d52a +243b6174b8cec47806d20bf38f904369a8a33372f5b9f8ec700efc3f1a9f8dcf0fe731f3e3f1f9e8080000000000000000000000000000000000000000000000 +__label__ +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000002000020000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000200000002000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00020000000200000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000020000000200000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +02000000020000000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00002000000020000000000000000000001101000000000000000000000000000000000000000000000000000011010000000000000000000000000000000000 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000010011000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000001011000000000000000000000000000000000 +00002000200002000222222002222220001001100222222002222220022222200222222002222220000000000011001002222220022222200000000000000000 +00000000000000000200000002000000001101000200000002000000020000000200000002000000001111110101011002000000020000000000000000000000 +00000000000000000200000002000000001001100200000002000000020000000200000002000000001010101111001002000000020000000000000000000000 +00000002000000000200000002000000001101000200000002000000020000000200000002000000001100000000011002000000020000000000000000000000 +00000000000000000200000002000000001001100200000002000000020000000200000002000000001001111010101002000000020000000000000000000000 +00000000000000000200000002000000001101000200000002000000020000000200000002000000001101011111111002000000020000000000000000000000 +00000000000000000000000000000000001001100000000000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000001101000000000000000000000000000000000000000000 +00000000022222200222222000000000001001100000000000000000000000000000000000000000001001100000000000000000022222200222222000000000 +00000000020000000200000000000000001101011111111000000000000000000000000000000000001101000000000000000000020000000200000000000000 +00000000020000000200000000000000001001111010101000000000000000000000000000000000001001100000000000000000020000000200000000000000 +00000000020000000200000000000000001100000000011000000000000000000000000000000000001101000000000000000000020000000200000000000000 +00000000020000000200000000000000001010101111001000000000000000000000000000000000001001100000000000000000020000000200000000000000 +00000000020000000200000000000000001111110101011000000000000000000000000000000000001101000000000000000000020000000200000000000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000000000000000000 +02222220022222200000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000222222002222220 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +02000000020000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000200000002000000 +02000000020000000000000000000000000000000011010000000000000000000000000000000000001101000000000000000000000000000200000002000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000001001100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000101100000000000000000000000000000000000000000 +00000000000000000000000000000000000000000010011002222220022232200222222002222220001100100000000000000000000000000000000002222220 +01010101010101033333333033333333033333333033333333033333333033000233300002000000010101100000000000000000000000000000000002000000 +11111111111111133333333033333333033333333033333333033333333033300333000002000000111100100000000000000000000000000000000002000000 +00000000000000033000011033000033000000033011033002000000020003333330000002000000000001100000000000000000000000000000000002000000 +10101010101010133111001033000033033333333010133002033333333000333300000002000000101010100000000000000000000000000000000002000000 +11111111111111133101011033000033033333333011133102033333333000333300000002000000111111100000000000000000000000000000000002000000 +00000000000000033011001033000033033033300000033000000000000003333330000000000000000000000000000000000000000000000000000000000000 +00000000000000033333333033333333033003330000033000033333333033300333000000000000000000000000000000000000000000000000000000000000 +02222220000000033333333033333333033000333222233000033333333333000033000000000000022222200000000000000000000000000000000002222220 +02000000000000000011010000000000000000033200000000000000000000000003000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000003200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000010011000000000000000000200000000000000000000000000000000000000020000000000000000000000000000000000000002000000 +02000000000000000011010000000000000000000200033333333033000333033333333033333333033333333033033333333033333333000000000002000000 +00000000000000000010011000000000000000000000033333333033003330033333333033333333033333333033033333333033333333000000000000000000 +00000000000000000011010000000000000000000011033000033033033300000000000000000033000000033033033000033000000000000000000000000000 +02222220000000000010011000000000022222200010033000033033333000033333333033333333033333333233233000033033333333000000000002222220 +02000000000000000011010000000000020000000011033000033033330000033333333033333333033333333233033000333033333333000000000002000000 +02000000000000000010011000000000020000000010033000033033300000000000000033033300033033300233033003330000000000000000000002000000 +02000000000000000011010000000000020000000011033333333033000000033333333033003330033003330233033033300033333333000000000002000000 +02000000000000000010011000000000020000000010033333333030000000033333333033000333033000333233033333000033333333000000000002000000 +02000000000000000011010000000000020000000011010000000000000000000000000000000033000000033200033330000000000000000000000002000000 +00000000000000000010011000000000000000000010011000000000000000000000000000000003000000003000033300000000000000000000000000000000 +00000000000000000011010000000000000000000011010000000000000000000000000000000000000000000000033000000000000000000000000000000000 +02222220000000000010011000000000022222200010011000000000220222220222222000000000000000000222232000000000000000000000000000000000 +02000000000000000011010000000000020000000011010101010101200000000000000000000000001111110200000001010101010101010101010101010101 +02000000000000000010011000000000020000000010011111111111200001011111000000000000001010100200000011111111111111111111111111111111 +02000000000000000011010000000000020000000011000000000000200100000001100000000000001100000200000000000000000000000000000000000000 +02000000000000000010011000000000020000000010101010101010201100000000000000000000001001110200000010101010101010101010101010101010 +02000000000000000011010000000000020000000011111111111111201000011000010000000000001101010200000011111111111111111111111111111111 +00000000000000000010011000000000000000000000000000000000201000100000010000000000001001100000000000000000000000000000000000000000 +00000000000000000011010000000000000000000000000000000000201000100100000000000000000101100000000000000000000000000000000000000000 +02222220000000000010011000000000022222200000000000000000201000011000010000000000001100100222222000000000000000000000000000000000 +02000000001111110011010101010101020000000000000000000000201000000000010001010101010101100200000000000000000000000000000000000000 +02000000001010100010011111111111020000000000000000000000200000000000110011111111111100100200000000000000000000000000000000000000 +02000000001100000011000000000000020000000000000000000000200110000001100000000000000001100200000000000000000000000000000000000000 +02000000001001110010101010101010020000000000000000000000000011111011000010101010101010100200000000000000000000000000000000000000 +02000000001101010011111111111111020000000000000000000000200000000000000011111111111111100200000000000000000000000000000000000000 +00000000001001100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000101100000000000000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000000000 +00000000001100100000000000000000022222200000000000000000001001100000000000000000000000000222222000000000000000000000000002222220 +01010101010101100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +11111111111100100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +00000000000001100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +10101010101010100000000000000000020000000000000000000000001001100000000000000000000000000200000000000000000000000000000002000000 +11111111111111100000000000000000020000000000000000000000001101000000000000000000000000000200000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000000000000000000000101100000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000222222000000000001100100000000000000000022222200000000000000000000000000000000002222220 +02000000000000000000000000000000000000000200000001010101010101100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111100100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000000000000000001100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000010101010101010100000000000000000020000000000000000000000000000000000000002000000 +02000000000000000000000000000000000000000200000011111111111111100000000000000000020000000000000000000000000000000000000002000000 +00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000000000000000000000000000000000 +02222220000000000000000000000000000000000011001002222220022222200222222002222220000000000000000000000000000000000000000000000000 +02000000000000000000000000000000001111110101011002000000020000000200000002000000000000000000000000111111010101010101010101010101 +02000000000000000000000000000000001010101111001002000000020000000200000002000000000000000000000000101010111111111111111111111111 +02000000000000000000000000000000001100000000011002000000020000000200000002000000000000000000000000110000000000000000000000000000 +02000000000000000000000000000000001001111010101002000000020000000200000002000000000000000000000000100111101010101010101010101010 +02000000000000000000000000000000001101011111111002000000020000000200000002000000000000000000000000110101111111111111111111111111 +00000000000000000000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000001101000000000000000000000000000000000000000000000000000000000000110100000000000000000000000000 +02222220022222200000000000000000001001100000000000000000000000000000000000000000000000000000000000100110000000000222222002222220 +02000000020000000000000000000000001101011111111000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001001111010101000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001100000000011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +02000000020000000000000000000000001010101111001000000000000000000000000000000000000000000000000000100110000000000200000002000000 +02000000020000000000000000000000001111110101011000000000000000000000000000000000000000000000000000110100000000000200000002000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000022222200222222000000000000000000010011000000000000000000000000000000000000000000000000000100110022222200222222000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000000 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000020000000200000000000000000000000010011000000000000000000000000000000000000000000000000000100110020000000200000000000200 +00000000020000000200000000000000000000000011010000000000000000000000000000000000000000000000000000110100020000000200000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000011010000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000222222002222220022222200010011002222220022222200222222002222220022222200222222000100110022222200000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000000 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000200000002000000020000000010011002000000020000000200000002000000020000000200000000100110020000000000000000000200 +00000000000000000200000002000000020000000011010002000000020000000200000002000000020000000200000000110100020000000000000000000000 +00000000000000000000000000000000000000000010011000000000000000000000000000000000000000000000000000100110000000000000000000000000 +00000000000000000000000000000000000000000001011000000000000000000000000000000000000000000000000000110100000000000000000000000000 +00000000000000000000000000000000000000000011001000000000000000000000000000000000000000000000000000100110000000000000000020000200 +00000000000000000011111101010101010101010101011000000000000000000000000000000000000000000000000000110101111111100000000000000000 +00000000000000000010101011111111111111111111001000000000000000000000000000000000000000000000000000100111101010100000000000000000 +00000000000000000011000000000000000000000000011000000000000000000000000000000000000000000000000000110000000001100000000000000000 +00000000000000000010011110101010101010101010101000000000000002000000020002000000000000000000000000101010111100100000020000000000 +00000000000000000011010111111111111111111111111000000000000000000000000000000200000000000000000000111111010101100000000000000000 +00000000000000000010011000000000000000000000000000000000000000000000000000000000000000000000000000000000001100100000000000000000 + +__gff__ +0000000041000100000004040400000000000000000000000000000404000000000000000000800000000104000011010100000000000000010101010000010103030303030303030303030303000001030303030303030003030303030000010101030301010303030303010100000001000303010103200000002001050000 +0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__map__ +0381c542a15400ce005e070394cec762881ce22a0001cac762b150a2762840318a05528958ac7628140ab00232612ca05028c09880e5480a2942022b020280aa9409905bc0012e0767039281eac0b480f0682801a01c00000ec018c0f005503990c06038180c0521500054538041c02ca018d00073840788d649239b204c8768 +0b3c045203c90154359260b6871004174a00642a2bc07120747022480f2c070a0fe83c06834542d84811c400073881cbe04379bcfc6f379bcfe6f0d8ac0804806140352004c6d8051810de7b301248e603713a01eb0035808981203450204021bcc05983e14180c002c1ec014c0e433f010f07f82c91b4fc6f3f9f8de6025459 +ca13870ff488298542c3d8851443ccfc783d1e8f27a3c1e4f47a0d87210c500728009c050a01046d80614178808602c91cc06f3dc03d602667b83fa1bcf6040240b4e0fe6690381cef07f40e8020cea7f3cc1448f04d2f1e8f47e3d9bcc311e23b8341a2d8bfac3d480e25134628e31e707123c43c4e0eeb07038861c20160fe +90ed33495cc102032ec4a0a0a25094283fa4278a070a6f301681a0d1ec7688e3266287fa14cde7886991e0da783c1e4f27f3f18233cb1f848c220a81a3d1544bca42647d3f4498e2f0918328011c0308086f058320de84e882a41788d107f485221bc9a6e371ba0fec6f88011bcf517f224c350e3c0707f52c184fd0ca63d02c +f27a3fc5fd20ff121f21e4429615c474922b908cc46231988723159191c169208085d371809468309bc9a4e87819e89a6e8a990122fe85d89a10c20fa44d37180e530d3983181e4fe4710011cc11c8337c7008de1b8af145fce4d6507f79c414429662a7076322100804195f181c0e4420918cc6622114806330c768e32846f3 +d06c1926d437178da6f2e9b85c2f2f4414e0feb21b2181e8606e37978de6b98abc1fd00107f28bf942750f46f3cc6b48953bf29e01cee4e007f013f2850188a0738b7bcc3124fe91fd23d1e87c3f84aac1fd8dc6e184ccca502530958c3147fcc0e0724184fd07623d0c0f230271e8f2789969ceb56009f3c16803dcecd0e060 +36495d24bce6a8769c7f8e622920023012652286f02480d274a910e2301245907f587fc1e4bb0a3237184952bf29e699e8162b9d9aced92014d3e529bd1c007e618a6d8f091360fed0ef29a7947550c049948a01284453a33a4591ae10c46024cf54a90871ff29eb144c889c7e9ee15078a57e47b99c982c561c9d99cee0c160 +c9f52ce8ce003f38c92ec7848bc602d43fd09a5e9d424f50a0fe6619d925088e71c462319820fe672ad11569089942e2a241d35c8f107f434c0b5a0fc53e249b098ac193f43a7235404a6e451ef787fd490d28da92cd3a4abd1fd6114530c48fc1ca39250c452b2811ac92483051f8e982c793c4efde9c45461490631ae5be75 +c68807e4dfca5bc555a337178fc7e3012a0fe70a97aca9c2456177130e6ac5155bca55c94782aaa19b690c67930928b153f2a26254b8a25c640800118e4a6900b5807f98cd709c2853b980d84923cd85a136f6112808dc080aa40959023c56708d2489bf9592d0db5fd237d1fca02051688a571ce3ce6c691b32ba6b403f0b70 +9c305984c44020918884022900b85b85819188c4422908890b73b3f916e01cf0be42e4a816c7887f91711ae2a2f16b485ff433ce02c945f3a1d1dd35a01f44b39c9788374d6728132e76b25c4a0d9c2d5689b6733056548dd5af2b869c270a2c064594a145ad617e94e7485745db126c0773f2ba6a403daf04742f3a0015359e +9ae6770001cc8522b158a653c1691b0df01988c155b04ee60778d23962958e707f38afc53f1aa0c90192a5c1db4ccdd07f38d815c01c0975d693f9dff48578162301270ac40721d08f286494438ab414e36645730a96c56c42bae8c500b05c1fca79ab7fc4b3795375701481b158ac1985e283f901e84ff81b4bdc2581ca0ff5 +4ddcc66998665ab8aa49f73dfc20162b15e135663f54dd2a4eb4200eebc7731399fbe1d529698308092812f60b4cfec3f94efcf17e543d7ab6641fd2b3ed31f83418693c758750d872fbc61dc2994363a962717e6b54a810090dd8af0e50fce53c79d93ae09da33cd8492c5d04ecf4d00fca51a6178b56a474d4e1dab5e81720 +4abe1d2e6add1a42000008743f292fa48912f24747f5987dcf7ca63b332f6a51ad894b4bc7b35333502ca001d8272bceb5355a82974e42c2b11c40044201725be78be4ca21d1fdb021f3278e07170312682a0cbbf9eebdf67640d16eb9ce006d40b33563b8b30e73252cff36c20f71670287038aa5be78be2b19266f5301ffbd +5c2915b818957a4b5816b78f86adc39432148ec53291db93a5c252366df08ca5783ff41ee2108c071564b535b666e2f609d28fed227fb73a7014f7f278d56bda16d78a4fe507f821f0a0acce67e379b76524040210a83450ff6cff192fa30996fc266cfcf17cf7a73a3f9cce576f337467c0c1dda9a804d27f5d10af4e48d06f +caa16fdc8f07a36f0b8c0807160360feda632cfe174ad32b53cff3c16a0c35747a123d171657d30f1a0bceab69c4f1271b6c3db696f78e1ff27f3d723080908322e9b8dc7b3d1bf7f45b8450001cefbfe2aedadeb2b1feb8be7c31be396e2ca94673d6aa0c0363037d1f9fa049bde28005074ce7e3c1b79ea5d384e9c91f8a5c +6e293fae7f92cb698ac6e801ea5ceffe71538a7fdd0e538b4999a2ef3a71748f7927683ffee8d478533ff8652fb49ddf23f9ab0ba52ff5f6d16df139773d2d6bffe01345c7a67a94d1d398ee0ebbef2181be0fcfa5f3e2f17778a2034742111886662310c85cb03e5b110ceb3ff6bc9b5e2909ddac4ed6c0239acc1000a36188 +c700f7359bcf47a8011400cc087b301248e600207039010480121bc605e181b8bc6f301a40f000538c01d09650281460144563b158a85080021ccc1045383141b20a646d8222c0114c0592d900804023198890052801217201ee6381c99e8de6f8411810df07f383dc420d4f709b205980b50796843295cd86030148ac53301c +21a130c53800541fc8de6d86a91349d0db4301aa16450aa3289d8a30b15802bc2e521d8e0483ff934dc6e181bcf40b15980930ff41540092214443835b812004a3006030560c85be9b4de5d370b85e5e2e9bcdd0002301a48f0b22800fc4ae22db10fff861141f8e0fee070a85a30e42c068341b1a06024011408300d8ac361c +8d9a02e10cc4d2ec75c87c3f8eda8120fe4603786c562b0e1bcdb1255803344b1e3f6504a237c5ffe0fe67a3d06c0804301663fea2513481c83a0738802349308251841f4a2fe70ab206473089a6e2f4950e302707f58eb9c1fce0a2b05868d85812292507f6807b4040e15a60b15c1fc8b517f8068347a070394e06f303a58d +291e0df16283597a5769084293fc12254241b0e43e1c9a5e938240c5648b26036124ae6b359bcff1abf80b949f680e072c4d33268ac7937980c52de5969ac18ce5ac724328ff9120c32a143d1749d1d779bb9c0c4a6273317438918c31862311048c4622ccc188a6620110805c0381c0100099a621188845214d3dcc6603f1e4 +f47f8d2987675053435834ac90d4c05a9ab9cd9123a4919848081180933ca22acf0c8aa5103cf38a7bc40034180de7e301260d8871000027e081d3881c542a83f5002698c502601c0106f3000008e613f511488521c4039c4af3c358f54d0fc8ae609839c3f08bb3a638cfacbbd262ee529e7114cca073b81cc2043f180e4513 +bcfbea1a6747233b1468e2c739a629ac9207001de7206002b984fe7a3d1f8a53a239d1919e486507fb96b94bfce9cc534f4a4ac4dbce42e530d33f1fe4e494672321b0094c8338148ed45f232d3d8c0105339dfb51c58d93f823dd2b480101b403800e2683f1e0f27f355478a0fe658961b524262fed51439802d5c569de87e3 +dcd99244c66e301aa8b4c7f379fa60464f83fcc14de780d3b0283fa0003a00ac999d084000010cea008c02d7acaa32d520736555d20fe949533d13692a9497287f94ea8a82865e05830186f30924e258379e2bf8e793f57c500125438e9b47562b5ab0ff394b94a6e2400b5eb380171724fe84d2e9ba92a96512a39a98092573 +051e0ecfe61b15870fe602501c90043c1e60f890053b3aa000593f123448fcc9d0a75895ada54a3fe507f585195a75a3fe64b28144ac563b5a85abe4a4c23c9fce81eb4957a399180926883f958a486108030e589c80e68a44a48388fd18223e47fd26a66689dfa5a288de1b065658eace571541e0345b07b8acd9c9fced3c72 +d13b50c9ae1ce527f3a4aed7bccd12b923797460791847fd0c2698e49118cc4333118876db43ac9fe25a6523b2060c0360c37d510ec2642b37c87525e0771b99ff1da78cff23e4379faaac50052bab919cc15612a4af47fce7dc57fe3181e632477e74bb431988c662311a4b6907f92e8c0f4308bd241f8c3919d28b834eab69 +52540d2c291492180932922cc59cae565495bb161030c049995956a5606467f977a5fa264a0945458139c29cf158a1b0e5bb8ea2681da8b9193018465a5820f2950b2c39c6419765ce50ff3ab9a5a01ed5661c9ffa56a565ea4783ce38170d4d50c29a819c65fe989728f895b597238f59341e0b40000341f8fc7f35567d6a50 +771ebca42980b30e74c739de020f40b937912490619272414621eed34c6a6e24dfdc576514c86949ecf1d8729d4af8999cfe7b379aa606d600287f90322f8f07f6bff9e524b19e45e81fa5a1c8161ca60991cc02b061b4f537149f8167958597652068b6b9ab55038311980de1cbdda82c381b942160c0af2a82a150f4e20734 +1fcfd57c2a3e955f4ac2087e99211e8f768069b0159fd2f9f182343686efde857300ac3957e2a7919f8fe7e9b79df8566b257ea306834557f123bd54d717691352c0fb66b4a4fcc74211c4867583fa1de076853a9f15740a0aeb5482d2b9ebfd8cd7d7f3a6006349a536f4900ce9d2b67fb815f9bf6e07bf0ae75136209c4884 +1334d60af76e5c3881c8642234da88cc4620cdc0aece5001f808b554e2fef65c8177c0ae703d9b6f20000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +__sfx__ +151000000c0730000000000000000c013000000000000000266550d0000e625000000e615000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61500000 +d1100000021450e14502115021450212502115021450e11502145021250211502145021250211502145021150f145031250311503145031250f1150314503115021450e1250211502145021250e1150214502115 +c3100000027500e73002710027500272002710027500271002750027300271002750027200271002750027100f750037200371003750037200f7100374003710027500e7300271002750027200e7100275002710 +a71000000c0730c0000c033000000c023000000c013000000c003000000000000000000000000000000000000c0730c0000c033000000c023000000c013000000000000000000000000000000000000000000000 +151000000c0730000000000000000c013000000c0730c000266550d0000e625000000e625000000e615000000c0730000000000000000c013000000c07300000266550d0000e625000000e615000000e61528600 +cd0e000008d500cd5010d5013d5017d5018d5017d5014d500ed5009d5005d5001d5005d5008d500dd5010d5008d500cd5010d5013d5017d5018d5017d5014d5010d500bd5009d5008d5007d5009d500dd500fd50 +47010000000000000000000000003706035060310600000000000000002506000000000000000000000160600000000000000000a060000000000000000000000000000000000000000000000000000000000000 +46010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +93010000000000000009770097700a7700a7700a6700b7700c7700d7700f77011670117701377015770177701b6701b7701d77021770267702877000000000000000000000000000000000000000000000000000 +cb0600000f5503c6002d6001f60000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000050000000 +d5040000393712d37129371243711e37118371123710c3710a3510535105351053510435104351033510235101331013310033100331003210032100321003210031100311003110131101311013110131101311 +a702000035453334532f4532b4532645325453234531e4531e4531945316453174531145310453104530d4530a453094530745302453034530045300000000000000000000000000000000000000000000000000 +a702000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +d1090000397702d67029770246701e77018670127700c6700a7400564005740056400474004640037400264001720016200072000620007100061000710006100000000000000000000000000000000000000000 +17050000246552f655276553000600000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000006 +1703000000453024530445306453084530b4530e45311453164531a4531c4531e45320453224532445327453294532c4532f45332453344533745300000000000000000000000000000000000000000000000000 +170400003745337453354533345332453304532c4532945326453224531f4531c4531b4531945315453114530e4530c4530945307453044530245300000000000000000000000000000000000000000000000000 +a5100000021450e14502115021450a12502115021450e1150214502125021150a145091250211502145021150f14503125031150a145031250f115031450b115021450a125021150a145021250a1150214502115 +a30300002d1212212118121121210e121111030010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d7020000251501b150141500010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100001000010000100 +d107000037650316502f65029650226501e65019650166501465012640106400e6400903005630036300262000620006200062000620016200162000620006100061000610006100061000610006100061000610 +d70e00000f2400c2401024013240172401824017240142400e24009240052400124005240082400d24010240082400c240102401324017240182401724014240102400b240092400824007240092400d24013240 +d70e00000c2400f240132400c2400f240122400c2400f240102400c2400f240132400c2400f24014240122400c2400f240132400c2400f240122400c2400f2400e2400c2400f2401324016240152401424012240 +311000000675506755027550275502745027450273502735027250272502715027150271502715027150271507755077550275502755027450274502735027350272502725027150271502715027150271502715 +c31000000f7550f755037550375503745037450373503735037250372503715037150975509755097450974501755017550275502755027450274502735027350272502725027150271502715027150271502715 +c3100000027500e730027100275002720027100275002710027500273002710027500272002710027500271001750017200171001750017200171001740017100075000720007100075000720007100074000710 +010e00000c0730000000000000000c013000000c07300003266550d0000d625000000e6150e6050c6150e6050c0730000000000000000c073000000000000000266550d0000d625000000e6150e6050c6150e600 +15040000306503b65027650246501865018650186500c650000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +590400002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000000000000000000000000000000000000000000000000000000000000000000 +a70800000137001300003700000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000 +8d0600002b6502865026650216501f6501c6501a650186501665013640116400f6400d63009630076300662004610026100061000600006000060000600006000060000600006000060000600006000060000600 +__music__ +01 00175144 +00 00184344 +00 00170144 +00 04185844 +00 00171144 +00 02034344 +02 19034344 +01 1a154344 +02 1a164344 +00 02424344 + From 03a7c3e2a2706c31d897c832bba546c56738905e Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Thu, 9 Oct 2025 22:16:17 +0100 Subject: [PATCH 14/16] add camera, entity system, and optimizations to v0.3.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - implemented gamecam with smooth player following - added complete entity system with physics-based movement - player control with target-based movement and 32px radius limit - directional sprite animation (horizontal/up/down) with walk cycles - added dist_trig helper for precise distance calculations - optimized decompression using @ operator instead of peek() - removed facing_left state variable (calculate in draw) - refactored entity:draw() with elegant ternary expressions token count: 902 / 8192 (11.01%) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- v0.3.0.p8 | 141 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 140 insertions(+), 1 deletion(-) diff --git a/v0.3.0.p8 b/v0.3.0.p8 index 9f1c0f1..49ef4d0 100644 --- a/v0.3.0.p8 +++ b/v0.3.0.p8 @@ -10,6 +10,10 @@ init_fn,update_fn,draw_fn=nil,nil,nil current_mission=1 function _init() + -- set transparency + palt(0,false) + palt(14,true) + -- load compressed map data from cart v=2040 map_data={ @@ -19,11 +23,13 @@ function _init() pack(peek(0x1000+1788,1402)) } + cam=gamecam.new() change_state(init_game,update_game,draw_game) end function _update() update_fn() + cam:update() end function _draw() @@ -36,6 +42,12 @@ function change_state(i,u,d) init_fn() end +-- helper functions +function dist_trig(dx,dy) + local ang=atan2(dx,dy) + return dx*cos(ang)+dy*sin(ang) +end + -- intro function init_intro() end @@ -69,13 +81,140 @@ end -- gameplay function init_game() decompress_map() + player=entity.new(64,64) end function update_game() + player:update() end function draw_game() map(0,0,0,0,128,56) + player:draw() +end + +-- entity +entity={} +entity.__index=entity + +function entity.new(x,y) + return setmetatable({ + x=x, + y=y, + vx=0, + vy=0, + w=8, + h=8, + target_x=x, + target_y=y, + max_speed=4, + acceleration=0.8, + deceleration=0.9, + last_dir="down" + },entity) +end + +function entity:update() + self:control() + self:follow_target() + self:apply_physics() +end + +function entity:control() + local ix=(btn(1) and 1 or 0)-(btn(0) and 1 or 0) + local iy=(btn(3) and 1 or 0)-(btn(2) and 1 or 0) + + if ix==0 and iy==0 then + self.target_x+=(self.x-self.target_x)*0.3 + self.target_y+=(self.y-self.target_y)*0.3 + return + end + + self.target_x+=ix*6 + self.target_y+=iy*6 + + local dx,dy=self.target_x-self.x,self.target_y-self.y + + if dist_trig(dx,dy)>32 then + local angle=atan2(dx,dy) + self.target_x=self.x+cos(angle)*32 + self.target_y=self.y+sin(angle)*32 + end +end + +function entity:follow_target() + local dx,dy=self.target_x-self.x,self.target_y-self.y + local dist=dist_trig(dx,dy) + + if dist>1 then + self.vx=self:approach(self.vx,dx*0.1,self.acceleration) + self.vy=self:approach(self.vy,dy*0.1,self.acceleration) + + -- track direction + if abs(self.vx)>abs(self.vy) then + self.last_dir="horizontal" + else + self.last_dir=self.vy<0 and "up" or "down" + end + else + self.vx=self:approach(self.vx,0,self.deceleration) + self.vy=self:approach(self.vy,0,self.deceleration) + end + + -- limit speed + local speed=dist_trig(self.vx,self.vy) + if speed>self.max_speed then + self.vx=(self.vx/speed)*self.max_speed + self.vy=(self.vy/speed)*self.max_speed + end +end + +function entity:approach(current,target,step) + if currenttarget then + return max(current-step,target) + end + return current +end + +function entity:apply_physics() + self.vx=abs(self.vx)<0.01 and 0 or self.vx*self.deceleration + self.vy=abs(self.vy)<0.01 and 0 or self.vy*self.deceleration + self.x+=self.vx + self.y+=self.vy +end + +function entity:draw() + spr(49,self.x,self.y+1) -- shadow + + local spd=dist_trig(self.vx,self.vy) + local moving=spd>0.2 + + -- sprite base: horizontal=0, down=16, up=32 + local s=(self.last_dir=="up" and 32 or self.last_dir=="horizontal" and 0 or 16)+(moving and 2 or 0) + + -- animate + s+=flr(t()*(moving and 10+min(spd/self.max_speed,1)*10 or 3))%2 + + spr(s,self.x,self.y,1,1,self.vx<0) +end + +-- camera +gamecam={} +gamecam.__index=gamecam + +function gamecam.new() + return setmetatable({ + x=0, + y=0 + },gamecam) +end + +function gamecam:update() + self.x+=(player.x-self.x-64)*0.2 + self.y+=(player.y-self.y-64)*0.2 + camera(self.x,self.y) end -- map decompression @@ -105,7 +244,7 @@ function decompress_to_mem(data,dest) local dist=di-read_bits(12)-1 local len=read_bits(4)+1 for _=1,len do - poke(di,peek(dist)) + poke(di,@dist) dist+=1 di+=1 end From 969753b50e8d6d8c6f295b9960f400f81cebeaf4 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Thu, 9 Oct 2025 22:21:33 +0100 Subject: [PATCH 15/16] simplify physics: remove double deceleration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - removed redundant deceleration from apply_physics() - follow_target() now handles all velocity changes - apply_physics() just applies velocity to position - cleaner separation: follow_target = velocity logic, apply_physics = position update - movement feels slightly more floaty/momentum-based token count: 896 / 8192 (10.94%) saved: 6 tokens 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- v0.3.0.p8 | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/v0.3.0.p8 b/v0.3.0.p8 index 49ef4d0..77486a3 100644 --- a/v0.3.0.p8 +++ b/v0.3.0.p8 @@ -179,8 +179,8 @@ function entity:approach(current,target,step) end function entity:apply_physics() - self.vx=abs(self.vx)<0.01 and 0 or self.vx*self.deceleration - self.vy=abs(self.vy)<0.01 and 0 or self.vy*self.deceleration + self.vx=abs(self.vx)<0.01 and 0 or self.vx + self.vy=abs(self.vy)<0.01 and 0 or self.vy self.x+=self.vx self.y+=self.vy end From 8a98c4d5660d79a4ae28e21db8f3e920bf24f987 Mon Sep 17 00:00:00 2001 From: ebonura-fastly Date: Fri, 10 Oct 2025 10:45:34 +0100 Subject: [PATCH 16/16] implement collision system with precise hitboxes - add check_tile_flag helper function for tile collision detection - implement tile-based collision with smart sliding (x-only, y-only, stop) - add precise collision box properties (col_x, col_y, col_w, col_h) - player uses 8x7 hitbox with 1px top offset (matches actual sprite) - spawn player at map tile with flag 7 (spawn point marker) - collision checks use entity's collision box, not full sprite size - allows different hitbox shapes per entity type token count: 1096 / 8192 (13.38%) --- v0.3.0.p8 | 54 ++++++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 52 insertions(+), 2 deletions(-) diff --git a/v0.3.0.p8 b/v0.3.0.p8 index 77486a3..0be8ae8 100644 --- a/v0.3.0.p8 +++ b/v0.3.0.p8 @@ -48,6 +48,10 @@ function dist_trig(dx,dy) return dx*cos(ang)+dy*sin(ang) end +function check_tile_flag(x,y) + return fget(mget(flr(x/8),flr(y/8)),0) +end + -- intro function init_intro() end @@ -81,6 +85,18 @@ end -- gameplay function init_game() decompress_map() + + -- find spawn point (flag 7) + for ty=0,63 do + for tx=0,127 do + if fget(mget(tx,ty),7) then + player=entity.new(tx*8,ty*8) + return + end + end + end + + -- fallback if no spawn found player=entity.new(64,64) end @@ -105,6 +121,10 @@ function entity.new(x,y) vy=0, w=8, h=8, + col_x=0, -- collision box offset from x + col_y=1, -- collision box offset from y (skip top row) + col_w=8, -- collision box width + col_h=7, -- collision box height (7 pixels, not 8) target_x=x, target_y=y, max_speed=4, @@ -178,11 +198,41 @@ function entity:approach(current,target,step) return current end +function entity:check_tile_collision(x,y) + -- use actual collision box (not visual sprite size) + local cx1,cy1=x+self.col_x,y+self.col_y + local cx2,cy2=cx1+self.col_w-1,cy1+self.col_h-1 + + local tx1,ty1=flr(cx1/8),flr(cy1/8) + local tx2,ty2=flr(cx2/8),flr(cy2/8) + + for tx=tx1,tx2 do + for ty=ty1,ty2 do + if fget(mget(tx,ty),0) then + return true + end + end + end + return false +end + function entity:apply_physics() + local nx,ny=self.x+self.vx,self.y+self.vy + + -- check collision and slide + if self:check_tile_collision(nx,ny) then + if not self:check_tile_collision(nx,self.y) then + ny=self.y -- slide horizontally + elseif not self:check_tile_collision(self.x,ny) then + nx=self.x -- slide vertically + else + nx,ny=self.x,self.y -- stop + end + end + self.vx=abs(self.vx)<0.01 and 0 or self.vx self.vy=abs(self.vy)<0.01 and 0 or self.vy - self.x+=self.vx - self.y+=self.vy + self.x,self.y=nx,ny end function entity:draw()