_kh.bf_calcWalkDistance = function(s, x1, y1, x2, y2) local closed = {}; local open = { {p = {{x1, y1}}, l = 0} }; while #open > 0 do local item = table.remove (open, 1); local node = item.p[#item.p]; local inClosed = false; for i = 1, #closed do if (closed[i][1] == node[1] and closed[i][2] == node[2]) then inClosed = true; break; end; end; if (not inClosed) then if (node[1] == x2 and node[2] == y2) then return item; end; table.insert(closed, node); local pos = 1; while (#open >= pos and open[pos].l <= item.l) do pos = pos + 1; end; if s:isPassable(node[1], node[2] - 1) or (node[1] == x2 and node[2] - 1 == y2) then local p = {}; for i = 1, #item.p do table.insert(p, item.p[i]); end; table.insert(p, {node[1], node[2] - 1}); table.insert(open, pos, { p = p, l = item.l + 1 }); end; if s:isPassable(node[1], node[2] + 1) or (node[1] == x2 and node[2] + 1 == y2) then local p = {}; for i = 1, #item.p do table.insert(p, item.p[i]); end; table.insert(p, {node[1], node[2] + 1}); table.insert(open, pos, { p = p, l = item.l + 1 }); end; if s:isPassable(node[1] - 1, node[2]) or (node[1] - 1 == x2 and node[2] == y2) then local p = {}; for i = 1, #item.p do table.insert(p, item.p[i]); end; table.insert(p, {node[1] - 1, node[2]}); table.insert(open, pos, { p = p, l = item.l + 1 }); end; if s:isPassable(node[1] + 1, node[2]) or (node[1] + 1 == x2 and node[2] == y2) then local p = {}; for i = 1, #item.p do table.insert(p, item.p[i]); end; table.insert(p, {node[1] + 1, node[2]}); table.insert(open, pos, { p = p, l = item.l + 1 }); end; end; end; return nil; end; _kh.bf_calcShootDistance = function(x1, y1, x2, y2) --return math.max(math.abs(x2 - x1), math.abs(y2 - y1)); return math.floor(math.sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1))); end; _kh.bf_calcDirection = function(x1, y1, x2, y2) -- 0 - close by, 1-8 - north to north-west clockwise, 9 - too far local dist = _kh.bf_calcDistance(x1,y1,x2,y2); if (dist == 0) then return 0; elseif (dist == 1) then if (x1 == x2 and y1 > y2) then return 1; elseif (x1 < x2 and y1 > y2) then return 2; elseif (x1 < x2 and y1 == y2) then return 3; elseif (x1 < x2 and y1 < y2) then return 4; elseif (x1 == x2 and y1 < y2) then return 5; elseif (x1 > x2 and y1 < y2) then return 6; elseif (x1 > x2 and y1 == y2) then return 7; elseif (x1 > x2 and y1 > y2) then return 8; end; else return 9; end; end; hp_indicator = obj { nam = "hp_indicator"; disp = function(s) return "Здоровье:" .. pl.hp .. "/10"; end; }; hp_indicator:disable(); battlefield = function(tab) local make_turn = tab.make_turn; tab.showhp = true; tab.nosave = true; tab.noautosave = true; tab.not_follow = true; tab.battlefield = true; tab.ui_state = nil; tab.click = function(s, x, y) local tx = math.floor(x / 32) + 1; local ty = math.floor(y / 32) + 1; if s.ui_state == nil then -- No tile selected if s.plX == tx and s.plY == ty then pr [[Это вы. ]]; if pl.hp > 9 then pr [[Вы не ранены. ]]; elseif pl.hp > 7 then pr [[Вы легко ранены. ]]; elseif pl.hp > 3 then pr [[Вы ранены. ]]; else pr [[Вы тяжело ранены. ]]; end; pr [[Нажмите еще раз, чтобы ждать. ]]; s.ui_state = 1; return; end; for i = 1, #s.obj do if not disabled(s.obj[i]) and (s.obj[i].hp == nil or s.obj[i].hp > 0) then if (s.obj[i].x == tx and s.obj[i].y == ty) then pr('Это ' .. s.obj[i].disp); if s.obj[i].enemy then pr(', ваш противник. '); elseif s.obj[i].ally then pr(', ваш союзник. '); else pr('. '); end; pr(EngineUtils.getValue(s.obj[i].state, s.obj[i])); if s.obj[i].enemy and EngineUtils.getValue(s.obj[i].canshoot, s.obj[i]) then if s.obj[i]:canhit() or (s.obj[i]:canthrust() and have(item_harpoon)) then s.ui_state = {tx, ty, 'hit', s.obj[i]}; if have(item_harpoon) then pr('Нажмите еще раз, чтобы ударить гарпуном. '); else pr('Нажмите еще раз, чтобы ударить. '); end; elseif s:canShoot(s.plX, s.plY, tx, ty) and (have(item_colt) and not s.underwater) or (have(item_harpoon)) then if have(item_colt) and not s.underwater and item_colt.bullets == 0 then pr [[Ваш револьвер не заряжен. ]]; elseif have(item_harpoon) and item_harpoon.charge == 0 and s.underwater or not have(item_colt) then pr [[Ваше ружье не заряжено. ]]; else s.ui_state = {tx, ty, 'shoot', s.obj[i]}; pr('Нажмите еще раз, чтобы выстрелить. '); end; end; end; return; end; end; end; if math.abs(s.plX - tx) + math.abs(s.plY - ty) < 2 then if not s:isPassable(tx, ty) then return end; s.ui_state = {tx, ty, 'walk'}; pr('Нажмите еще раз, чтобы идти. '); end; elseif s.ui_state == 1 then -- tile selected. Click same tile to confirm action, or elsewhere to cancel s.ui_state = nil; if s.plX == tx and s.plY == ty then p("Вы ждете. "); here():make_turn(); else pr [[Действие отменено. ]]; end; else if tx == s.ui_state[1] and ty == s.ui_state[2] then if s.ui_state[3] == 'shoot' then if have(item_colt) and not s.underwater and item_colt.bullets > 0 then pr(item_colt:use(s.ui_state[4])); elseif have(item_harpoon) and item_harpoon.charge > 0 then pr(item_harpoon:use(s.ui_state[4])); else pr [[Если вы видите это сообщение -- это баг. ]]; end; elseif s.ui_state[3] == 'hit' then if have(item_harpoon) then pr(item_harpoon:use(s.ui_state[4])); else pr(s.ui_state[4]:act()); end; elseif s.ui_state[3] == 'walk' then here().plX = s.ui_state[1]; here().plY = s.ui_state[2]; here():make_turn(); end; else pr [[Действие отменено. ]]; end; s.ui_state = nil; end; end; tab.isPassable = function(s, x, y) if (x < 1 or x > 7 or y < 1 or y > 7 or tab.map[y][x] > 0) then return false; end; for i = 1, #s.obj do local obst = EngineUtils.getValue(s.obj[i].obst, s.obj[i]); if (obst and obst > 0) then if (s.obj[i].x == x and s.obj[i].y == y) then return false; end; end; end; return true; end; tab.isShootable = function(s, x, y) if (x < 1 or x > 7 or y < 1 or y > 7 or tab.map[y][x] > 1) then return false; end; for i = 1, #s.obj do local obst = EngineUtils.getValue(s.obj[i].obst, s.obj[i]); if (obst and obst > 1) then if (s.obj[i].x == x and s.obj[i].y == y) then return false; end; end; end; return true; end; tab.canShoot = function(s, x1, y1, x2, y2) local dx = math.abs(x1 - x2); local dy = math.abs(y1 - y2); local invx = x1 > x2; local invy = y1 > y2; local xc = 0; local yc = 0; local error = 0; if (dx > dy) then --horizontal line local y = 0; for x = 0, dx do if invx then xc = x1 - x; else xc = x1 + x; end; if invy then yc = y1 - y; else yc = y1 + y; end; if ((xc ~= x1 or yc ~= y1) and not s:isShootable(xc, yc) and (xc ~= x2 or yc ~= y2)) then return false; end; error = error + dy; if (2 * error > dx) then y = y + 1 error = error - dx; end; end; else --vertical line local x = 0; for y = 0, dy do if invx then xc = x1 - x; else xc = x1 + x; end; if invy then yc = y1 - y; else yc = y1 + y; end; if ((xc ~= x1 or yc ~= y1) and not s:isShootable(xc, yc) and (xc ~= x2 or yc ~= y2)) then return false; end; error = error + dx; if (2 * error > dy) then x = x + 1; error = error - dy; end; end; end; return true; end; tab.check_walk = function(s) bf_north:enable(); bf_east:enable(); bf_south:enable(); bf_west:enable(); if (not s:isPassable(s.plX, s.plY - 1)) then bf_north:disable(); end; if (not s:isPassable(s.plX, s.plY + 1)) then bf_south:disable(); end; if (not s:isPassable(s.plX - 1, s.plY)) then bf_west:disable(); end; if (not s:isPassable(s.plX + 1, s.plY)) then bf_east:disable(); end; end; local entered = tab.entered; tab.entered = function(s) s:check_walk(); if (type(entered) == 'function') then return entered(s); else return entered; end; end; tab.onreload = function(s) here():make_turn(); end; if not tab.check_win then tab.check_win = function(s) for i = 1, #s.obj do if (s.obj[i].enemy and s.obj[i].hp > 0) then return; end; end; walk(s.win); end; end; tab.make_turn = function(s) -- A bit peggish but works pl.x = s.plX; pl.y = s.plY; for i = 1, #s.obj do if (type(s.obj[i].make_turn) == 'function' and s.obj[i].hp > 0) then s.obj[i]:make_turn(); end; end; if (make_turn) then make_turn(s); end; if (pl.hp <= 0) then walk(s.lose); end; s:check_walk(); s:check_win(); end; tab.getWalkDistance = _kh.bf_calcWalkDistance; tab.getShootDistance = _kh.bf_calcShootDistance; tab.getDirection = _kh.bf_calcDirection; if not tab.wall then tab.wall = "images/battle_wall.png"; end; if not tab.obstacle then tab.obstacle = "images/battle_obstacle.png"; end; if (not tab.obj) then tab.obj = {}; end; if not tab.bcg_pic then tab.bcg_pic = 'images/battle_bg.png'; end; if not tab.player_pic then tab.player_pic = 'images/player.png'; end; if (not tab.pic) then tab.pic = function(s) local v = s.bcg_pic .. ";" .. s.player_pic .. "@" .. tostring(s.plX * 32 - 32) .. "," .. tostring(s.plY * 32 - 32); for i = 1, #s.obj do if ((s.obj[i].hp) and (s.obj[i].hp > 0) and not(s.obj[i]:disabled()) and s.obj[i].x and s.obj[i].y) then if s.obj[i].pic then local pic = EngineUtils.getValue(s.obj[i].pic, s.obj[i]); if type(pic) ~= 'table' then pic = {pic}; end; for x = 1, #pic do v = v .. ";" .. pic[x] .. "@" .. tostring(s.obj[i].x * 32 - 32) .. "," .. tostring(s.obj[i].y * 32 - 32); end; end; end; end; for i = 1, 7 do for j = 1, 7 do if (tab.map[i][j] == 2) then v = v.. ";" .. tab.wall .. "@" .. tostring(j * 32 - 32) .. "," .. tostring(i * 32 - 32); elseif (tab.map[i][j] == 1) then v = v.. ";" .. tab.obstacle .. "@" .. tostring(j * 32 - 32) .. "," .. tostring(i * 32 - 32); end; end; end; if s.ui_state == nil then elseif s.ui_state == 1 then v = v .. ';images/' .. 'wait_cursor.png' .. '@' .. tostring(s.plX * 32 - 32) .. "," .. tostring(s.plY * 32 - 32); else v = v .. ';images/' .. s.ui_state[3] .. '_cursor.png' .. '@' .. tostring(s.ui_state[1] * 32 - 32) .. "," .. tostring(s.ui_state[2] * 32 - 32); end; return v; end; end; table.insert(tab.obj, 'bf_north'); --table.insert(tab.obj, 'bf_northeast'); table.insert(tab.obj, 'bf_east'); --table.insert(tab.obj, 'bf_southeast'); table.insert(tab.obj, 'bf_south'); --table.insert(tab.obj, 'bf_southwest'); table.insert(tab.obj, 'bf_west'); --table.insert(tab.obj, 'bf_northwest'); table.insert(tab.obj, 'bf_wait'); return room(tab); end; bf_north = obj { nam = "north", dsc = "{На север}", act = function(s) here().plY = here().plY - 1; p("Вы идете на север"); here():make_turn(); end; }; bf_east = obj { nam = "east", dsc = "{На восток}", act = function(s) here().plX = here().plX + 1; p("Вы идете на восток."); here():make_turn(); end; }; bf_south = obj { nam = "south", dsc = "{На юг}", act = function(s) here().plY = here().plY + 1; p("Вы идете на юг."); here():make_turn(); end; }; bf_west = obj { nam = "west", dsc = "{На запад}", act = function(s) here().plX = here().plX - 1; p("Вы идете на запад."); here():make_turn(); end; }; bf_wait = obj { nam = "wait", dsc = "{Ждать}", act = function(s) p("Вы ждете. "); here():make_turn(); end; }; combatant = function(tab) if (tab.canshoot == nil) then tab.canshoot = function(s) if not here():canShoot(here().plX, here().plY, s.x, s.y) then return false, 'Вы не попадете в цель отсюда!'; end; local dist = math.sqrt((s.x - here().plX) * (s.x - here().plX) + (s.y - here().plY) * (s.y - here().plY)); if tab.ally then return false, nil; elseif dist >= 6 then return false, 'Цель слишком далеко!'; end; return true; end; end; if (tab.onthrust == nil) then tab.onthrust = function(s) local dist = math.sqrt((s.x - here().plX) * (s.x - here().plX) + (s.y - here().plY) * (s.y - here().plY)); if (rnd(4) > dist - 2) then tab.hp = tab.hp - 2; if (tab.hp > 0) then p(tab.thrustHit); else if (tab.onkill) then tab:onkill(); end; p(tab.thrustKill); end; else p(tab.thrustMiss); end; here():make_turn(); end; end; if (tab.onshoot == nil) then tab.onshoot = function(s) local dist = math.sqrt((s.x - here().plX) * (s.x - here().plX) + (s.y - here().plY) * (s.y - here().plY)); if (rnd(4) > dist - 2) then if (dist == 0) then tab.hp = tab.hp - 2; end; tab.hp = tab.hp - 1; if (tab.hp > 0) then p(tab.shootHit); else if (tab.onkill) then tab:onkill(); end; p(tab.shootKill); end; else p(tab.shootMiss); end; here():make_turn(); end; end; if (tab.canthrust == nil) then tab.canthrust = function(s) return math.abs(s.x - here().plX) + math.abs(s.y - here().plY) <= 2 and not s.ally; end; end; if (tab.canhit == nil) then tab.canhit = function(s) return (math.abs(s.x - here().plX) == 1 and s.y == here().plY or s.x == here().plX and math.abs(s.y - here().plY) == 1) and not tab.ally; end; end; if (tab.onhit == nil) then tab.onhit = function(s) if (math.abs(s.x - here().plX) == 1 and s.y == here().plY or s.x == here().plX and math.abs(s.y - here().plY) == 1) then tab.hp = tab.hp - 2; if (tab.hp > 0) then p(tab.wpnHit); else if (tab.onkill) then tab:onkill(); end; p(tab.wpnKill); end; here():make_turn(); else p(tab.wpnFar); end; end; end; if (not tab.act) then tab.act = function(s) if tab.ally then return tab.nohit; end; if (math.abs(s.x - here().plX) == 1 and s.y == here().plY or s.x == here().plX and math.abs(s.y - here().plY) == 1) then tab.hp = tab.hp - 1; if (tab.hp > 0) then p(tab.handHit); else if (tab.onkill) then tab:onkill(); end; p(tab.handKill); end; here():make_turn(); else p(tab.handFar); end; end; end; if (not tab.make_turn) then tab.make_turn = function(s) end; end; if (not tab.obst) then tab.obst = function(s) if (s.hp > 0) then return 2; else return 0; end; end; end; return obj(tab); end;