-------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- Strife! (sans gonads) (last updated 01 Jan 2008) -- -- Programmed by Sean Connelly (sean@codetank.com) -- This source code is hereby released as public domain. I reserve no rights. -- -- To run this program, you must load it in Brain Damage, which can be found -- here: http://codetank.com/damage/ -- -- To learn more about Brain Damage, please see http://codetank.com/ -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- -- This game was released with Brain Damage 1.2. It's not perfect, but it is -- playable and fun. Some ideas on how you might try to improve the game: -- -- 1. Play around with the configuration table, and see what different values -- do to alter the game. Read the comments in the source code, and try -- changing things around in there too. Can you hack the code to make -- yourself invincible permanently? -- -- 2. Add different kinds of guns. Draw powerups, and if the user picks it up, -- then upgrade their gun. Make a grenade launcher :-). -- -- 3. I started to draw more enemies in the image files, but I got lazy and -- decided not to add them. See if you can add them - or - just make your -- own new images, and add more enemies. Add a boss, that the player has -- to beat. Add more levels, at different locations. -- -------------------------------------------------------------------------------- -------------------------------------------------------------------------------- -- The following table will configure the entire script start-up values: config = { width = 480, -- window width map_width = 75, -- width of map in tiles walk_speed = 1.3, -- how fast guy can walk sec_per_frame = 1 / 125, -- how many seconds per frame? bullet_speed = 3, -- how fast are the bullets? machine_gun = false, -- have a machine gun? enemy_speed = 1, -- how fast are the walking enemies? jump_force_up = 4.8, -- how much force does our guy fly off the ground when jumping standing still? jump_force_over = 4, -- how much force when jumping forward/backward? jump_horiz_up = 0.8, -- how much horizontal movement while jumping up? jump_horiz_over = 1.8, -- how much horizontal movement while jumping sideways? jump_grav = 0.1, -- how much gravity pulls our guy back down to earth? crawler_freq = 125, -- how many frames (average) to wait until spawning a crawler? lower = more frequent crawler_hurt = 5, -- how much health to take away when crawler hits player? sprites = "StrifeSprites.png", -- where to find the sprites tiles = "StrifeTiles.png", -- where to find the tiles -- There is an important difference between "sprites" and "tiles", and a reason why they are -- separated into two files. If a file contains transparency, then it is drawn differently -- than a file with no transparency. A file without transparency will be drawn faster, so -- that's why the solid tiles are placed in the tile file, and the sprites that contain -- transparency are placed in a separate sprite file. This allows the engine to draw the -- tiles with a faster algorithm internally, because it won't have to worry about skipping -- transparent pixels, since there is no transparency. The optimization only works internally -- if the ENTIRE file is opaque. } -- end configuration -- calculate some other values based on initial configuration config.width = config.width + ((32 - (config.width % 32)) % 32) -- force width to be divisible by 32 config.height = 320 -- DO NOT CHANGE, must be 320 for mapping purposes config.cx = math.floor(config.width / 2) -- center x config.cy = math.floor(config.height / 2) -- center y -- seed random number generator math.randomseed() -- this function will check to make sure the user is running this script in a Brain Damage IDE that -- meets a minimum requirement, and also displays a warning if they are running the script in a -- version that is far above the requirement (which might mean deprecated functions) function CheckBrainDamageVersion(maj, min, rev) local want = "Brain Damage " .. maj .. "." .. min; if (rev > 0) then want = want .. "." .. rev; end -- extract the sub-version numbers using pattern matching local ver = {} local w = "" for w in string.gmatch(_BRAINDAMAGE_VERSION, "%d+") do ver[table.getn(ver) + 1] = tonumber(w); end ver[3] = ver[3] or 0 -- if revision isn't specified, then assume 0 local ret = true; if (maj > ver[1]) then ret = false; elseif (maj == ver[1]) then if (min > ver[2]) then ret = false; elseif (min == ver[2]) then if (rev > ver[3]) then ret = false end end elseif (maj < ver[1] - 1) then -- if Brain Damage is two major versions ahead, there may be issues with compatibility local url = "http://codetank.com/damage/" .. maj .. "." .. min; if (rev > 0) then url = url .. "." .. rev end url = url .. "/" alert("This program is outdated. It was originally written for " .. want .. "\nYour current version is: " .. _BRAINDAMAGE_VERSION .. "\n\nYou can still attempt to run this program, but it might unknowingly use outdated or" .. "\ndeprecated functions. For optimal results, you can download the older version of" .. "\nBrain Damage at: " .. url, "Warning: Outdated Program") end if (not ret) then alert("This program requires " .. want .. "\nYour current version is: " .. _BRAINDAMAGE_VERSION .. "\n\nYou can download the latest version of Brain Damage at:" .. "\nhttp://codetank.com/damage", "Fatal Error: Outdated Version"); end return ret; end -- now we perform our check to make sure the user has at least v1.2: if (not CheckBrainDamageVersion(1, 2, 0)) then return -- if we don't have a compatible version, then kill the script end -- returns a map and collision table of a randomly generate street map, with tile_cx tiles across function generate_street_map(tile_cx) local map = {} local solid = {} local i -- create our rows for i = 0, 9 do map[i] = {} solid[i] = {} end -- fill the map with the filler data local crack_dist = math.random(2) for i = 0, tile_cx - 1 do map[0][i] = 0 -- red sky map[1][i] = 0 -- red sky map[2][i] = 1 -- fence top map[3][i] = 17 -- fence bottom map[4][i] = 16 -- alley map[5][i] = 16 -- alley map[6][i] = 16 -- alley map[7][i] = 2 -- sidewalk crack_dist = crack_dist - 1 if (crack_dist == 0) then map[7][i] = 3 -- sidewalk separator crack_dist = math.random(3) + 1 end map[8][i] = 18 -- road map[9][i] = 19 -- road with paint -- mark road as solid (and impenetrable using positive number) solid[8][i] = 16 if ((i % 2) == 0) then map[9][i] = 18 -- every other one isn't painted end end -- output buildings local state = 0 local building = 0 local building_width = 0 for i = 0, tile_cx - 1 do if (state == 0) then -- in alley local d = math.random(5) -- increase this number to increase average alley width if (d == 1) then -- 1 in 5, choose tan building state = 1 building = 0 building_width = 0 elseif (d == 2) then -- 1 in 5, choose red building state = 1 building = 1 building_width = 0 end -- 3 in 5, keep alley elseif (state == 1 or state == 6) then -- building side (1 = left, 6 = right) local tile = 50 + building * 32 if (state == 6) then -- if right side, use that tile tile = 34 + building * 32 end map[0][i] = tile map[1][i] = tile map[2][i] = tile map[3][i] = tile map[4][i] = tile map[5][i] = tile map[6][i] = tile if (state == 1) then -- if left side of building, then start building state = 2 else -- else, just finished building, so go back to alley state = 0 end elseif (state >= 2 and state <= 5) then -- output normal building crap -- (2 = pre-door, 3 = left door, 4 = right door, 5 = post-door) -- output building brick texture and windows local d = math.random(3) -- window frequency local tile = 32 + building * 32 -- brick texture if (d == 1) then -- output window solid[1][i] = -11 + building * 10 -- walk on window, negative = penetrable map[1][i] = 33 + building * 32 map[2][i] = 49 + building * 32 else -- output no window map[1][i] = 32 + building * 32 map[2][i] = 48 + building * 32 end solid[2][i] = -29 -- walk on window sill, negative = penetrable map[0][i] = tile map[3][i] = tile map[4][i] = tile map[5][i] = tile map[6][i] = tile building_width = building_width + 1 -- prevents fat buildings -- figure out if we're suppose to do a door, and do it if so -- then figure out where we should go from here if (state == 2) then -- waiting for door if ((building_width >= 1 and math.random(4) == 1) or building_width >= 3) then -- should do door state = 3 end elseif (state == 3) then -- do left door map[5][i] = 35 + building * 32 -- left door top map[6][i] = 51 + building * 32 -- left door bottom solid[5][i] = -1 -- walk on door, negative = penetrable if (math.random(2) == 1) then -- should do right door? state = 4 else state = 5 -- skip right door end elseif (state == 4) then -- do right door map[5][i] = 36 + building * 32 -- right door top map[6][i] = 52 + building * 32 -- right door bottom solid[5][i] = -1 -- walk on door, negative = penetrable state = 5 elseif (state == 5) then if ((building_width >= 4 and math.random(4) == 1) or building_width >= 8) then -- end building? state = 6 end end end end return map, solid end -- this function will draw the map to the back buffer -- it is used to initially draw the map, and then also used to draw the edges of the -- map when it's scrolled to the right or left... start_tile is what horizontal -- tile to start at, tile_width is how many tiles across to draw, and start_pix is -- the horizontal pixel to start drawing at function output_map_to_backbuf(start_tile, tile_width, start_pix) local i local y local tile local end_tile = start_tile + tile_width - 1 for i = start_tile, end_tile do for y = 0, 319, 32 do tile = map[math.floor(y / 32)] if (tile) then tile = tile[i] if (tile) then tiles:blit(backbuf, start_pix, y, (tile % 16) * 32, math.floor(tile / 16) * 32, 32, 32) end end end start_pix = start_pix + 32 end end -- this function will inject an explosion into the explosion table, which will then -- be animated and drawn by the wnd:draw function function create_explosion(x, y) explosion[explosion.nexti] = { x = x, y = y, alive = true, frame = 0, frame_time = 10, } explosion.nexti = explosion.nexti + 1 end -- this function will inject 10 gut particles into the guts table, which will then -- be animated and drawn by the wnd:draw function function create_guts(x, y) local i for i = 1, 10 do guts[guts.nexti] = { -- position and trajectory x = x, y = y, dx = math.random(-2, 2), dy = math.random(-5, -1), -- how long the chunks last life = 100, -- a random tile tx = 480 + math.random(0, 25), ty = math.random(0, 25), dtx = math.random(4, 6), dty = math.random(4, 6), alive = true, } guts.nexti = guts.nexti + 1 end end -- this function will create a crawler function create_crawler(side) -- side = 0 for left, 1 for right enemy[enemy.nexti] = { type = 2, x = game.scroll + math.random(0, config.width / 2), y = 0, dy = 0, direction = side, health = 1, frame = 0, frame_time = 0, alive = true, } enemy.total = enemy.total + 1 if (side == 1) then enemy[enemy.nexti].x = game.scroll + math.random(config.width / 2, config.width + 100) end enemy.nexti = enemy.nexti + 1 end -- helper function for boxes_overlap function lines_overlap(l1x, l1y, l1cx, l2x, l2y, l2cy) return l1x <= l2x and l1x + l1cx >= l2x and l2y <= l1y and l2y + l2cy >= l1y end -- will return true if the two boxes overlap... used for collision detection between -- enemies and the player function boxes_overlap(b1x, b1y, b1cx, b1cy, b2x, b2y, b2cx, b2cy) return lines_overlap(b1x, b1y, b1cx, b2x, b2y, b2cy) or lines_overlap(b1x, b1y, b1cx, b2x + b2cx, b2y, b2cy) or lines_overlap(b1x, b1y + b1cy, b1cx, b2x, b2y, b2cy) or lines_overlap(b1x, b1y + b1cy, b1cx, b2x + b2cx, b2y, b2cy) or lines_overlap(b2x, b2y, b2cx, b1x, b1y, b1cy) or lines_overlap(b2x, b2y, b2cx, b1x + b1cx, b1y, b1cy) or lines_overlap(b2x, b2y + b2cy, b2cx, b1x, b1y, b1cy) or lines_overlap(b2x, b2y + b2cy, b2cx, b1x + b1cx, b1y, b1cy) end -- returns (true, new_y) if passing from old_y to y should hit a ground element function should_hit_ground(x, y, old_y, is_enemy) -- figure out if our guy is on something solid local tx = math.floor(x / 32) local ty = math.floor(y / 32) -- figure out where the user was in relation to y % 32... if (math.floor(old_y / 32) < ty) then old_y = -1 elseif (math.floor(old_y / 32) > ty) then old_y = 32 else old_y = old_y % 32 end -- if we are standing on something solid... if (solid[ty] ~= nil and solid[ty][tx] ~= nil and solid[ty][tx] ~= 0) then local s = math.abs(solid[ty][tx]) - 1 if (solid[ty][tx] < 0) then -- can we go under this block? -- yes, so only force player to walk when passing down through it -- if travelling down, and passes through the solid border... if (old_y < s and (y % 32) >= s) then return true, y - (y % 32) + s elseif ((y % 32) == s and (not is_enemy or math.random(100) ~= 1)) then return true, y end else -- not allowed to go under, so force the player up if ((y % 32) > s) then return true, y - (y % 32) + s elseif ((y % 32) == s) then return true, y end end end return false end -- returns the button state of UP, DOWN, LEFT, RIGHT, SHOOT, JUMP function get_input() -- if we are using a joystick, then poll that if (game.joystick > 0) then local joy = input.getjoystick(game.joystick) if (joy.pov == nil) then return joy.y < -0.5, joy.y > 0.5, joy.x < -0.5, joy.x > 0.5, joy.button[1], joy.button[2] end -- joystick has pov, so the x and y values probably represent a thumbstick... -- so let's use the pov instead (which is usually a D-pad) return (joy.pov >= 0 and joy.pov <= 50) or joy.pov >= 310, joy.pov >= 130 and joy.pov <= 230, joy.pov >= 220 and joy.pov <= 320, joy.pov >= 40 and joy.pov <= 140, joy.button[1], joy.button[2] end -- else we are using the keyboard, so poll that return input.getkey(input.key.UP), input.getkey(input.key.DOWN), input.getkey(input.key.LEFT), input.getkey(input.key.RIGHT), input.getkey(input.key.X), input.getkey(input.key.Z) end -- returns true for a useable joystick, false for an unusable one function useable_joystick(n) local joy = input.getjoystick(n) return joy ~= nil -- joystick must exist and joy.x ~= nil -- and must have x for horizontal movement and joy.y ~= nil -- and must have y for veritcal movement and joy.button[1] ~= nil -- and must have a button 1 (for shooting) and joy.button[2] ~= nil -- and must have a button 2 (for jumping) end -- create our display image that shows everything display = image.create(config.width, config.height) -- create our back buffer to speed up the background drawing backbuf = image.create(config.width, config.height) -- load our sprites sprites = image.load(config.sprites) if (sprites == nil) then alert("Failed to load sprite file: " .. config.sprites .. "\n\nPlease make sure the file is in the same directory as Strife.lua\n\nYou should have downloaded this file with the game.", "Error") return end -- load our tiles tiles = image.load(config.tiles) if (tiles == nil) then alert("Failed to load tile file: " .. config.tiles .. "\n\nPlease make sure the file is in the same directory as Strife.lua\n\nYou should have downloaded this file with the game.", "Error") return end -- our game table holds the state of the game game = { state = 0, joystick = 0, is_over = false, over_count = 500, scroll = 0, doscroll = 0, spawn_crawler = config.crawler_freq, score = 0, camp_count = 0, total_camp_count = 0, camp_x = 0, camp_y = 0, } player = { x = 200, y = 200, ddy = 0, health = 100, invinc = 200, invinc_draw = true, machine_gun_delay = 0, state = 1, -- 0 = walking, 1 = jumping/falling didjump = false, -- used to prevent auto-jumping didshoot = false, -- used to prevent auto-firing jump_horiz = config.jump_horiz_up, animation = 0, anim = { [0] = { [0] = 0 }, -- standing still [1] = { [0] = 1 }, -- standing still looking up [2] = { [0] = 2 }, -- standing still looking down [3] = { [0] = 3, 4, 5, 4 }, -- walking [4] = { [0] = 6, 7, 8, 7 }, -- walking looking up [5] = { [0] = 9, 10, 11, 10 }, -- walking looking down [6] = { [0] = 3 }, -- jumping [7] = { [0] = 6 }, -- jumping looking up [8] = { [0] = 9 }, -- jumping looking down }, bulletinfo = { -- holds where the bullet comes out of the gun, for each frame -- [frame number] = {bullet start x, bullet start y, bullet direction y} [ 0] = {30, 22, 1, 0}, [ 1] = {15, 0, 0, -1}, [ 2] = {30, 42, 1, 0}, [ 3] = {30, 22, 1, 0}, [ 4] = {30, 22, 1, 0}, [ 5] = {30, 22, 1, 0}, [ 6] = {26, 2, 1, -0.7}, [ 7] = {26, 2, 1, -0.7}, [ 8] = {26, 2, 1, -0.7}, [ 9] = {25, 43, 1, 0.7}, [10] = {25, 43, 1, 0.7}, [11] = {25, 43, 1, 0.7}, }, anim_timer = 0, anim_frame = 0, direction = 0, } -- this table will hold all the bullets flying around bullet = { starti = 1, nexti = 1, } -- this table will hold all the enemies enemy = { starti = 1, nexti = 1, total = 0, } -- this table holds all the current explosions explosion = { starti = 1, nexti = 1, } -- this table holds all the particles of guts guts = { starti = 1, nexti = 1, } -- generate our map, along with the collision information map, solid = generate_street_map(config.map_width) -- draw the entire map to the back buffer... after this point, all that -- needs to be done is to scroll the map left or right, and draw the missing gap output_map_to_backbuf(0, math.ceil(config.width / 32), 0) -- figure out if there are any joysticks joystick_count = 0 for i = 1, 16 do if (useable_joystick(i)) then joystick_count = joystick_count + 1 end end -- if the user has a joystick, and wants to use it, then flag it if (joystick_count > 0) then if (confirm("Do you want to use your joystick?", "Configure Input")) then if (joystick_count > 1) then n = tonumber(prompt("You have " .. joystick_count .. " joysticks. Which one do you want to use?\n(Enter a number from 1 to " .. joystick_count .. ")")) if (n >= 1 and n <= joystick_count) then for i = 1, 16 do if (useable_joystick(i)) then n = n - 1 if (n == 0) then game.joystick = i end end end end else for i = 1, 16 do if (useable_joystick(i)) then game.joystick = i end end end end end wnd = window.create{ width = config.width, height = config.height, title = "Strife!", think = function(wnd) local i, j -- grab the input local up, down, left, right, shoot, jump = get_input() local old_animation = player.animation local old_y = player.y -- if game is over, then clear the input -- else, increase score (only if not camping) if (game.is_over) then up, down, left, right, shoot, jump = false, false, false, false, false, false else if (game.camp_count < 1000) then game.score = game.score + 0.01 end end -- figure out if the user has been hanging out in the same area for a while... -- if so, then they're camping! shame on them :-) local camp_dx = game.camp_x - player.x local camp_dy = game.camp_y - player.y if (camp_dx * camp_dx + camp_dy * camp_dy < 9000) then game.camp_count = game.camp_count + 1 else -- user is no longer camping, so follow them, waiting for them to camp if (game.camp_count > 1000) then game.total_camp_count = game.total_camp_count + 1 end game.camp_count = 0 game.camp_x = player.x game.camp_y = player.y end -- check to see if we should spawn a crawler game.spawn_crawler = game.spawn_crawler - math.random() * 2 if (game.spawn_crawler <= 0) then -- higher score = higher frequency crawler spawn game.spawn_crawler = config.crawler_freq - game.score * 0.05 if (game.spawn_crawler < 10) then game.spawn_crawler = 10 end -- create our crawler in a random location create_crawler(math.random(0, 1)) end -- figure out our guys animation, and where to move him if (player.state == 0) then -- is on ground if (left or right) then player.animation = 3 if (up) then player.animation = 4 elseif (down) then player.animation = 5 end if (left) then player.direction = 1 player.x = player.x - config.walk_speed else player.direction = 0 player.x = player.x + config.walk_speed end else player.animation = 0 if (up) then player.animation = 1 elseif (down) then player.animation = 2 end end if (jump) then -- is the user trying to jump? if (not left and not right and down) then -- jumping under current platform local tx = math.floor(player.x / 32) local ty = math.floor(player.y / 32) -- if solid table contains negative number... if (solid[ty] == nil or solid[ty][tx] == nil or solid[ty][tx] < 0) then player.y = player.y + 1 old_y = old_y + 1 player.state = 1 player.ddy = 1 end else if (not player.didjump) then -- make sure they aren't just holding down the jump key -- player is jumping, so change his state, and throw him in the air! player.state = 1 player.didjump = true if (left or right) then player.ddy = -config.jump_force_over player.jump_horiz = config.jump_horiz_over else player.ddy = -config.jump_force_up player.jump_horiz = config.jump_horiz_up end end end else player.didjump = false end elseif (player.state == 1) then -- is in air -- set animation based on the input player.animation = 6 if (left) then player.direction = 1 player.x = player.x - player.jump_horiz elseif (right) then player.direction = 0 player.x = player.x + player.jump_horiz end if (up) then player.animation = 7 elseif (down) then player.animation = 8 end -- make the player go in an arc, buy using some basic physics player.y = player.y + player.ddy player.ddy = player.ddy + config.jump_grav if (not jump and player.ddy < -1) then -- this code will make it so that if the user lets go of the -- jump button in mid jump, then it will cut the jump short player.ddy = -1 end end -- clip the player against the world local found_solid = false for txd = -8, 8, 8 do local hit, newy = should_hit_ground(player.x + txd, player.y, old_y, false) if (hit and (player.state == 0 or player.ddy > 0)) then player.y = newy player.state = 0 found_solid = true end end -- if the player just walked off a ledge... if (player.state == 0 and not found_solid) then -- then set his state to falling player.state = 1 player.ddy = 0 player.jump_horiz = config.walk_speed end -- if player is trying to walk off map (left or right), then stop him if (player.x < 0) then player.x = 0 elseif (player.x > config.map_width * 32) then player.x = config.map_width * 32 end -- simple animation engine, that figures out what animation to play, and cycles -- through the frames given in the anim table, updating every 6 frames if (old_animation ~= player.animation) then player.anim_frame = 0 end player.anim_timer = (player.anim_timer + 1) % 6 if (player.anim_timer == 0) then player.anim_frame = (player.anim_frame + 1) % (#player.anim[player.animation] + 1) end if (player.invinc > 0) then -- if invincible, then tick the counter down... player.invinc = player.invinc - 1 end -- fire some bullets, but only if user isn't camping! if (shoot and game.camp_count < 1000) then if (not player.didfire and player.machine_gun_delay <= 0) then -- bi will hold the information of where the bullet should come out of the player, -- which is based on what animation frame he currently is local bi = player.bulletinfo[player.anim[player.animation][player.anim_frame]] if (config.machine_gun) then player.machine_gun_delay = 10 end player.didfire = not config.machine_gun -- create the bullet bullet[bullet.nexti] = { y = player.y - 64 + bi[2], dx = (-2 * player.direction + 1) * bi[3] * config.bullet_speed, dy = bi[4] * config.bullet_speed, col = 54, alive = true, from_player = true, } -- if the player is facing left, then we need to mirror the bi information if (player.direction == 0) then -- if facing right bullet[bullet.nexti].x = player.x - 16 + bi[1] else -- if facing left bullet[bullet.nexti].x = player.x + 16 - bi[1] end bullet.nexti = bullet.nexti + 1 end else player.didfire = false if (config.machine_gun) then player.machine_gun_delay = 10 end end if (player.machine_gun_delay > 0) then player.machine_gun_delay = player.machine_gun_delay - 1 end -- move bullets for i = bullet.starti, bullet.nexti - 1 do if (bullet[i].alive) then bullet[i].x = bullet[i].x + bullet[i].dx bullet[i].y = bullet[i].y + bullet[i].dy -- clip bullets against enemies for j = enemy.starti, enemy.nexti - 1 do if (enemy[j].alive) then if (enemy[j].type == 2) then if (bullet[i].x >= enemy[j].x - 16 and bullet[i].x <= enemy[j].x + 16 and bullet[i].y >= enemy[j].y - 14 and bullet[i].y <= enemy[j].y) then bullet[i].alive = false enemy[j].health = enemy[j].health - 1 if (enemy[j].health <= 0) then enemy[j].alive = false enemy.total = enemy.total - 1 create_guts(enemy[j].x, enemy[j].y) if (math.random(5) == 1) then create_explosion(enemy[j].x, enemy[j].y) end if (game.camp_count < 1000) then game.score = game.score + 100 end end end end end end end end -- update enemies for i = enemy.starti, enemy.nexti - 1 do if (enemy[i].alive) then -- move enemy based on it's type if (enemy[i].type == 2) then -- these guys walk around enemy[i].x = enemy[i].x + (1 - enemy[i].direction * 2) * config.enemy_speed local old_y = enemy[i].y enemy[i].y = enemy[i].y + enemy[i].dy -- figure out if we just walked through the ground... (oops) local hit, newy = should_hit_ground(enemy[i].x, enemy[i].y, old_y, true) if (not hit) then -- nope, not hitting ground, so accellerate enemy[i].dy = enemy[i].dy + config.jump_grav else -- yes, we hit the ground... enemy[i].dy = 0 enemy[i].y = newy end -- animate enemy[i].frame_time = (enemy[i].frame_time + 1) % 6 if (enemy[i].frame_time == 0) then enemy[i].frame = (enemy[i].frame + 1) % 4 end end if (player.invinc <= 0 and not game.is_over) then -- check for player <-> enemy collision local hit_player = false if (enemy[i].type == 2) then if (boxes_overlap(player.x - 12, player.y - 62, 24, 60, enemy[i].x - 12, enemy[i].y - 14, 24, 14)) then -- player is hitting the enemy! change player health player.health = player.health - config.crawler_hurt hit_player = true end --else -- type == 1, etc end if (hit_player) then -- if the player just got hit if (player.state == 0) then -- if on ground... -- throw him in the air a little bit, and make him invincible player.state = 1 player.invinc = 200 player.ddy = -2 player.jump_horiz = config.jump_horiz_up else -- if in air, then... -- force him to stop moving upwards, and make him invincible player.invinc = 200 if (player.ddy < 0) then player.ddy = 0 end player.jump_horiz = 0 end -- oh goodness, did we just kill the player!? if (player.health <= 0) then -- if so, then game is over... and let's create some guts while were at it :-) game.is_over = true for j = 0, 60, 5 do create_guts(player.x + math.random(-13, 13), player.y - j) end end end end -- if the enemy is off the screen, then just remove him if (enemy[i].y < -64 or enemy[i].y > config.height + 64 or enemy[i].x < game.scroll - 128 or enemy[i].x > game.scroll + config.width + 128) then enemy[i].alive = false enemy.total = enemy.total - 1 end else if (i == enemy.starti) then enemy[i] = nil enemy.starti = enemy.starti + 1 end end end -- inform the drawing engine how much we'd like to scroll - let drawing engine clip game.doscroll = math.floor(player.x) - config.cx - game.scroll end, draw = function(wnd) -- clip the desired scroll if (game.doscroll > 0) then if (game.scroll + game.doscroll + config.width > config.map_width * 32) then game.doscroll = config.map_width * 32 - game.scroll - config.width end end if (game.doscroll < 0) then if (game.scroll + game.doscroll < 0) then game.doscroll = -game.scroll end end -- scroll the window if (game.doscroll > 0) then -- scroll forward backbuf:blit(display, 0, 0) display:blit(backbuf, -game.doscroll, 0) output_map_to_backbuf(math.floor((game.scroll + config.width) / 32), 1, config.width - game.doscroll - (game.scroll % 32)) game.scroll = game.scroll + game.doscroll game.doscroll = 0 elseif (game.doscroll < 0) then -- scroll backwards backbuf:blit(display, 0, 0) display:blit(backbuf, -game.doscroll, 0) output_map_to_backbuf(math.floor(game.scroll / 32), 1, -32 - game.doscroll + (32 - (game.scroll % 32))) game.scroll = game.scroll + game.doscroll game.doscroll = 0 end backbuf:blit(display, 0, 0) -- draw the player if the game isn't over, and also blink him if he's invincible if (not game.is_over and (player.invinc <= 0 or (player.invinc > 0 and player.invinc_draw))) then -- output the player sprite local frame = player.anim[player.animation][player.anim_frame] sprites:blit(display, math.floor(player.x) - game.scroll - 16, player.y - 64, frame * 32, 64 * player.direction, 32, 64) end player.invinc_draw = not player.invinc_draw -- output the enemies for i = enemy.starti, enemy.nexti - 1 do if (enemy[i].alive) then if (enemy[i].type == 2) then sprites:blit(display, enemy[i].x - 16 - game.scroll, enemy[i].y - 32, 384 + enemy[i].frame * 32, 64 + 32 * enemy[i].direction, 32, 32) end end end -- output the bullets for i = bullet.starti, bullet.nexti - 1 do local sx = bullet[i].x - game.scroll if (bullet[i].alive and sx > 0 and sx < config.width and bullet[i].y > 0 and bullet[i].y < config.height) then tiles:blit(display, sx - 1, bullet[i].y - 1, 128 + 4 * (bullet[i].col % 8), 4 * math.floor(bullet[i].col / 8), 3, 3) else if (i == bullet.starti) then bullet.starti = bullet.starti + 1 bullet[i] = nil elseif (bullet[i].alive) then bullet[i] = { alive = false, x = 0 } end end end -- output and animate the guts for i = guts.starti, guts.nexti - 1 do if (guts[i].alive) then guts[i].life = guts[i].life - 1 if (guts[i].life < 0) then guts[i].alive = false end guts[i].x = guts[i].x + guts[i].dx guts[i].y = guts[i].y + guts[i].dy guts[i].dy = guts[i].dy + 0.1 sprites:blit(display, guts[i].x - game.scroll, guts[i].y, guts[i].tx, guts[i].ty, guts[i].dtx, guts[i].dty) else if (i == guts.starti) then guts[i] = nil guts.starti = guts.starti + 1 end end end -- output and animate the explosions for i = explosion.starti, explosion.nexti - 1 do if (explosion[i].alive) then explosion[i].frame_time = explosion[i].frame_time - 1 if (explosion[i].frame_time <= 0) then explosion[i].frame_time = 10 explosion[i].frame = explosion[i].frame + 1 end if (explosion[i].frame < 4) then sprites:blit(display, explosion[i].x - 16 - game.scroll, explosion[i].y - 16, 256 + 32 * (explosion[i].frame % 2), 128 + 32 * math.floor(explosion[i].frame / 2), 32, 32) else explosion[i].alive = false end else if (i == explosion.starti) then explosion[i] = nil explosion.starti = explosion.starti + 1 end end end -- we just finished drawing all our shit to the display bitmap, so invalidate to draw the display to the paint wnd:invalidate(false) end, onsizing = function(wnd, szi) -- will force the window to stay a good ratio if the user decides to resize it local zoom if (szi.side == "bottom" or szi.side == "top") then zoom = szi.height / config.height else zoom = szi.width / config.width end return config.width * zoom, config.height * zoom end, onerasebkgnd = function(wnd, pnt) -- no need to erase the background... the onpaint function has this covered return false end, onpaint = function(wnd, pnt) -- draw our display over the entire window display:stretchblit(pnt, 0, 0, wnd.width, wnd.height) -- make sure health isn't lower than 0, because then it'll screw up the health bar if (player.health < 0) then player.health = 0 end -- draw the bright red health bar pnt:rectangle(10, 10, wnd.width * player.health / 300, wnd.height * 0.01, false, rgb(255, 0, 0)) local camp_msg = "" if (game.camp_count > 1000) then camp_msg = "\nCampers never win" end -- print the score, and maybe the camper message pnt:print(10, wnd.height * 0.01 + 20, "Score: " .. math.floor(game.score - 2000 * game.total_camp_count) .. camp_msg, "left", rgb(255, 255, 255)) if (player.health < 100) then -- draw the dark red not-health bar pnt:rectangle(10 + wnd.width * player.health / 300, 10, wnd.width * (100 - player.health) / 300, wnd.height * 0.01, false, rgb(63, 0, 0)) end end, } frame_skip = 0 time_between = config.sec_per_frame * (frame_skip + 1) while window.getcount() > 0 and game.over_count > 0 do if (window.hasmessages()) then window.pumpmessages() else -- try to sync the frame... if it fails, then we need to think some more, and draw less if (syncframe(time_between)) then -- syncframe succeeds, so we can afford to waste more time... in that case, then draw more if (frame_skip > 0) then frame_skip = frame_skip - 1 time_between = config.sec_per_frame * (frame_skip + 1) end else -- syncframe fails, so we can't afford to waste any time... trim back our drawing frame_skip = frame_skip + 1 time_between = config.sec_per_frame * (frame_skip + 1) end -- think a bunch for frame_skip = 0, frame_skip do wnd:think() end -- draw what we just thought about wnd:draw() -- if it's game over, then this little counter will make the window stay open for a little bit -- as the player watches the aliens run all over the screen if (game.is_over) then game.over_count = game.over_count - 1 end end end if (window.getcount() > 0) then -- if the window is still open, then close it wnd:close() end -- and lastly, display our final score :-) alert("Game over.\n\nFinal Score: " .. math.floor(game.score - 2000 * game.total_camp_count), "Game Over")