very VIP version; already working to some degree

This commit is contained in:
Sokomine 2023-03-16 02:16:44 +01:00
parent 5425c0269b
commit 7f8e486d48
4 changed files with 417 additions and 0 deletions

88
formspec.lua Normal file
View File

@ -0,0 +1,88 @@
-- which NPC object did the player talk to?
guards.player_talked_to = {}
guards.on_receive_fields = function(player, formname, fields)
local name = player:get_player_name()
local self = guards.player_talked_to[name]
if(not(self)) then
return
end
if self.owner ~= name then
return
end
if fields.origin then
self.origin.pos = self.object:get_pos()
self.origin.yaw = self.object:get_yaw()
end
if fields.follow_owner and fields.follow_owner == "true" then
self.metadata.patrol = "false"
self.order = "follow"
self.following = player
minetest.chat_send_player(name,
"New Order: "..tostring(self.order)..
" State: "..tostring(self.state)..
" Owner: "..tostring(self.owner))
elseif fields.walk_speed and fields.walk_speed ~= "" and fields.walk_speed ~= tostring(self.walk_velocity) then
self.walk_velocity = math.max(1, tonumber(fields.walk_speed)) * 1.0
elseif fields.fear_height and fields.fear_height ~= "" and fields.fear_height ~= tostring(self.fear_height) then
self.fear_height = math.max(1, tonumber(fields.fear_height))
elseif fields.follow_owner and fields.follow_owner == "false" then
self.order = "stand"
self.following = nil
elseif fields.patrol then
if fields.patrol == "false" then
self.metadata.patrol_index = 0
end
self.order = "patrol"
self.following = nil
elseif fields.add_patrol then
local pos = self.object:get_pos()
if pos then
table.insert(self.metadata.patrol_points, pos)
end
elseif fields.clear_patrol then
self.metadata.patrol_points = {}
else
return
end
minetest.show_formspec(name, "guards:main", guards.get_formspec(self, player))
end
guards.get_formspec = function(self, clicker)
return "size[8,8.5]"
.."checkbox[0.5,4.5;follow_owner;Follow;"..tostring(self.order == "follow").."]"
.."checkbox[3.5,4.5;patrol;Patrol;"..self.metadata.patrol.."]"
.."field[6.0,2.5;2.0.0,0.5;walk_speed;Walking speed;"..tostring(self.walk_velocity).."]"
.."field[6.0,4.0;2.0.0,0.5;fear_height;Fear height;"..tostring(self.fear_height).."]"
.."field[6.0,5.5;2.0.0,0.5;rest_time;Rest (sec);"..self.metadata.patrol_rest.."]"
.."label[0.5,6.5;Patrol Points: "..#self.metadata.patrol_points.."]"
.."button[5.5,6.5;2.5,0.5;add_patrol;Add Point]"
.."button[3.5,6.5;2.0,0.5;clear_patrol;Clear]"
.."button[0.0,8.0;2.0,0.5;origin;Set Origin]"
.."button_exit[7.0,8.0;1.0,0.5;;Ok]"
end
guards.on_rightclick = function(self, clicker)
if(not(clicker)) then
return
end
local name = clicker:get_player_name()
if name and name == self.owner then
-- catch the guard with a lasso
if mobs:capture_mob(self, clicker, nil, 100, 100, true, nil) then
return
end
guards.player_talked_to[name] = self
minetest.show_formspec(name, "guards:main", guards.get_formspec(self, clicker))
end
end
minetest.register_on_player_receive_fields( function(player, formname, fields)
if(formname == "guards:main") then
guards.on_receive_fields(player, formname, fields)
return true
end
end)

87
init.lua Normal file
View File

@ -0,0 +1,87 @@
guards = {}
local abs = math.abs
dofile(minetest.get_modpath("guards").."/formspec.lua");
dofile(minetest.get_modpath("guards").."/patrol.lua");
guards.on_spawn = function(self)
-- npcf-guard-specific data:
self.metadata = {
attack_players = "false",
patrol = "false",
patrol_points = {},
patrol_index = 0,
patrol_rest = 2,
show_armor = "true",
}
self.var = {
rest_timer = 0,
}
self.origin = {
pos = self.object:get_pos(),
yaw = self.object:get_yaw(),
}
return true
end
guards.guard_data = {
-- type needs to be "npc" - else it doesn't know how to follow
type = "npc", -- "guard",
passive = true,
damage = 9,
attack_type = "dogfight",
attacks_monsters = true,
attack_npcs = false,
owner_loyal = false,
pathfinding = false,
hp_min = 60,
hp_max = 100,
armor = 0,
collisionbox = {-0.3, 0.0, -0.3, 0.3, 1.7, 0.3},
visual = "mesh",
visual_size = {x = 1, y = 1},
mesh = "skinsdb_3d_armor_character_5.b3d",
drawtype = "front",
textures = {{
"blank.png", -- cape?
-- "yl_speak_up_main_default.png", -- 64x64 skin
"2019_05_19_survivalist-dave-slim-13008091.png",
"3d_armor_trans.png", -- shield?!
"3d_armor_trans.png", -- item right hand
}},
makes_footstep_sound = true,
sounds = {},
walk_velocity = 2,
run_velocity = 3,
jump = false,
water_damage = 0,
lava_damage = 0,
light_damage = 0,
view_range = 4,
owner = "Guard Guild",
order = "stand",
fear_height = 3,
animation = {
speed_normal = 30,
speed_run = 30,
stand_start = 0,
stand_end = 79,
walk_start = 168,
walk_end = 187,
run_start = 168,
run_end = 187,
punch_start = 200,
punch_end = 219,
},
-- show the formspec
on_rightclick = guards.on_rightclick,
-- set up metadata
on_spawn = guards.on_spawn,
do_custom = guards.on_step,
}
mobs:register_mob("guards:guard", guards.guard_data)
mobs:register_egg("guards:guard", "Guard", "wool_grey.png", 1)

6
mod.conf Normal file
View File

@ -0,0 +1,6 @@
author = Sokomine
description = Guards that can patrol (based on npcf)
release = 20220226
title = Guards
name = guards
depends = mobs, mob_world_interaction

236
patrol.lua Normal file
View File

@ -0,0 +1,236 @@
local abs = math.abs
guards.get_walkable_pos = function(pos, dist)
local destpos
local rpos = vector.round(pos)
for y = rpos.y+dist-1, rpos.y-dist-1, -1 do
for x = rpos.x-dist, rpos.x+dist do
for z = rpos.z-dist, rpos.z+dist do
local p = {x=x, y=y, z=z}
local node = minetest.get_node(p)
local nodedef = minetest.registered_nodes[node.name]
if not (node.name == "air" or nodedef and (nodedef.walkable == false or nodedef.drawtype == "airlike")) then
p.y = p.y +1
local node = minetest.get_node(p)
local nodedef = minetest.registered_nodes[node.name]
if node.name == "air" or nodedef and (nodedef.walkable == false or nodedef.drawtype == "airlike") then
if destpos == nil or vector.distance(p, pos) < vector.distance(destpos, pos) then
destpos = p
end
end
end
end
end
end
return destpos
end
guards.get_path = function(self, pos)
-- minetest.chat_send_all("GUARDS get_path from "..
-- minetest.serialize(spos).." to "..
-- minetest.serialize(pos))
local spos = self.object:get_pos()
local startpos = vector.round(spos)
startpos.y = startpos.y - 1 -- NPC is to high
local refpos
if vector.distance(spos, pos) > 20 then
refpos = vector.add(spos, vector.multiply(vector.direction(spos, pos), 20))
else
refpos = pos
end
local destpos = guards.get_walkable_pos(refpos, 2)
if not destpos then
destpos = spos
end
startpos.y = startpos.y + 1
local max_drop = (self.fear_height or 6) - 1
local path = minetest.find_path(startpos, destpos, 30, 1, max_drop, "A*_noprefetch")
-- local path = mob_world_interaction.find_path(startpos, destpos, { collisionbox = {1,0,3,4,2}});
-- minetest.chat_send_player("singleplayer", "Path found: "..minetest.serialize(path).." from: "..minetest.pos_to_string(startpos).." to: "..minetest.pos_to_string(destpos))
if not path then
path = { destpos, pos }
--print("fallback path to "..minetest.pos_to_string(pos))
elseif path then
--print("calculated path to "..minetest.pos_to_string(destpos).."for destination"..minetest.pos_to_string(pos))
table.insert(path, pos)
end
--[[
-- from below the NPC to below the target point
destpos.y = destpos.y - 1
-- without objects
local ray = Raycast(startpos, destpos, false, true)
for pointed_thing in ray do
minetest.chat_send_player("singleplayer", "Ray: "..minetest.serialize(pointed_thing))
end
--]]
return path
end
-- the mob first tires to walk in a straight line;
-- if that fails (cliff, collusion, stuck) it will switch to a pahtfinder
guards.switch_to_pathfinding = function(self)
-- the old path no longer works; search a new one
self.follow_path = guards.get_path(self, self.follow_path[#self.follow_path])
self.path_stuck_timer = 0
-- at_cliff isn't updated that frequently; wait a bit
self.wait_a_bit = 2.0
end
-- taken from npcf
guards.get_face_direction = function(self, p1, p2)
if p1 and p2 and p1.x and p2.x and p1.z and p2.z then
local px = p1.x - p2.x
local pz = p2.z - p1.z
return math.atan2(px, pz)
end
end
guards.on_step = function(self, dtime, moveresult)
if(self.order ~= "patrol") then
return true
end
self.ptimer = (self.ptimer or 0) + dtime
-- if self.ptimer < 0.2 then
-- return true
-- end
local pos = self.object:get_pos()
if(not(self.path_stuck_timer)) then
self.path_stuck_timer = 0
end
-- the NPC has a path which it shall follow
if(self.follow_path and #self.follow_path > 0) then
local p_next = self.follow_path[1]
local dist = abs(pos.x - p_next.x) + abs(pos.z - p_next.z) + abs(pos.y - p_next.y)
local last_pos = self.path_lastpos
if(dist < 0.8) then
-- minetest.set_node({x=p_next.x, y=p_next.y-1, z=p_next.z}, {name="wool:orange"})
table.remove(self.follow_path , 1)
-- not stuck - temporary target reached
self.path_stuck_timer = 0
self.wait_a_bit = 0
elseif(self.wait_a_bit and self.wait_a_bit > 0) then
self.wait_a_bit = self.wait_a_bit - dtime
elseif(self.at_cliff) then
minetest.chat_send_player("singleplayer", "There's a cliff! Searching new path.")
guards.switch_to_pathfinding(self)
elseif(last_pos and (abs(last_pos.x - pos.x) + abs(last_pos.z - pos.z) + abs(last_pos.y - pos.y )< 0.3)) then
self.path_stuck_timer = self.path_stuck_timer + dtime
if self.path_stuck_timer > 5.0 then
local coll_node = 0
local coll_obj = 0
if(moveresult and moveresult.collisions and #moveresult.collisions > 1) then
for i, v in ipairs(moveresult.collisions) do
if(v.type and v.type == "object") then
coll_obj = coll_obj + 1
elseif(v.type and v.type == "node") then
coll_node = coll_node + 1
end
end
end
minetest.chat_send_player("singleplayer", "I'm stuck! Searching new path."..
" Objects: "..tostring(coll_obj).." Nodes: "..tostring(coll_node))
-- if(coll_node > 1) then
guards.switch_to_pathfinding(self)
-- else
---- minetest.chat_send_player("singleplayer", "I'm stuck! There's something in the way.")
-- self.wait_a_bit = 5.0
-- end
end
else
-- not stuck - just walking to temporary target
self.path_stuck_timer = 0
end
-- store last postion to detect if we get stuck
self.path_lastpos = {x = pos.x, y = pos.y, z = pos.z}
-- either an error occoured..
if(not(self.follow_path)
-- ..or we reached the end of the path
or #self.follow_path < 1) then
if(self.follow_path and #self.follow_path < 1) then
self.state = "position_reached"
elseif(self.at_cliff) then
self.state = "failed_goto_because_cliff"
elseif(self.path_stuck_timer > 0) then
self.state = "failed_goto_because_stuck"
end
-- minetest.chat_send_player("singleplayer","Guard: "..tostring(self.state))
self.path_stuck_timer = 0
self.follow_path = nil
self:set_velocity(0)
self:set_animation("stand")
-- normal walking along the path
-- TODO: do this only when starting to walk?
else
-- go to the next position that belongs to the path
p_next = self.follow_path[1]
local yaw = guards.get_face_direction(self, pos, p_next)
self.object:set_yaw(yaw)
if(self.wait_a_bit and self.wait_a_bit > 0) then
self:set_velocity(0)
self:set_animation("stand")
else
self.wait_a_bit = nil
self:set_velocity(self.walk_velocity)
self:set_animation("walk")
self.state = "follow_path"
end
end
elseif(self.order == "patrol") then
self.var.rest_timer = self.var.rest_timer + self.ptimer
if(self.state == "position_reached") then
local index = self.metadata.patrol_index
local patrol_pos = self.metadata.patrol_points[index]
self.object:set_pos(patrol_pos)
self.path_stuck_timer = 0
self.state = "patrol_waiting"
self:set_velocity(0)
self:set_animation("stand")
elseif self.var.rest_timer > self.metadata.patrol_rest then
local index = self.metadata.patrol_index + 1
if index > #self.metadata.patrol_points then
index = 1
end
local patrol_pos = self.metadata.patrol_points[index]
if patrol_pos then
self.metadata.patrol_index = index
self.follow_path = {patrol_pos}
self.state = "goto_position"
self.var.rest_timer = 0
--[[
local distance = vector.distance(pos, patrol_pos)
minetest.chat_send_player("singleplayer","Guard patrol: "..minetest.pos_to_string(patrol_pos).." d: "..tostring(distance))
if distance > 1.0 then
-- start by trying to walk directly
self.follow_path = {patrol_pos}
else
self.object:set_pos(patrol_pos)
self.metadata.patrol_index = index
self.var.rest_timer = 0
self:set_velocity(0)
self:set_animation("stand")
end
--]]
end
end
end
self.ptimer = 0
-- return false
return true
end