0,]']>S}Wup8d%qy)6o;]AsVl33.--"."./3""@D@D@D@D03030404 " " " " " "@D@D@D@D03030404 " " " " " "0D4CCCC4CC40 VfenPePfUUfPevUugUVeVffVnfDDfvff @ @"D3 DvD`fePeUvUvUwefwgvffwfeVeVeUUUUVUv93D@D݈݈?DD`@tw D"v`fvwfUUUVUeovww'eeRUf'fU~'ff/"""ffD"D@B""D)""")""((" ".`foeff "D"D D"D D$DD DD"" " " "4CC4 CD4D சપ𪚉DDD4 DD D4ꩠ鞠DC@D@3ꈙꞙݙ@D@3ꈙꞙ.4# "D"ND) " DD /))" "I@I9ID3 3ݐCJDD8D4DݟDD݉DHD ݀9HC@DHDD 홐HDHD"#2H$HDٝ"D"D(D) D@  IHIݪ    "D4C=`nf"DC4cVUf @DCDD_ev.NDD4ODgDPUeefwDnnVwV-M@=Ufn"D3D3 fVU"DPfVUDpgFD"DMNfVCTeCdf"$Pf/VU$pgFD"C43fVBTeCdf??33  33C$ DD$fO-@@>Pfm"D3DcnfefVUUVeenPeUPvVePv_UPvPvPvVUeUUeUUfUWfWgooWegWVv`fUunUufwVUUUwEwwgeWUnWUwfe_UwUUwwTPuUfPugPewPuPPvPvVU`vUUWWovgnvfwggoUegUegPefuwUfeuDDVf@Ue]ffUeUnVfUmfVgVwgffvefDvfFww4`f`fvew`fv`fvCdf^wfogvgfww4`f`fvew`fv`fvCdf^wfogvgf`Pfb"^PUPUU UUUVGvwGf@e'U&Uv PfPePUfPUPUPeDff`ffUunVufunovnovofggf@tfUtwwpUf`U`Ue`Uf`UeWfWeGwwGgeWe^U^UWUWeDDwGfgvUUvUeUeUUPUvwDDOtgvfgUUpVUpVUpUUgUtUUtww`fv`ef`UvPUvPUw`vVUGwwGgeWUWUWUwegwwgffVeVU`Uf`UvpevwwwvffvfewVUWUWUffgvffvVevVUwVUwUe`Uf`Uv`evgfgVvwUu`UupUu`VUpVufuffvVevVUwVUwUe`Uf`Uv`evgfgVvwUu`UupUu`VUpVufugfwV`V`g`vff`U`efv ewUv UefUUwouwUeUUUwVv`vfvpwgfvgUvVUvVUVUVUVegugvge`evgVvgvpwwwvffovffovfwvfDDDDDDDDDĄDDDĄD DĄD DĄD DĄD DĄD DĄD Ą           **::JJZZjjzz   **::JJZZjjzz        NHIJK\]LLLLXYZ[LLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM]LLLLLLLLLLLLM^a/r`?.pqOgh_ixyobcffdeflmnjklmnsvwvwtuvw|}~z{|}~Ә =)22?DDDDD33333DDDDDDDDDDDDDDD3333333333DDDDDDDDDDDDDDDDDDDDDDDDDDDDD4333333333CDDDDDDDDDNDDDD433333DDDD3333333333DD333DD33333CDDDDNDDDDDDDDDDD333333333CDDDDDDDDDDDDDDDDDDDDDDDDDDDDD333333333CDDDDDDDDDNDDDD43333CDDDDDDD333333333C3333333333CDDDDDNDDDDDDDDDDDDDD333333333CDDDDDDDDDDDDDDDDDDDDDDDDDD33333333333CDDDDDDDDDDDDD33333DDDDDDDDDD4333333334333333333CDDDDDDDDNDDDDDDDDDDDDDDDDD333333333CDDDDDDDDDDDDDDDDDDDDDD333334333333333DDDDDDDDDD3333CDDDDDDDDDDDDD3333333DDDD333333333DDDDDDDDDDDDDDDDDDDDDDDDDDD3333333333DDDDDDDDDDDDDDDDDDD33333DDD4333333333DDDDDD43333CDDDDDDDDDDDDDDD433333DDDDDDD433333333CDDDDDDDDDDDDDDDDDDDDDD43333333333333DDDDDDDDDDDDDDD43333DDDDDDD333333333DDD33333DDDDDDDDDDDDDDDDDDD3333DDDDDDDDD333333333DDDDDDDDDDDDDDDDDDD43333DD433333333CDDDDDDDDDDD43333DDDDDDDDDD4333333333333CDDDDDDDDDDDDDDDDDDDDDD33DDDDDDDDDDDDD33333333CDDDDDDDDDDDDDDDDD33CDDDDD433333333CDDDDDDD43333DDDDDDDDDDDD4333333333DDDDDDDDDDDDDDDDDDDDDDDDD4DNDDDDDDDDDDDDDDD433333333DDDDDDDDDDDDDDDDDDDDDDDDD433333333DDDD43333DDDDNDDDDDDDD33333333CDDDDD433DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD33333333CDDDDDDDDDDDDDDDDDDDDDDDD433333333D3333DDDDDDDDDDDDDDD33333333CDD3333CDDDDDDDDDDDDDDD3DDDDDDDDDDDDDDDDDDDDDDDD433333333DDDDDDDDDDDDDDDDDDDDDDDD4333333333DDDDDDDDDDDDDDDDDDDDD433333333333DDDDDDDDDDDDDDDDD33333DDDDDDDDDDDDDDDDDDDDDDDD33333333CDDDDDDDDDDDDDDDDDDDDDDD43333333CDDDDDDDDDDDDDDDDDDDDDD433333333CDDDDDDDDDDDDDDDDDDD43333333DDDDDDDDDDDDDDDDDDDDDDDD3333333CDDDDDDDDDDDDDDDDDDDDDDD43333333CDDDDDDDDDDDDDDDDDD3333C33333333DDDDDDDDDDDDDDDDDDDDD43333333CDDDDDDDDDDDDDDDDDDD33333333333DDDDD333DDDDDDDDDDDDD4333333333DDDDDDDDDDDDDDD433CDDDD33333333DDDDDDDDDDDDDDDDDDDDDDDD3333333CDDDDDDDDDDDDDD4333CDDD3333333C4333CDDDDDDDDDDD4333CD43333333DDDDDD3CDDDDDDDDDDDDDD43333333DDDDDDDDDDDDDDDDDDDDDDDDDD3333333CDDDDDDDDDDDDDDDDDDDDD43333333CDDDDDDDDDDDDDD3CDDD333333333CDD333CDDDDDDDDDDDDDDD43333333DDDD333CDDDDDDDDDDDNDDDDD33333333CDDDDDDDDDDDDDDDDDDDDD4333333CDDDDDDDDDDDDDDD3333DD333333333DDDDDDDDDDDDDDDDDDDD3333333333CDDDD3CDDDDDDDDDDD333CDD3333333DDDDDDDDDDDDDDDDDDDDD3333333DDDDDDDDDDD333CDDDDDD3333333DDDDDDDDDDDDDDDDDDDDD4333333CDDDDD333333CDDDDDDDDDD333CDDDDDD3333333DDDDDDDDDDDDDDDDDDD33333333CDDDDD4333CDDDDDDDDDD3333333DDDDDDDDDDDDDDDDDDDD4333333CDDD333D4333333DDDDDD333CDDDDDDDDDD4333333DDDDDDDDDDDDDD4333DD3333333D4333DDDDDDDDDDD333333CDDDDDDDDDDDDDDDDDDDD333333CD333CDDDDD4333333D333CDDDDDDDDDDDDDD4333333DDDDDDDDD4333DDDDDDD3333333DDDDDDNDDDDDDDDDDDD333333CDDDDDDDDDDDDDDD333C3333334333DDDDDDDDD4333333DDDDDDDDDDDDDDDDDDD333333CDDDD333DDDDDDDDD433333CDDDDDDDDDDDDDDDD333333333DDDDDDDDDDD333DDDDD4333C333CDDDDDDDDDD433333CD333CDDDDDDDDDDDDD333333333CDDDDDDDDDDD333333DDDDDDDDDDDD43CDDDD333333DDDDD4333DDDNDDDDD43333333DDDDDDDDDDDDDDDDDD333333CDDDDDDDDDDDDDDDDD433333CDDDDDDDDDDDDDDDDD433333CDDDDDDDDDDDDDDDDD4333333433CDDDDDDDDDDDDDD333333CDDDDDDDDDDD4333DD433333DDDDDDDDDDDDDDDDD433333CDDDDDDDDDDDDDDDDD333333DDDDDDDDDDDDDDDDD433333CDDDDDDDDDDDDDDDDDDD433CDD433333DDD433DDDDDDDDDDD333333DDDD4CDDDDDDDDDDD33333CDDDDDDDDDDDDDD43333333CDDD43DDDDDDDDDD4333333CDDDDDDDDDD4CDDDDDDDDDDD333DD333333CDDDDDDDDDDDDDDD33333333DDDDDDDDDDDDDD433333DDDDDDDDDDD3DDDD4333333CDDDDDDDDD33DDD333333DDD33CDDDD33333CDDDDD333DDDDDDD433333DDDDDDDDDDDDDDDD33333CDDDDDDDDDD33333333DDDDDDDDDDDDDDDD33333CDDDDDDDDD433CDD433333DDDDDDD433DDD43333333DDDNDDDDDDD43333CDDDDDDDDDDDDDD333333CDDDDDD433CDDDDD33333DDDDDDDDDDDDDD3333333DDDD333DDNDDDD43333CDDDDD333DDDDDDD33333DDDDDDDDDDDD43333333DDDDDDDD333DDDD33333D433DDDDDDDDDDD33333DDDDDDDD33CDDDD433333DDDDNDDDDDDDD43333C43333CDDDDDDDDDDDDDD33333DDDDDDDDDDDDDD43333CD433DDDDDDD33333DDD3CDDDDDDDDD43333C433DDDNDDDDD33333DDDDDDDDDD33CD433DDDDDDD43333CDDDDDDDDDDDDD43333DDDDDDDDDDDDDD33333DDDDDDDDDDDD433333CDDDDDDDDDDDDD43333CDDDDDDDDDDDDD43333DDDDDDDDDDDDD3433DDDDDDDDDD43333DDDDDDDDD33DD43333433DDDDDDD33CD3333CDDD3DDDDDDDDD3333CDDDDDDDDDDDDD3333CDDDDDDDDDDDD33333CDDD4DDDDDD3333CDDDDDDD43C43333DDDDDDDDDDDDD3333CDDDDDD433DDD43333DDDDDDDDDDDDD3333DDDDDDDDDDDDD4333CDDDDDDDDDDDD43333DDDDDDD43CD43333DD43CDDDDDDD4333CDDDDDDD433DD433333C4DDDDDDD3333DDDDDDDDDDDD33333DDDDDD33CDDD43333DDDDDDDDDDDD4333C33DDNDDDD4333CD33DDDDDDDD4333C33DDDD4"D43D3CDDD3DDD43DD4333CDDDD33DDDDDD3333CDDDDDDDDDDD3333DDDD43DDDDDD43333CDDDDDDD33DD3333DDDDDDDDDDD4333CD4"DD4DDDDD333CDDDDDDDDDDD4333DDDDDDDDDD43333CDDDDDDDDDDDDDDD3333DDDDDD33DDD333CDDDDDDDDDD4333CDDDDDDDD4#4333DDD$B3"DDDD3333DDDDDDDDD33333C33DDDDDDDD4333C433CDDD43CDDDD333CDDDDDDDDDD333CDDDDDDD33D4333D,3"D4DD4#2"DDDDDDD33D433343CDDDDD4333DDDDDDDDADDDDDDD4333DDDDDDDDDD333CDDDDDDDDD4333DDDNDDD,D"33DC4#B"DDD433CDDDDDDDDDD333DDDDDDDDDD333!BDDD4333DDDDDDDD333CDDDDDDDDD333CDDDDDD43D330 and 1 or x<0 and -1 or 0 end function g_entity_move(self, speed) local math_floor = math.floor local math_sign = g_math_sign local math_min = math.min local math_max = math.max local sign_speed = math_sign(speed) if mget( math_max(math_min(math_floor(self.pos_x + self.dir_x * speed + sign_speed * math_sign(self.dir_x) * 0.25), g_LEVEL_WIDTH - 1), 0), math_floor(self.pos_y)) == 0 then self.pos_x = self.pos_x + self.dir_x * speed end if mget( math_floor(self.pos_x), math_max(math_min(math_floor(self.pos_y + self.dir_y * speed + sign_speed * math_sign(self.dir_y) * 0.25), g_LEVEL_HEIGHT - 1), 0)) == 0 then self.pos_y = self.pos_y + self.dir_y * speed end end Weapon = { ui_tex_idx = 0, ammo = 0, weapon_texture_y = 0, textures_x = {}, raise_time = 0, shoot_time = 0, reload_time = 0, double_spread_angle = 0, min_dmg = 0, max_dmg = 0, n_pellets = 0, range_sqr = 0, } Weapon.__index = Weapon -- Weapon States: -- 0: raise -- 1: lower -- 2: ready -- 3: shoot -- 4: reload function Weapon.new(ui_tex_idx, ammo, texture_y, textures_x, raise_time, shoot_time, reload_time, spread_angle, min_dmg, max_dmg, n_pellets, range) local self = setmetatable({}, Weapon) self.ui_tex_idx = ui_tex_idx self.ammo = ammo self.texture_y = texture_y self.textures_x = textures_x self.raise_time = raise_time self.shoot_time = shoot_time self.reload_time = reload_time self.double_spread_angle = spread_angle * 2 self.min_dmg = min_dmg self.max_dmg = max_dmg self.n_pellets = n_pellets self.range_sqr = range * range return self end function g_ray_entity_collides(rel_pos_x, rel_pos_y, dir_x, dir_y, hitbox_rad) if (rel_pos_x * dir_x + rel_pos_y * dir_y) / math.sqrt(rel_pos_x * rel_pos_x + rel_pos_y * rel_pos_y) > 0 then --in front local dist_perp = math.abs(dir_x * rel_pos_y - dir_y * rel_pos_x) if dist_perp < hitbox_rad then return true end end return false end -- Target Types -- 0: Player -- 1: Enemies function Weapon:shoot(entity, target_type) local math_cos = math.cos local math_sin = math.sin local math_random = math.random local math_floor = math.floor local math_abs = math.abs local ray_entity_collides = g_ray_entity_collides sfx(0) self.ammo = self.ammo - 1 for _=1,self.n_pellets do local angle = (math_random() - 0.5) * self.double_spread_angle local dir_x = entity.dir_x * math_cos(angle) - entity.dir_y * math_sin(angle) local dir_y = entity.dir_x * math_sin(angle) + entity.dir_y * math_cos(angle) local damage = math_floor(math_random(self.min_dmg, self.max_dmg)) local map_x = math_floor(entity.pos_x) local map_y = math_floor(entity.pos_y) local delta_dist_x = math_abs(1 / dir_x) local delta_dist_y = math_abs(1 / dir_y) local step_x local side_dist_x if dir_x < 0 then step_x = -1 side_dist_x = (entity.pos_x - map_x) * delta_dist_x else step_x = 1 side_dist_x = (map_x + 1.0 - entity.pos_x) * delta_dist_x end local step_y local side_dist_y if dir_y < 0 then step_y = -1 side_dist_y = (entity.pos_y - map_y) * delta_dist_y else step_y = 1 side_dist_y = (map_y + 1.0 - entity.pos_y) * delta_dist_y end -- DDA local side while true do if side_dist_x < side_dist_y then side_dist_x = side_dist_x + delta_dist_x map_x = map_x + step_x side = 0 else side_dist_y = side_dist_y + delta_dist_y map_y = map_y + step_y side = 1 end tile_data = mget(map_x, map_y) if tile_data > 0 and tile_data // 16 < 8 then --if more than half-height break end end local wall_dist if side == 0 then wall_dist = side_dist_x - delta_dist_x else --side == 1 wall_dist = side_dist_y - delta_dist_y end local wall_dist_x = dir_x * wall_dist local wall_dist_y = dir_y * wall_dist local wall_dist_sqr = wall_dist * wall_dist local max_dist_sqr = math.min(wall_dist_sqr, self.range_sqr) if target_type == 0 then local player = g_player local rel_pos_x = player.pos_x - entity.pos_x local rel_pos_y = player.pos_y - entity.pos_y local dist_sqr = rel_pos_x * rel_pos_x + rel_pos_y * rel_pos_y if dist_sqr < max_dist_sqr and ray_entity_collides(rel_pos_x, rel_pos_y, dir_x, dir_y, player.hitbox_rad) then player:damage(damage * g_ENEMY_WEAPON_SCALING[g_settings.difficulty]) end else --target_type == 1 local enemies = g_enemies local closest_enemy local closest_enemy_dist_sqr = 9999 for _,enemy in pairs(enemies) do if enemy ~= nil then local rel_pos_x = enemy.pos_x - entity.pos_x local rel_pos_y = enemy.pos_y - entity.pos_y local dist_sqr = rel_pos_x * rel_pos_x + rel_pos_y * rel_pos_y if closest_enemy_dist_sqr > dist_sqr and ray_entity_collides(rel_pos_x, rel_pos_y, dir_x, dir_y, enemy.hitbox_rad) then closest_enemy = enemy closest_enemy_dist_sqr = dist_sqr end end end if closest_enemy_dist_sqr < max_dist_sqr then closest_enemy:damage(damage * g_WEAPON_SCALING[g_settings.difficulty]) local dist = math.sqrt(closest_enemy_dist_sqr) - 0.1 hitmarker_set( entity.pos_x + dir_x * dist, entity.pos_y + dir_y * dist, 1 ) elseif wall_dist_sqr == max_dist_sqr then if side == 0 then local dx = step_x * -0.1 hitmarker_set(entity.pos_x + wall_dist_x + dx, entity.pos_y + wall_dist_y + dx * dir_y / dir_x, 0) else --side == 1 local dy = step_y * -0.1 hitmarker_set(entity.pos_x + wall_dist_x + dy * dir_x / dir_y, entity.pos_y + wall_dist_y + dy, 0) end end end end end Item = { id = 0, pos_x = 0, pos_y = 0, type = 0, value = 0, sprite = nil, } Item.__index = Item -- Item types -- 0: Health -- 1: Ammo pistol -- 2: Ammo shotgun -- 3: Key -- Keys -- 1: Gold -- 2: Silver function Item.new(id, pos_x, pos_y, type, value) local self = setmetatable({}, Item) self.id = id self.pos_x = pos_x self.pos_y = pos_y self.type = type self.value = value if type == 3 then self.sprite = Sprite.new(pos_x, pos_y, 69 + value, 8, 4, 0.75) else self.sprite = Sprite.new(pos_x, pos_y, 64 + 2 * type, 4, 4, 0.75) end return self end function Item:process(_delta) local player = g_player local rel_pos_x = player.pos_x - self.pos_x local rel_pos_y = player.pos_y - self.pos_y local dist_sqr = rel_pos_x * rel_pos_x + rel_pos_y * rel_pos_y if dist_sqr < 0.04 then if self.type == 0 then player.health = player.health + self.value elseif self.type == 1 then local weapon = g_WEAPONS[2] weapon.ammo = weapon.ammo + self.value elseif self.type == 2 then local weapon = g_WEAPONS[3] weapon.ammo = weapon.ammo + self.value else --self.type == 3 player.keys[self.value] = 1 end g_items[self.id] = nil end end Hitmarker = { visible = false, timer = 0, sprite = nil, } Hitmarker.__index = Hitmarker function Hitmarker.new() local self = setmetatable({}, Hitmarker) self.sprite = Sprite.new(0, 0, 0, 6, 6, 0) return self end -- Hitmarker types -- 0: wall -- 1: enemy function hitmarker_set(pos_x, pos_y, type) for _,hitmarker in pairs(g_hitmarkers) do if not hitmarker.visible then hitmarker.timer = 0 hitmarker.visible = true hitmarker.sprite.pos_x = pos_x hitmarker.sprite.pos_y = pos_y hitmarker.sprite.tex_id = 59 + type break end end end function Hitmarker:process(delta) if self.visible then self.timer = self.timer + delta if self.timer > 200 then self.visible = false end end end Player = { pos_x = 0, pos_y = 0, dir_x = -1, dir_y = 0, plane_x = 0, plane_y = 0.8, speed_move = 0.003, weapon_idx = 1, weapon_state = 0, weapon_timer = 0, health = 100, hitbox_rad = 0.4, move = g_entity_move, keys = { [1] = 0, [2] = 0, } } Player.__index = Player function Player.new(pos_x, pos_y) local self = setmetatable({}, Player) self.pos_x = pos_x self.pos_y = pos_y return self end function Player:rotate(delta) local speed = g_settings.turn_speed * delta local old_dir_x = self.dir_x local math_cos = math.cos local math_sin = math.sin self.dir_x = self.dir_x * math_cos(speed) - self.dir_y * math_sin(speed) self.dir_y = old_dir_x * math_sin(speed) + self.dir_y * math_cos(speed) local old_plane_x = self.plane_x self.plane_x = self.plane_x * math_cos(speed) - self.plane_y * math_sin(speed) self.plane_y = old_plane_x * math_sin(speed) + self.plane_y * math_cos(speed) end function Player:damage(value) if self.health > 0 then self.health = self.health - value if self.health <= 0 then g_game_state = 2 g_timer = 0 end end end function Player:process(delta) self.weapon_timer = self.weapon_timer + delta local weapon = g_WEAPONS[self.weapon_idx] if btn(2) then self:rotate(delta) elseif btn(3) then self:rotate(-delta) end if btn(0) then self:move(self.speed_move * delta) elseif btn(1) then self:move(-self.speed_move * delta) end if btnp(4) then if self.weapon_state == 2 then self.weapon_state = 1 self.weapon_timer = 0 end elseif btnp(5) and self.weapon_state == 2 and weapon.ammo > 0 then self.weapon_state = 3 self.weapon_timer = 0 weapon:shoot(self, 1) end if self.weapon_state == 0 then if self.weapon_timer >= weapon.raise_time then self.weapon_state = 2 end elseif self.weapon_state == 1 then if self.weapon_timer >= weapon.raise_time then self.weapon_state = 0 self.weapon_timer = 0 self.weapon_idx = self.weapon_idx % 3 + 1 end elseif self.weapon_state == 3 then if self.weapon_timer >= weapon.shoot_time then if weapon.reload_time == 0 then self.weapon_state = 2 else self.weapon_state = 4 self.weapon_timer = 0 end end elseif self.weapon_state == 4 then if self.weapon_timer >= weapon.reload_time then self.weapon_state = 2 end end end Sprite = { pos_x = 0, pos_y = 0, tex_id = 0, scl_horiz = 1, scl_vert = 1, offset_vert = 0, --from 0.5 (floor) to -0.5 (ceiling) screen_offset_vert = 0, screen_width = 0, screen_height = 0, dist = 0, --Distance to player (negative if not in viewing triangle) screen_x = 0, draw_start_y = 0, draw_end_y = 0, draw_start_x = 0, draw_end_x = 0, } Sprite.__index = Sprite function Sprite.new(pos_x, pos_y, tex_id, scl_horiz, scl_vert, offset_vert) local self = setmetatable({}, Sprite) self.pos_x = pos_x self.pos_y = pos_y self.tex_id = tex_id self.scl_horiz = scl_horiz self.scl_vert = scl_vert self.offset_vert = offset_vert return self end function Sprite:process(inv_det) local SCREEN_WIDTH = g_SCREEN_WIDTH local SCREEN_HEIGHT = g_SCREEN_HEIGHT local SCREEN_HALF_HEIGHT = SCREEN_HEIGHT / 2 local player = g_player local math_abs = math.abs local rel_x = self.pos_x - player.pos_x local rel_y = self.pos_y - player.pos_y self.dist = math.sqrt(rel_x * rel_x + rel_y * rel_y) local trans_y = inv_det * (player.plane_x * rel_y - player.plane_y * rel_x) if trans_y <= 0 then self.dist = -self.dist return end local trans_x = inv_det * (player.dir_y * rel_x - player.dir_x * rel_y) self.screen_x = math.floor((SCREEN_WIDTH * 0.5) * (1 + trans_x / trans_y)) self.screen_width = math_abs(SCREEN_HEIGHT / trans_y) / self.scl_horiz self.draw_start_x = self.screen_x - self.screen_width // 2 if self.draw_start_x < 0 then self.draw_start_x = 0 elseif self.draw_start_x >= SCREEN_WIDTH then self.dist = -self.dist return end self.draw_end_x = self.screen_x + self.screen_width // 2 if self.draw_end_x >= SCREEN_WIDTH then self.draw_end_x = SCREEN_WIDTH - 1 elseif self.draw_end_x < 0 then self.dist = -self.dist return end self.screen_height = math_abs(SCREEN_HEIGHT / trans_y) / self.scl_vert self.screen_offset_vert = SCREEN_HALF_HEIGHT * self.offset_vert // trans_y self.draw_start_y = SCREEN_HALF_HEIGHT - self.screen_height // 2 + self.screen_offset_vert if self.draw_start_y < 0 then self.draw_start_y = 0 end self.draw_end_y = SCREEN_HALF_HEIGHT + self.screen_height // 2 + self.screen_offset_vert if self.draw_end_y >= SCREEN_HEIGHT then self.draw_end_y = SCREEN_HEIGHT - 1 end end function Sprite:set_enemy_tex(tex_id) self.tex_id = tex_id self.scl_horiz = 48 / g_SPRITE_SIZES[tex_id][1] end Enemy = { id = 0, pos_x = 0, pos_y = 0, dir_x = -1, dir_y = 0, state = 0, timer = 0, tex_ids = {}, sprite = nil, weapon = nil, health = 0, activate_dist_sqr = 0, shoot_dist_sqr = 0, die_time = 0, speed_move = 0, hitbox_rad = 0, pain_time = 0, pain_chance = 0, } Enemy.__index = Enemy g_enemy_pistol_tex_ids = {192, 194, 195, 196, 198, 200, 202, 204, 96, 206, 76} g_enemy_shotgun_tex_ids = {128, 130, 131, 132, 134, 136, 138, 140, 100, 142, 78} -- Enemy types -- 0: pistol -- 1: shotgun -- Enemy states -- 0: neutral -- 1: raise -- 2: ready -- 3: shoot -- 4: reload -- 5: die -- 6: hurt -- Enemy sprites -- 01: forward -- 02: left -- 03: right -- 04: back -- 05: raise -- 06: ready -- 07: shoot -- 08: die -- 09: corpse -- 10: walk -- 11: pain function Enemy.new(id, type, pos_x, pos_y, angle) local self = setmetatable({}, Enemy) self.id = id self.pos_x = pos_x self.pos_y = pos_y self.activate_dist_sqr = 64 self.shoot_dist_sqr = 16 self.dir_x = math.cos(angle) self.dir_y = math.sin(angle) if type == 0 then self.tex_ids = g_enemy_pistol_tex_ids self.sprite = Sprite.new(pos_x, pos_y, 0, 1, 1.5, 0.25) self.weapon = g_ENEMY_WEAPONS[1] self.health = 30 self.die_time = 1000 self.speed_move = 0.002 self.hitbox_rad = 0.3 self.pain_time = 170 self.pain_chance = 0.7 elseif type == 1 then self.tex_ids = g_enemy_shotgun_tex_ids self.sprite = Sprite.new(pos_x, pos_y, 0, 1, 1.5, 0.25) self.weapon = g_ENEMY_WEAPONS[2] self.health = 60 self.die_time = 1000 self.speed_move = 0.0015 self.hitbox_rad = 0.3 self.pain_time = 210 self.pain_chance = 0.61 end return self end function Enemy:move(speed) g_entity_move(self, speed) self.sprite.pos_x = self.pos_x self.sprite.pos_y = self.pos_y end function Enemy:process(delta) local player = g_player local rel_pos_x = player.pos_x - self.pos_x local rel_pos_y = player.pos_y - self.pos_y self.dist_sqr = rel_pos_x * rel_pos_x + rel_pos_y * rel_pos_y local angle = math.atan2( self.dir_x * rel_pos_y - self.dir_y * rel_pos_x, self.dir_x * rel_pos_x + self.dir_y * rel_pos_y) local abs_angle = math.abs(angle) self.timer = self.timer + delta if self.state == 0 then if self.dist_sqr <= 1 or (abs_angle <= 0.5235988 --30 deg and self.activate_dist_sqr >= self.dist_sqr) then self.state = 1 self.timer = 0 self.sprite:set_enemy_tex(self.tex_ids[5]) else local tex_idx if abs_angle <= 0.785398163397 then --pi / 4 tex_idx = 1 elseif abs_angle >= 2.35619449019 then --3 * pi / 4 tex_idx = 4 elseif angle > 0 then tex_idx = 2 else tex_idx = 3 end self.sprite:set_enemy_tex(self.tex_ids[tex_idx]) end elseif self.state == 1 then if self.timer >= self.weapon.raise_time then self.state = 2 self.sprite:set_enemy_tex(self.tex_ids[6]) end elseif self.state == 2 then local math_floor = math.floor local math_abs = math.abs local inv_mag = 1 / math.sqrt(self.dist_sqr) self.dir_x = rel_pos_x * inv_mag self.dir_y = rel_pos_y * inv_mag local map_x = math_floor(self.pos_x) local map_y = math_floor(self.pos_y) local delta_dist_x = math_abs(1 / self.dir_x) local delta_dist_y = math_abs(1 / self.dir_y) local step_x local side_dist_x if self.dir_x < 0 then step_x = -1 side_dist_x = (self.pos_x - map_x) * delta_dist_x else step_x = 1 side_dist_x = (map_x + 1.0 - self.pos_x) * delta_dist_x end local step_y local side_dist_y if self.dir_y < 0 then step_y = -1 side_dist_y = (self.pos_y - map_y) * delta_dist_y else step_y = 1 side_dist_y = (map_y + 1.0 - self.pos_y) * delta_dist_y end -- DDA LOS check local side while true do if side_dist_x < side_dist_y then side_dist_x = side_dist_x + delta_dist_x map_x = map_x + step_x side = 0 else side_dist_y = side_dist_y + delta_dist_y map_y = map_y + step_y side = 1 end tile_data = mget(map_x, map_y) if tile_data > 0 and tile_data // 16 < 8 then --if more than half-height break end end local wall_dist_sqr if side == 0 then wall_dist_sqr = side_dist_x - delta_dist_x else --side == 1 wall_dist_sqr = side_dist_y - delta_dist_y end wall_dist_sqr = wall_dist_sqr * wall_dist_sqr if self.shoot_dist_sqr < self.dist_sqr --player too far or wall_dist_sqr < self.dist_sqr then --player not visible self:move(self.speed_move * delta) local tex_idx if self.timer // 500 % 2 == 0 then --TODO:un-hardcode tex_idx = 6 else tex_idx = 10 end self.sprite:set_enemy_tex(self.tex_ids[tex_idx]) else self.state = 3 self.timer = 0 self.weapon:shoot(self, 0) self.sprite:set_enemy_tex(self.tex_ids[7]) end elseif self.state == 3 then if self.timer >= self.weapon.shoot_time then self.state = 4 self.timer = 0 self.sprite:set_enemy_tex(self.tex_ids[6]) end elseif self.state == 4 then if self.timer >= self.weapon.reload_time then self.state = 2 end elseif self.state == 5 then if self.timer >= self.die_time then self.sprite.tex_id = self.tex_ids[9] self.sprite.scl_horiz = 1 self.sprite.scl_vert = 4 self.sprite.offset_vert = 0.75 g_enemies[self.id] = nil table.insert(g_sprites, self.sprite) end elseif self.state == 6 then if self.timer >= self.pain_time then self.state = 2 self.sprite:set_enemy_tex(self.tex_ids[6]) end end end function Enemy:damage(value) if self.health > 0 then self.health = self.health - value if self.health <= 0 then self.sprite:set_enemy_tex(self.tex_ids[8]) self.state = 5 self.timer = 0 else if (not (self.state == 6)) and math.random() < self.pain_chance then self.state = 6 self.timer = 0 self.sprite:set_enemy_tex(self.tex_ids[11]) elseif self.state == 0 then self.state = 2 self.sprite:set_enemy_tex(self.tex_ids[6]) end end end end Door = { map_x = 0, map_y = 0, opening = false, closed = true, type = 1, tex_id = 0, height = 0, timer = 0, } Door.__index = Door function Door.new(map_x, map_y, tex_id, type) local self = setmetatable({}, Door) self.map_x = map_x self.map_y = map_y self.tex_id = tex_id self.type = type return self end function Door:process(delta) if self.closed then if self.opening then self.timer = self.timer + delta if self.timer > 100 then if self.height == 15 then self.closed = false mset(self.map_x, self.map_y, 0) else self.timer = 0 self.height = self.height + 1 mset(self.map_x, self.map_y, self.tex_id + 16 * self.height) end end else local player = g_player if player.keys[self.type] == 1 then local rel_pos_x = player.pos_x - self.map_x - 0.5 local rel_pos_y = player.pos_y - self.map_y - 0.5 local dist_sqr = rel_pos_x * rel_pos_x + rel_pos_y * rel_pos_y if dist_sqr < 2.0 then self.opening = true end end end end end function g_get_tex_pixel(offset, id, x, y) return peek4(offset + 0x40 * (id + 16 * (y // 8) + x // 8) + 0x8 * (y % 8) + (x % 8)) end function init() g_SCREEN_WIDTH = 240 g_SCREEN_HEIGHT = 120 g_LEVEL_WIDTH = 113 g_LEVEL_HEIGHT = 32 g_SPRITE_SIZES = { [0]= {16,16}, [2]= {16,16}, [4]= {16,16}, [6]= {16,16}, [8]= {16,16}, --Enemy pistol [192]={16,32}, [194]={8 ,32}, [195]={8 ,32}, [196]={16,32}, [198]={16,32}, [200]={16,32}, [202]={16,32}, [204]={16,32}, [96]= {32,16}, [206]={16,32}, [76]= {16,32}, --Enemy shotgun [128]={16,32}, [130]={8 ,32}, [131]={8 ,32}, [132]={16,32}, [134]={16,32}, [136]={16,32}, [138]={16,32}, [140]={16,32}, [100]={32,16}, [142]={16,32}, [78]= {16,32}, --Items [64]= {16,16}, [66]= {16,16}, [68]= {16,16}, [70]= {8 ,16}, [71]= {8 ,16}, --Hitmarkers [59]= {8, 8 }, [60]= {8, 8 }, } g_TEX_MAP = { [1]=1, [2]=3, [3]=5, [4]=7, [5]=9, [6]=11, [7]=13, [8]=33, [9]=35, [10]=37, [11]=39, [12]=41, [13]=43, } g_player = Player.new(10, 7) g_prev_time = 0 g_sprites = { Sprite.new(8.5, 5.5, 2, 2, 1, 0.125), Sprite.new(45.5, 27.5, 6, 2, 2, -0.5), Sprite.new(40, 14, 6, 2, 2, -0.5), Sprite.new(38.5, 27.5, 0, 2, 2, 0.5), Sprite.new(32.5, 18.5, 8, 1, 1, 0), Sprite.new(32.5, 21.5, 8, 1, 1, 0), Sprite.new(36.5, 15.5, 4, 2, 2, 0.5), Sprite.new(62.5, 17.5, 6, 2, 2, -0.5), Sprite.new(71.5, 17.5, 6, 2, 2, -0.5), Sprite.new(80.5, 17.5, 6, 2, 2, -0.5), Sprite.new(89.5, 17.5, 6, 2, 2, -0.5), Sprite.new(98.5, 17.5, 6, 2, 2, -0.5), Sprite.new(107.5, 17.5, 6, 2, 2, -0.5), Sprite.new(62.5, 25.5, 6, 2, 2, -0.5), Sprite.new(71.5, 25.5, 6, 2, 2, -0.5), Sprite.new(80.5, 25.5, 6, 2, 2, -0.5), Sprite.new(89.5, 25.5, 6, 2, 2, -0.5), Sprite.new(98.5, 25.5, 6, 2, 2, -0.5), Sprite.new(107.5, 25.5, 6, 2, 2, -0.5), } g_WEAPONS = { Weapon.new(204, 1e9, 107, {234, 228}, 300, 250, 0 , 0.05, 2, 20, 1, 1 ), Weapon.new(200, 0 , 113, {234, 228}, 350, 350, 0 , 0.05, 5, 15, 1, 32), Weapon.new(196, 0 , 113, {222, 216, 210}, 400, 350, 350, 0.12, 5, 15, 7, 32), } g_ENEMY_WEAPONS = { Weapon.new(0, 99999999, 0, nil, 0, 350, 0 , 0.4, 3, 15, 1, 32), Weapon.new(0, 99999999, 0, nil, 0, 350, 350, 0.4, 3, 15, 3, 32), } g_ENEMY_WEAPON_SCALING = { [1] = 0.65, [2] = 1.0, [3] = 1.35, } g_WEAPON_SCALING = { [1] = 1.2, [2] = 1.0, [3] = 0.9, } g_enemies = { [1]=Enemy.new(1, 0, 8.5, 10.5, 1.57), [2]=Enemy.new(2, 0, 23.5, 16.5, 1.57), [3]=Enemy.new(3, 0, 23.5, 23.5, -1.57), [4]=Enemy.new(4, 0, 45.5, 26.5, 1.57), [5]=Enemy.new(5, 0, 45.5, 28.5, -1.57), [6]=Enemy.new(6, 1, 43.5, 14.5, 3.14), [7]=Enemy.new(7, 0, 50.5, 13.5, 0), [8]=Enemy.new(8, 0, 47.5, 13.5, 0), [9]=Enemy.new(9, 0, 76, 15.5, 1.57), [10]=Enemy.new(10, 1, 67, 15.5, 1.57), [11]=Enemy.new(11, 0, 85, 15.5, 1.57), [12]=Enemy.new(12, 1, 94, 15.5, 1.57), [13]=Enemy.new(13, 0, 103, 15.5, 1.57), [14]=Enemy.new(14, 0, 76, 27.5, -1.57), [15]=Enemy.new(15, 1, 67, 27.5, -1.57), [16]=Enemy.new(16, 0, 85, 27.5, -1.57), [17]=Enemy.new(17, 1, 94, 27.5, -1.57), [18]=Enemy.new(18, 0, 103, 27.5, -1.57), [19]=Enemy.new(19, 1, 95.5, 21.5, 3.14), [20]=Enemy.new(20, 0, 55.5, 19.5, 1.57), } g_items = { [1]= Item.new(1, 45.5, 27.5, 3, 2), [2]= Item.new(2, 59.5, 27.5, 3, 1), [3]= Item.new(3, 10.5, 13.5, 0, 35), [4]= Item.new(4, 6.5, 13.5, 1, 15), [5]= Item.new(5, 36.5, 27.5, 2, 4), [6]= Item.new(6, 45.5, 6.5, 0, 15), [7]= Item.new(7, 47.5, 13.5, 2, 2), [8]= Item.new(8, 56.5, 6.5, 2, 2), [9]= Item.new(9, 55.5, 21.5, 0, 25), [10]=Item.new(10, 40.5, 27.5, 1, 15), [11]=Item.new(11, 37.5, 14.5, 1, 15), [12]=Item.new(12, 60.5, 21.5, 0, 50), [13]=Item.new(13, 65.5, 21.5, 1, 20), [14]=Item.new(14, 70.5, 21.5, 2, 6), } g_doors = { Door.new(110, 21, 6, 1), Door.new(57, 21, 7, 2), } g_NUM_HITMARKERS = 7 g_hitmarkers = {} for i=1,g_NUM_HITMARKERS do g_hitmarkers[i] = Hitmarker.new() end g_timer = 0 g_settings = { floor_ceil = false, interlace = 2, --disabled=g_interlace>=2 difficulty = 2, debug = false, turn_speed = 0.003, selected_setting = 0, sprite = 0, } g_pause_text = { [true]="ON", [false]="OFF", [1]="EASY", [2]="NORM", [3]="HARD", } -- Game States -- 1: Alive -- 2: Dead -- 3: Paused g_game_state = 1 math.randomseed(tstamp()) end init() function TIC() local t = time() local delta = t - g_prev_time --msec since last frame g_prev_time = t g_timer = g_timer + delta local game_state = g_game_state if btnp(7) then if game_state == 1 then g_game_state = 3 g_timer = 0 g_settings.selected_setting = 0 elseif game_state == 3 then g_game_state = 1 end end if game_state == 1 then game_process(delta) elseif game_state == 2 then death_process(delta) elseif game_state == 3 then pause_process(delta) end end function death_process(delta) if g_timer >= 3000 then reset() end local dist = math.floor(g_timer * 0.0272) for i=0,7 do spr(328, i * 32, dist, 0, 1, 0, 0, 4, 1) end end function pause_process(delta) local settings = g_settings local pause_text = g_pause_text if g_timer >= 750 then settings.sprite = (settings.sprite + 1) % 2 g_timer = 0 end map(226, 91, 14, 11, 64, 20) print("Interlacing", 72, 40) print("Floor&Ceil", 72, 52) print("Difficulty", 72, 64) print("Turn Speed", 72, 76) print("Debug", 72, 88) if btnp(0) then settings.selected_setting = math.max(0, settings.selected_setting - 1) elseif btnp(1) then settings.selected_setting = math.min(4, settings.selected_setting + 1) end local setting_change = 0 if btnp(2) then setting_change = -1 elseif btnp(3) then setting_change = 1 end if setting_change ~= 0 then if settings.selected_setting == 0 then if settings.interlace < 2 then settings.interlace = 2 else settings.interlace = 0 end elseif settings.selected_setting == 1 then settings.floor_ceil = not settings.floor_ceil elseif settings.selected_setting == 2 then settings.difficulty = math.min(3, math.max(1, settings.difficulty + setting_change)) elseif settings.selected_setting == 3 then settings.turn_speed = math.max(0, settings.turn_speed + setting_change * 0.001) else --settings.selected_setting == 4 settings.debug = not settings.debug end end print(pause_text[settings.interlace < 2], 142, 40) print(pause_text[settings.floor_ceil], 142, 52) print(pause_text[settings.difficulty], 142, 64) print(math.floor(settings.turn_speed * 1000), 142, 76) print(pause_text[settings.debug], 142, 88) local selected_setting_y = 38 + 12 * settings.selected_setting spr(317 + settings.sprite, 134, selected_setting_y, 0) spr(317 + settings.sprite, 165, selected_setting_y, 0, 1, 1) end function game_process(delta) local SCREEN_WIDTH = g_SCREEN_WIDTH local SCREEN_HEIGHT = g_SCREEN_HEIGHT local SCREEN_HALF_HEIGHT = SCREEN_HEIGHT // 2 local TEX_WIDTH = 16 local TEX_HEIGHT = 16 local SPRITE_SIZES = g_SPRITE_SIZES local TEX_MAP = g_TEX_MAP local player = g_player local sprites = g_sprites local enemies = g_enemies local items = g_items local hitmarkers = g_hitmarkers local doors = g_doors local settings = g_settings local WEAPON_X = 111 local WEAPON_Y = 72 local MINIMAP_WIDTH = 14 local MINIMAP_HEIGHT = 11 local MINIMAP_OFFSET_X = 185 local MINIMAP_OFFSET_Y = 122 local get_tex_pixel = g_get_tex_pixel local math_floor = math.floor local math_abs = math.abs local math_min = math.min local math_max = math.max local start_vline local step_vline if settings.interlace >= 2 then start_vline = 0 step_vline = 1 cls(0) else start_vline = (settings.interlace + 1) % 2 settings.interlace = start_vline step_vline = 2 for x=start_vline,SCREEN_WIDTH-1,step_vline do for y=0,SCREEN_HEIGHT-1 do pix(x, y, 0) end end end -- game logic t = time() player:process(delta) for _,enemy in pairs(enemies) do if enemy ~= nil then enemy:process(delta) end end for _,item in pairs(items) do if item ~= nil then item:process(delta) end end for _,hitmarker in pairs(hitmarkers) do hitmarker:process(delta) end for _,door in pairs(doors) do door:process(delta) end local t_temp = time() local t_logic = t_temp - t t = t_temp -- drawing local inv_det = 1 / (player.plane_x * player.dir_y - player.dir_x * player.plane_y) local visible_sprites = {} for _,sprite in pairs(sprites) do sprite:process(inv_det) if sprite.dist > 0 then visible_sprites[#visible_sprites+1] = sprite end end for _,enemy in pairs(enemies) do if enemy ~= nil then local sprite = enemy.sprite sprite:process(inv_det) if sprite.dist > 0 then visible_sprites[#visible_sprites+1] = sprite end end end for _,item in pairs(items) do if item ~= nil then local sprite = item.sprite sprite:process(inv_det) if sprite.dist > 0 then visible_sprites[#visible_sprites+1] = sprite end end end for _,hitmarker in pairs(hitmarkers) do if hitmarker.visible then local sprite = hitmarker.sprite sprite:process(inv_det) if sprite.dist > 0 then visible_sprites[#visible_sprites+1] = sprite end end end table.sort(visible_sprites, function(a,b) return a.dist < b.dist end) local num_visible_sprites = #visible_sprites --draw weapon local weapon = g_WEAPONS[player.weapon_idx] if player.weapon_state == 0 then map(weapon.textures_x[1], weapon.texture_y, 6, 6, WEAPON_X, WEAPON_Y + 48 * (1 - player.weapon_timer / weapon.raise_time)) elseif player.weapon_state == 1 then map(weapon.textures_x[1], weapon.texture_y, 6, 6, WEAPON_X, WEAPON_Y + 48 * player.weapon_timer / weapon.raise_time) elseif player.weapon_state == 2 then map(weapon.textures_x[1], weapon.texture_y, 6, 6, WEAPON_X, WEAPON_Y) elseif player.weapon_state >= 3 then map(weapon.textures_x[player.weapon_state - 1], weapon.texture_y, 6, 6, WEAPON_X + 2, WEAPON_Y + 2) end -- draw HUD map(210, 134, 25, 2, 0, 120) map(235, 134 - 2 * player.keys[1], 1, 1, 200, 120) map(235, 135 - 2 * player.keys[2], 1, 1, 200, 128) map(236, 128 + 2 * player.weapon_idx, 4, 2, 208, 120) font(math_floor(player.health), 19, 124) local weapon_ammo if player.weapon_idx == 1 then weapon_ammo = ':' else weapon_ammo = weapon.ammo end font(weapon_ammo, 69, 124) -- draw minimap local player_tile_x = math_floor(player.pos_x) local player_tile_y = math_floor(player.pos_y) local minimap_start_x = math_min(math_max(0, player_tile_x - MINIMAP_WIDTH // 2), g_LEVEL_WIDTH - MINIMAP_WIDTH - 1) local minimap_start_y = math_min(math_max(0, player_tile_y - MINIMAP_HEIGHT // 2), g_LEVEL_HEIGHT - MINIMAP_HEIGHT - 1) for x=0,MINIMAP_WIDTH do for y=0,MINIMAP_HEIGHT do local tile_val = mget(x + minimap_start_x, y + minimap_start_y) if tile_val ~= 0 then pix(MINIMAP_OFFSET_X + MINIMAP_WIDTH - x, y + MINIMAP_OFFSET_Y, (287 - tile_val) // 32) end end end pix(minimap_start_x + MINIMAP_OFFSET_X + MINIMAP_WIDTH - player_tile_x, player_tile_y - minimap_start_y + MINIMAP_OFFSET_Y, 12) -- draw walls and sprites for x=start_vline,SCREEN_WIDTH-1,step_vline do local camera_x = 2 * x / SCREEN_WIDTH - 1 local ray_dir_x = player.dir_x + player.plane_x * camera_x local ray_dir_y = player.dir_y + player.plane_y * camera_x local map_x = player_tile_x local map_y = player_tile_y local delta_dist_x = math_abs(1 / ray_dir_x) local delta_dist_y = math_abs(1 / ray_dir_y) local step_x local side_dist_x if ray_dir_x < 0 then step_x = -1 side_dist_x = (player.pos_x - map_x) * delta_dist_x else step_x = 1 side_dist_x = (map_x + 1.0 - player.pos_x) * delta_dist_x end local step_y local side_dist_y if ray_dir_y < 0 then step_y = -1 side_dist_y = (player.pos_y - map_y) * delta_dist_y else step_y = 1 side_dist_y = (map_y + 1.0 - player.pos_y) * delta_dist_y end local current_sprite = 1 local not_hit_full_wall = true local prev_draw_start = SCREEN_HEIGHT while not_hit_full_wall do -- DDA local side local tile_data while true do if side_dist_x < side_dist_y then side_dist_x = side_dist_x + delta_dist_x map_x = map_x + step_x side = 0 else side_dist_y = side_dist_y + delta_dist_y map_y = map_y + step_y side = 1 end tile_data = mget(map_x, map_y) if tile_data > 0 then break end end local perp_wall_dist if side == 0 then perp_wall_dist = side_dist_x - delta_dist_x else perp_wall_dist = side_dist_y - delta_dist_y end --draw sprites for sprite_idx=current_sprite,num_visible_sprites do local sprite = visible_sprites[sprite_idx] if sprite.dist >= perp_wall_dist then break end current_sprite = sprite_idx + 1 if x >= sprite.draw_start_x and x <= sprite.draw_end_x then local sprite_size = SPRITE_SIZES[sprite.tex_id] local a = sprite_size[2] / sprite.screen_height local sprite_tex_x = math_floor((x - (sprite.screen_x - sprite.screen_width / 2)) * sprite_size[1] / sprite.screen_width) % sprite_size[1] for y=sprite.draw_start_y,sprite.draw_end_y do local tex_y = math_floor((y - sprite.screen_offset_vert - SCREEN_HALF_HEIGHT + sprite.screen_height / 2) * a) % sprite_size[2] local color = get_tex_pixel(0xC000, sprite.tex_id, sprite_tex_x, tex_y) if color > 0 and pix(x, y) == 0 then pix(x, y, color) end end end end --draw wall local tile_height = tile_data // 16 / 16 -- 0=full height, 1=no height if tile_height == 0 then not_hit_full_wall = false end local line_height = SCREEN_HEIGHT // perp_wall_dist local draw_start = SCREEN_HALF_HEIGHT + math_floor(line_height * (tile_height - 0.5)) + 1 if draw_start < 0 then draw_start = 0 elseif draw_start >= SCREEN_HEIGHT then draw_start = SCREEN_HEIGHT - 1 end local draw_end = SCREEN_HALF_HEIGHT + line_height // 2 - 1 if draw_end > prev_draw_start then draw_end = prev_draw_start elseif draw_end >= SCREEN_HEIGHT then draw_end = SCREEN_HEIGHT - 1 end local wall_x if side == 0 then wall_x = player.pos_y + perp_wall_dist * ray_dir_y else wall_x = player.pos_x + perp_wall_dist * ray_dir_x end wall_x = wall_x - math_floor(wall_x) local tex_id = TEX_MAP[tile_data % 16] local tex_x = math_floor(wall_x * TEX_WIDTH) local step_tex = TEX_HEIGHT / line_height local testart_vline = (draw_start - SCREEN_HALF_HEIGHT + line_height * 0.5) * step_tex for y=draw_start,draw_end do if pix(x, y) == 0 then local tex_y = math_floor(testart_vline + step_tex * (y - draw_start)) % TEX_HEIGHT pix(x, y, get_tex_pixel(0x8000, tex_id, tex_x, tex_y)) end end --draw top of variable-height walls if tile_height > 0.5 then if side_dist_x < side_dist_y then perp_wall_dist = (map_x + step_x - player.pos_x + (1 - step_x) * 0.5) / ray_dir_x else perp_wall_dist = (map_y + step_y - player.pos_y + (1 - step_y) * 0.5) / ray_dir_y end line_height = SCREEN_HEIGHT // perp_wall_dist local top_draw_start = SCREEN_HALF_HEIGHT + math_floor(line_height * (tile_height - 0.5)) if top_draw_start >= SCREEN_HEIGHT then top_draw_start = SCREEN_HEIGHT - 1 end local row_distance_part = (2 * tile_height - 1) * SCREEN_HALF_HEIGHT for y=top_draw_start,draw_start - 1 do if pix(x, y) == 0 then local row_distance = row_distance_part / (y - SCREEN_HALF_HEIGHT) local floor_x = player.pos_x + row_distance * ray_dir_x local floor_y = player.pos_y + row_distance * ray_dir_y tex_x = math_floor(TEX_WIDTH * floor_x) % TEX_WIDTH local tex_y = math_floor(TEX_HEIGHT * floor_y) % TEX_HEIGHT pix(x, y, get_tex_pixel(0x8000, tex_id, tex_x, tex_y)) end end prev_draw_start = top_draw_start else prev_draw_start = draw_start end end end t_temp = time() local t_wall_sprite = t_temp - t t = t_temp --draw floor + ceiling if settings.floor_ceil then local ray_dir_x0 = player.dir_x - player.plane_x local ray_dir_y0 = player.dir_y - player.plane_y local ray_dir_x1 = player.dir_x + player.plane_x local ray_dir_y1 = player.dir_y + player.plane_y for y=SCREEN_HALF_HEIGHT,SCREEN_HEIGHT-1 do local row_distance = SCREEN_HALF_HEIGHT / (y - SCREEN_HALF_HEIGHT) local floor_step_x = row_distance * (ray_dir_x1 - ray_dir_x0) / SCREEN_WIDTH local floor_step_y = row_distance * (ray_dir_y1 - ray_dir_y0) / SCREEN_WIDTH local floor_x = player.pos_x + row_distance * ray_dir_x0 + start_vline * floor_step_x local floor_y = player.pos_y + row_distance * ray_dir_y0 + start_vline * floor_step_y floor_step_x = floor_step_x * step_vline floor_step_y = floor_step_y * step_vline for x=start_vline,SCREEN_WIDTH-1,step_vline do local tex_x = math_floor(TEX_WIDTH * floor_x) % TEX_WIDTH local tex_y = math_floor(TEX_HEIGHT * floor_y) % TEX_HEIGHT --draw floor if pix(x, y) == 0 then pix(x, y, get_tex_pixel(0x8000, 3, tex_x, tex_y)) --[CONST] end --draw ceiling if pix(x, SCREEN_HEIGHT - y - 1) == 0 then pix(x, SCREEN_HEIGHT - y - 1, get_tex_pixel(0x8000, 5, tex_x, tex_y)) --[CONST] end floor_x = floor_x + floor_step_x floor_y = floor_y + floor_step_y end end end t_temp = time() local t_floor = t_temp - t if settings.debug then print(string.format("FPS %d\n#SPR %d\nLOGIC %.1f\nWALL&SPR %.1f\nFLR&CEIL %.1f", math_floor(1000 / delta), num_visible_sprites, t_logic, t_wall_sprite, t_floor), 0, 0, 5) end end