You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

513 lines
14 KiB

-- LUALOCALS < ---------------------------------------------------------
local include, nodecore, pairs, string, table
= include, nodecore, pairs, string, table
local string_lower
= string.lower
-- LUALOCALS > ---------------------------------------------------------
local modname = minetest.get_current_modname()
--nodecore.gametime
local cache = {}
local global_search_maximum = 361
local directions = { [0] =
vector.new(0, 0, -1),
vector.new(-1, 0, 0),
vector.new(0, 0, 1),
vector.new(1, 0, 0),
}
local down = vector.new(0, -1, 0)
local solid_drawtypes = {
normal = true,
glasslike_framed = true,
}
--[[
E: air
E_: other empty
Etype: territory marker of some type
Stype: a go stone of some type
W: full block wall
WE0-WE3: edge concrete
WC0-WC3: corner concrete
WB: other goban concrete
--]]
local function _check_position_uncached(pos)
local node = minetest.get_node(pos)
if node.name == "air" then
return "E"
end
local reg_item = minetest.registered_items[node.name]
if not reg_item then
return "W"
end
if reg_item.groups and reg_item.groups.go_stone then
return "S" .. reg_item.go_team
end
if reg_item.groups and reg_item.groups.go_territory_marker then
return "E" .. reg_item.go_team
end
if reg_item.pattern_def then
if reg_item.pattern_def.name == "edgy" then
return "WE" .. ((node.param2+2)%4)
elseif reg_item.pattern_def.name == "corny" then
return "WC" .. ((node.param2+2)%4)
elseif reg_item.pattern_def.name == "crossy" then
return "WB"
elseif reg_item.pattern_def.name == "starcrossy" then
return "WB"
end
end
if solid_drawtypes[reg_item.drawtype] and not reg_item.groups.falling_node then
return "W"
else
return "E_"
end
end
local function check_position(pos)
local hash = minetest.hash_node_position(pos)
if cache[hash] then
return cache[hash]
end
local ret = _check_position_uncached(pos)
cache[hash] = ret
return ret
end
local function set_and_cache(pos, value)
nodecore.set_loud(pos, value)
cache[minetest.hash_node_position(pos)] = _check_position_uncached(pos)
end
local smokecontrol
if nodecore.smokeclear then
smokecontrol = function(pos, smoke)
local existing = check_position(pos)
if existing:sub(1, 1) == "S" then
local node = minetest.get_node(pos)
node.param2 = smoke
minetest.swap_node(pos, node)
if smoke then
nodecore.smokefx(pos, 60, smoke)
else
nodecore.smokeclear(pos)
end
else
if smoke then
nodecore.smokefx(pos, smoke, 1)
else
nodecore.smokeclear(pos)
end
end
end
else
smokecontrol = function(pos, smoke) end
end
-- check for walls, physical or implied
local function edge_check(pos, dir, terminate)
local under = check_position(pos + down)
if under:sub(1, 1) == "W" and under:len() == 3 then
local corner = (under:sub(2, 2) == "C")
local edge_dir = tonumber(under:sub(3, 3))
if (dir == edge_dir) or (corner and (dir == (edge_dir+1)%4)) then
return true
end
end
if not terminate then
if edge_check(pos+directions[dir], (dir+2)%4, true) then
return true
end
end
return false
end
--[[
give valid neighbor directions from a position, excluding solid blocks
and also respecting borders indicated by goban concrete
--]]
local function neighbor_dirs(pos)
local neighbors = {}
for i,v in pairs(directions) do
if not edge_check(pos, i) then
if check_position(pos + directions[i]):sub(1, 1) ~= "W" then
neighbors[#neighbors+1] = i
end
end
end
return neighbors
end
local function connected_search(pos, final_result, early_termination_filter)
local group = {pos}
local stones = {[minetest.hash_node_position(pos)] = pos}
local checked = {}
local piece = check_position(pos)
local probe = 0
while probe < #group do
probe = probe + 1
pos = group[probe]
for i,v in pairs(neighbor_dirs(pos)) do
local newpos = pos + directions[v]
local newhash = minetest.hash_node_position(newpos)
if (not stones[newhash]) and (not checked[newhash]) then
local newnode = check_position(newpos)
if newnode == piece then
stones[newhash] = newpos
group[#group+1] = newpos
else
checked[newhash] = true
end
local filter_result
if early_termination_filter then
filter_result = early_termination_filter(newnode, #group)
end
if filter_result then
filter_result.stones = stones
return filter_result
end
end
end
end
final_result = final_result(#group)
final_result.stones = stones
return final_result
end
local function board_clear_fx(basepos)
local group = {basepos}
local checked = {[minetest.hash_node_position(basepos)] = basepos}
local probe = 0
while probe < #group do
probe = probe + 1
local pos = group[probe]
for i,v in pairs(neighbor_dirs(pos)) do
local newpos = pos + directions[v]
if (math.abs(newpos.x - basepos.x) <= 18) and (math.abs(newpos.z - basepos.z) <= 18) then
local newhash = minetest.hash_node_position(newpos)
local groups = minetest.registered_nodes[minetest.get_node(newpos).name].groups
if groups then
if groups.ko_stone then
set_and_cache(newpos, {name = "air"})
end
smokecontrol(newpos)
end
if (not checked[newhash]) then
group[#group+1] = newpos
checked[newhash] = true
end
end
end
end
end
local function _check_captures_filter(node, count)
if (count > global_search_maximum) or (node:sub(1, 1) == "E") then
return {capture = false}
end
end
local function _check_captures_final(count)
return {capture = count}
end
local function check_captures(pos)
return connected_search(pos, _check_captures_final, _check_captures_filter)
end
local function _connected_group_final()
return {complete = true}
end
local function _connected_group_filter(node, count)
if (count > global_search_maximum) then
return {}
end
end
local function connected_group(pos)
local search = connected_search(pos, _connected_group_final, _connected_group_filter)
if search.complete then
return search.stones
else
return {pos}
end
end
local function territory_search(pos, max)
if check_position(pos) ~= "E" then
return {}
end
local team
return connected_search(pos,
function()
return {team = team}
end,
function(node, count)
if (count > max) then
return {}
end
if node ~= "E_" and ((node:sub(1, 1) == "E") or (node:sub(1, 1) == "S")) and node:len() > 1 then
node = node:sub(2, -1)
if team then
if team ~= node then
return {}
end
else
team = node
end
end
end
)
end
local function multi_eject(proximal, stone, speed, count, inv)
local stack_max = minetest.registered_items[stone].stack_max
while count > 0 do
local items = stone .. " " .. math.min(count, stack_max)
if inv then items = inv:add_item("main", items) end
nodecore.item_eject(proximal, items, speed)
count = count - stack_max
end
end
local firenode = "nc_fire:fire_burst"
if not minetest.registered_nodes["nc_fire:fire_burst"] then
firenode = "nc_fire:fire"
end
function lc_liberties.handle_placement(pos)
cache = {}
--minetest.chat_send_all(tostring(pos))
local our_stone = check_position(pos)
if our_stone:sub(1, 1) ~= "S" then
return
end
our_stone = our_stone:sub(2, -1)
local captureses = {}
local captured = false
for i,v in pairs(neighbor_dirs(pos)) do
local new_pos = pos + directions[v]
local hash = minetest.hash_node_position(new_pos)
local checked = false
for i2,v2 in pairs(captureses) do
if v2.stones[hash] then
checked = true
end
end
if not checked then
local node = check_position(new_pos)
if (node:sub(1, 1) == "S") and (node:sub(2, -1) ~= our_stone) then
local captures = check_captures(new_pos)
if captures.capture then
captured = (captured or 0) + captures.capture
end
captureses[#captureses+1] = captures
end
end
end
local allcaptured = {}
local ko
if captured then
if captured == 1 then
for i,v in pairs(neighbor_dirs(pos)) do
local new_stone = check_position(pos + directions[v])
if (new_stone:sub(2, -1) == our_stone) or (new_stone:sub(1, 1) == "E") then
goto noko
end
end
ko = true
::noko::
end
for i,v in pairs(captureses) do
if v.capture then
local stone = check_position(v.stones[next(v.stones)]):sub(2, -1) -- minetest.get_node().name)
local count = 0
local proximal
local proximal_c = 0
for i2, v2 in pairs(v.stones) do
allcaptured[#allcaptured + 1] = v2
if ko then
-- save ko capture position, for adding ko marker in fx phase
ko = {stone = stone, pos = v2}
end
local under = check_position(v2 + down)
if (under:sub(1, 1) == "W") and (under:len() > 1) then
nodecore.sound_play("nc_fire_ignite", {gain = 1, pos = pos})
set_and_cache(v2, {name = firenode})
else
set_and_cache(v2, {name = "air"})
end
count = count + 1
if (v2:distance(pos) < 2.3) then
proximal_c = proximal_c + 1
if math.random(1, proximal_c) == 1 then
proximal = v2
end
end
end
multi_eject(proximal, modname .. ":stone_" .. stone, 3, count)
end
end
else
-- if no capture was made, check for a self-capture and reject the move
local captures = check_captures(pos)
if captures.capture then
local stone = minetest.get_node(pos).name
set_and_cache(pos, {name = "air"})
nodecore.item_eject(pos, stone, 5)
return
end
end
board_clear_fx(pos)
smokecontrol(pos, 2+#allcaptured)
for i = #allcaptured, 1, -1 do
local chosen = math.random(1, i)
smokecontrol(allcaptured[chosen], 90)
allcaptured[chosen] = allcaptured[i]
end
if ko then
set_and_cache(ko.pos, {name = modname .. ":ko_" .. ko.stone})
end
end
function lc_liberties.handle_territory_fill(itemstack, placer, pointed_thing)
local control = placer.get_player_control and placer:get_player_control()
if (control and (control.aux1 or control.sneak)) then
local ret = minetest.item_place(itemstack, placer, pointed_thing)
if (pointed_thing and pointed_thing.under) then
nodecore.node_sound(pointed_thing.under, "place")
end
return ret
end
local under
if (pointed_thing and pointed_thing.under) then
under = minetest.get_node(pointed_thing.under)
local override_rightclick = minetest.registered_nodes[under.name].on_rightclick
if override_rightclick then
return override_rightclick(pointed_thing.under, under, placer, itemstack, pointed_thing)
end
end
if pointed_thing and pointed_thing.above then
if under then
local reg_item = minetest.registered_items[under.name]
if reg_item and reg_item.groups and reg_item.groups.go_stone then
set_and_cache(pointed_thing.above, {name = modname .. ":territory_" .. reg_item.go_team})
itemstack:set_count(itemstack:get_count() - 1)
return itemstack
end
end
cache = {}
local territories = territory_search(pointed_thing.above, itemstack:get_count())
if territories.team then
local piece = modname .. ":territory_" .. territories.team
local count = 0
for i, v in pairs(territories.stones) do
set_and_cache(v, {name = piece})
count = count + 1
end
itemstack:set_count(itemstack:get_count() - count)
end
end
return itemstack
end
function lc_liberties.handle_dig(pos, node, digger)
local control = digger.get_player_control and digger:get_player_control()
if (control and (control.aux1 or control.sneak))
~=
(minetest.registered_items[node.name].groups.go_territory_marker ~= nil)
then
cache = {}
local stone = minetest.get_node(pos).name
local count = 0
for i, v in pairs(connected_group(pos)) do
count = count + 1
set_and_cache(v, {name = "air"})
end
multi_eject(pos, stone, 1, count, digger and digger:get_inventory())
return true
else
return minetest.node_dig(pos, node, digger)
end
end
function lc_liberties.handle_click_ko(pos, node, clicker, itemstack, pointed_thing)
local handnode = minetest.registered_nodes[itemstack:get_name()]
if handnode and handnode.groups.go_stone and
handnode.go_team ~= minetest.registered_nodes[node.name].go_team then
set_and_cache(pos, {name = handnode.name})
itemstack:set_count(itemstack:get_count() - 1)
end
return itemstack
end