advanced_npc/npc.lua

463 lines
14 KiB
Lua
Raw Normal View History

-- Advanced NPC by Zorman2000
-- Based on original NPC by Tenplus1
local S = mobs.intllib
npc = {}
-- Constants
npc.FEMALE = "female"
npc.MALE = "male"
npc.INVENTORY_ITEM_MAX_STACK = 99
---------------------------------------------------------------------------------------
-- General functions
---------------------------------------------------------------------------------------
-- Gets name of player or NPC
function npc.get_entity_name(entity)
if entity:is_player() then
return entity:get_player_name()
else
return entity:get_luaentity().nametag
end
end
-- Returns the item "wielded" by player or NPC
-- TODO: Implement NPC
function npc.get_entity_wielded_item(entity)
if entity:is_player() then
return entity:get_wielded_item()
end
end
---------------------------------------------------------------------------------------
-- Inventory functions
---------------------------------------------------------------------------------------
-- NPCs inventories are restrained to 16 slots.
-- Each slot can hold one item up to 99 count.
-- Utility function to get item name from a string
local function get_item_name(item_string)
local i,j = string.find(item_string, " ")
return item_string.sub(item_string, 1, i-1)
end
-- Utility function to get item count from a string
local function get_item_count(item_string)
local i,j = string.find(item_string, " ")
return tonumber(item_string.sub(item_string, i+1))
end
local function initialize_inventory()
return {
[1] = "", [2] = "", [3] = "", [4] = "",
[5] = "", [6] = "", [7] = "", [8] = "",
[9] = "", [10] = "", [11] = "", [12] = "",
[13] = "", [14] = "", [15] = "", [16] = "",
}
end
-- Add an item to inventory. Returns true if add successful
-- These function can be used to give items to other NPCs
-- given that the "self" variable can be any NPC
function npc.add_item_to_inventory(self, item_name, count)
-- Check if NPC already has item
local existing_item = npc.inventory_contains(self, item_name)
if existing_item ~= nil and existing_item.item_string ~= nil then
-- NPC already has item. Get count and see
minetest.log("What is this? "..dump(existing_item))
local existing_count = get_item_count(existing_item.item_string)
if (existing_count + count) < npc.INVENTORY_ITEM_MAX_STACK then
-- Set item here
self.inventory[existing_item.slot] =
get_item_name(existing_item.item_string).." "..tostring(existing_count + count)
return true
else
--Find next free slot
for i = 1, #self.inventory do
if self.inventory[i] == "" then
-- Found slot, set item
self.inventory[i] =
item_name.." "..tostring((existing_count + count) - npc.INVENTORY_ITEM_MAX_STACK)
return true
end
end
-- No free slot found
return false
end
else
-- Find a free slot
for i = 1, #self.inventory do
if self.inventory[i] == "" then
-- Found slot, set item
self.inventory[i] = item_name.." "..tostring(count)
return true
end
end
-- No empty slot found
return false
end
end
-- Same add method but with itemstring for convenience
function npc.add_item_to_inventory_itemstring(self, item_string)
local item_name = get_item_name(item_string)
local item_count = get_item_count(item_string)
npc.add_item_to_inventory(self, item_name, item_count)
end
-- Checks if an item is contained in the inventory. Returns
-- the item string or nil if not found
function npc.inventory_contains(self, item_name)
for key,value in pairs(self.inventory) do
if value ~= "" and string.find(value, item_name) then
return {slot=key, item_string=value}
end
end
-- Item not found
return nil
end
-- Removes the item from an NPC inventory and returns the item
-- with its count (as a string, e.g. "default:apple 2"). Returns
-- nil if unable to get the item.
function npc.take_item_from_inventory(self, item_name, count)
local existing_item = npc.inventory_contains(self, item_name)
if existing_item ~= nil then
-- Found item
local existing_count = get_item_count(existing_item.item_string)
local new_count = existing_count
if existing_count - count < 0 then
-- Remove item first
self.inventory[existing_item.slot] = ""
-- TODO: Support for retrieving from next stack. Too complicated
-- and honestly might be unecessary.
return item_name.." "..tostring(new_count)
else
new_count = existing_count - count
self.inventory[existing_item.slot] = item_name.." "..new_count
return item_name.." "..tostring(count)
end
else
-- Not able to take item because not found
return nil
end
end
-- Inventory functions for players
function npc.give_item_to_player(player, item_name, count)
local player_name = npc.get_entity_name(player)
local player_inv = minetest.get_inventory({type="player", name=player_name})
local item = ItemStack(item_name.." "..count)
player_inv:add_item("main", item)
end
function npc.check_item_to_player(player, item_name)
local player_name = npc.get_entity_name(player)
local player_inv = minetest.get_inventory({type="player", name=player_name})
local main_list = player_inv:get_list("main")
for i = 1, #main_list do
if main_list[i]:get_name() == item_name then
return main_list[i]
end
end
-- Not found
return nil
end
function npc.take_item_from_player(player, item_name, count)
local player_name = npc.get_entity_name(player)
local player_inv = minetest.get_inventory({type="player", name=player_name})
local main_list = player_inv:get_list("main")
for i = 1, #main_list do
if main_list[i]:get_name() == item_name then
main_list[i].take_item(count)
end
end
-- Not found
return nil
end
-- Chat functions
function npc.start_dialogue(self, clicker, show_married_dialogue)
-- Call chat function as normal
npc.dialogue.start_dialogue(self, clicker, show_married_dialogue)
-- Check and update relationship if needed
npc.relationships.dialogue_relationship_update(self, clicker)
end
---------------------------------------------------------------------------------------
-- Definitions
---------------------------------------------------------------------------------------
mobs:register_mob("advanced_npc:npc", {
type = "npc",
passive = false,
damage = 3,
attack_type = "dogfight",
attacks_monsters = true,
-- Added group attack
group_attack = true,
--pathfinding = true,
pathfinding = 1,
hp_min = 10,
hp_max = 20,
armor = 100,
collisionbox = {-0.35,-1.0,-0.35, 0.35,0.8,0.35},
visual = "mesh",
mesh = "character.b3d",
drawtype = "front",
textures = {
{"mobs_npc_male1.png"},
{"mobs_npc_female1.png"}, -- female by nuttmeg20
},
child_texture = {
{"mobs_npc_baby_male1.png"}, -- derpy baby by AmirDerAssassine
},
makes_footstep_sound = true,
sounds = {},
-- Added walk chance
walk_chance = 30,
-- Added stepheight
stepheight = 1,
walk_velocity = 2,
run_velocity = 3,
jump = true,
drops = {
{name = "default:wood", chance = 1, min = 1, max = 3},
{name = "default:apple", chance = 2, min = 1, max = 2},
{name = "default:axe_stone", chance = 5, min = 1, max = 1},
},
water_damage = 0,
lava_damage = 2,
light_damage = 0,
--follow = {"farming:bread", "mobs:meat", "default:diamond"},
view_range = 15,
owner = "",
order = "follow",
--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,
},
on_rightclick = function(self, clicker)
local item = clicker:get_wielded_item()
local name = clicker:get_player_name()
minetest.log(dump(self))
-- Receive gift or start chat. If player has no item in hand
-- then it is going to start chat directly
if self.can_have_relationship and item:to_table() ~= nil then
-- Get item name
local item = minetest.registered_items[item:get_name()]
local item_name = item.description
-- Show dialogue to confirm that player is giving item as gift
npc.dialogue.show_yes_no_dialogue(
"Do you want to give "..item_name.." to "..self.nametag.."?",
npc.dialogue.POSITIVE_GIFT_ANSWER_PREFIX..item_name,
function()
npc.relationships.receive_gift(self, clicker)
end,
npc.dialogue.NEGATIVE_ANSWER_LABEL,
function()
npc.start_dialogue(self, clicker, true)
end,
name
)
else
npc.start_dialogue(self, clicker, true)
end
end,
do_custom = function(self, dtime)
-- Timer function for gifts
for i = 1, #self.relationships do
local relationship = self.relationships[i]
-- Gift timer check
if relationship.gift_timer_value < relationship.gift_interval then
relationship.gift_timer_value = relationship.gift_timer_value + dtime
elseif relationship.talk_timer_value < relationship.gift_interval then
-- Relationship talk timer - only allows players to increase relationship
-- by talking on the same intervals as gifts
relationship.talk_timer_value = relationship.talk_timer_value + dtime
else
-- Relationship decrease timer
if relationship.relationship_decrease_timer_value
< relationship.relationship_decrease_interval then
relationship.relationship_decrease_timer_value =
relationship.relationship_decrease_timer_value + dtime
else
-- Check if married to decrease half
if relationship.phase == "phase6" then
-- Avoid going below the marriage phase limit
if (relationship.points - 0.5) >=
npc.relationships.RELATIONSHIP_PHASE["phase5"].limit then
relationship.points = relationship.points - 0.5
end
else
relationship.points = relationship.points - 1
end
relationship.relationship_decrease_timer_value = 0
minetest.log(dump(self))
end
end
end
end
})
---------------------------------------------------------------------------------------
-- Spawning functions
---------------------------------------------------------------------------------------
-- These functions are used at spawn time to determine several
-- random attributes for the NPC in case they are not already
-- defined. On a later phase, pre-defining many of the NPC values
-- will be allowed.
-- This function checks for "female" text on the texture name
local function is_female_texture(textures)
for i = 1, #textures do
if string.find(textures[i], "female") ~= nil then
return true
end
end
return false
end
-- Choose whether NPC can have relationships. Only 30% of NPCs cannot have relationships
local function can_have_relationships()
local chance = math.random(1,10)
return chance > 3
end
-- Choose a maximum of two items that the NPC will have at spawn time
-- These items are chosen from the favorite items list.
local function choose_spawn_items(self)
local number_of_items_to_add = math.random(1, 2)
local number_of_items = #npc.FAVORITE_ITEMS[self.sex].phase1
minetest.log("Number of items: "..dump(number_of_items))
for i = 1, number_of_items_to_add do
npc.add_item_to_inventory(
self,
npc.FAVORITE_ITEMS[self.sex].phase1[math.random(1, number_of_items)].item,
math.random(1,4)
)
end
end
local function npc_spawn(self, pos)
minetest.log("Spawning new NPC:")
local ent = self:get_luaentity()
ent.nametag = "Kio"
-- Determine sex based on textures
if (is_female_texture(ent.base_texture)) then
ent.sex = npc.FEMALE
else
ent.sex = npc.MALE
end
-- Initialize all gift data
ent.gift_data = {
-- Choose favorite items. Choose phase1 per default
favorite_items = npc.relationships.select_random_favorite_items(ent.sex, "phase1"),
-- Choose disliked items. Choose phase1 per default
disliked_items = npc.relationships.select_random_disliked_items(ent.sex),
}
-- Flag that determines if NPC can have a relationship
ent.can_have_relationship = can_have_relationships()
-- Initialize relationships object
ent.relationships = {}
-- Determines if NPC is married or not
ent.is_married_to = nil
-- Initialize dialogues
ent.dialogues = npc.dialogue.select_random_dialogues_for_npc(ent.sex,
"phase1",
ent.gift_data.favorite_items,
ent.gift_data.disliked_items)
-- Declare NPC inventory
ent.inventory = initialize_inventory()
-- Choose items to spawn with
choose_spawn_items(ent)
ent.trader_data = {
-- Type of trader
trader_status = npc.trade.get_random_trade_status(),
-- Current buy offers
buy_offers = {},
-- Current sell offers
sell_offers = {},
-- Items to buy change timer
change_offers_timer = 0,
-- Items to buy change timer interval
change_offers_timer_interval = 20
}
-- Initialize trading offers if NPC is casual trader
if ent.trader_data.trader_status == npc.trade.CASUAL then
ent.trader_data.buy_offers = {
[1] = npc.trade.get_casual_trade_offer(ent, npc.trade.OFFER_BUY)
}
ent.trader_data.sell_offers = {
[1] = npc.trade.get_casual_trade_offer(ent, npc.trade.OFFER_SELL)
}
end
minetest.log(dump(ent))
-- Refreshes entity
ent.object:set_properties(ent)
end
-- Spawn
mobs:spawn({
name = "advanced_npc:npc",
nodes = {"default:stone"},
min_light = 3,
active_object_count = 1,
interval = 5,
chance = 1,
--max_height = 0,
on_spawn = npc_spawn,
})
mobs:register_egg("advanced_npc:npc", S("Npc"), "default_brick.png", 1)
-- compatibility
mobs:alias_mob("mobs:npc", "advanced_npc:npc")
-- Marriage ring
minetest.register_craftitem("advanced_npc:marriage_ring", {
description = S("Marriage Ring"),
inventory_image = "diamond_ring.png",
})
-- Marriage ring craft recipe
minetest.register_craft({
output = "advanced_npc:marriage_ring",
recipe = { {"", "", ""},
{"", "default:diamond", ""},
{"", "default:gold_ingot", ""} },
})