123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356 |
- local modpath = minetest.get_modpath("markov_macaws")
- local settings = Settings(modpath .. "/markov_macaws.conf")
- local MarkovGraph = dofile(modpath .. "/markov_chains.lua")
- local parrot_brain = dofile(modpath .. "/parrot_brain.lua")
- local function parse_num(str)
- return 0 + str
- end
- local HEAR_DISTANCE = parse_num(settings:get("hear_distance"))
- local MAX_KNOWN_WORDS = parse_num(settings:get("max_known_words"))
- --make a list of edible food
- local foods = {}
- do
- local food = settings:get("foods")
- for name in string.gmatch(food, "[^,%s]+")
- do
- foods[name] = true
- end
- end
- local parrots = {}
- parrots["markov_macaws:macaw"] = true
- --put message into markov chain
- --if the parrot likes the player this is more likely to happen
- local function hear(self, talker_name, message)
- local rel = self.player_relations[talker_name]
- if rel < 0 or math.random() < 0.5
- then
- return
- end
- self.player_relations[talker_name] = rel + math.random() * 0.5
- self.talker:learn_line(message)
-
- if math.random() < 0.1
- then
- self.talker:cull(MAX_KNOWN_WORDS)
- end
- end
- --Makes parrots in a radius around the player hear messages
- local function parrot_chat_listener(name, message)
- local player = minetest.get_player_by_name(name)
- if not player
- then
- return
- end
- if minetest.check_player_privs(player, "shout")
- then
- local pos = player:get_pos()
- local objs = minetest.get_objects_inside_radius(pos, HEAR_DISTANCE)
- --check all entities in a radius around the player if they are
- --parrots. Those that are are called in the hear function
- for i, obj in pairs(objs)
- do
- local luae = obj:get_luaentity()
- if luae
- then
- if parrots[luae.name]
- then
- hear(luae, name, message)
- end
- end
- end
- end
- end
- minetest.register_on_chat_message(parrot_chat_listener)
- --on_activate function. Takes care of mobkit activation and also
- --initializes player_relations table and talker (markov chain) object
- local function init_parrot(self, staticdata, dtime_s)
- mobkit.actfunc(self, staticdata, dtime_s)
-
- --initialize self.talker
- --self.talker stores a markov chain object
- self.talker = MarkovGraph()
- local words = mobkit.recall(self, "words")
- if not words
- then
- --initialize with new words
- self.talker:learn_from_file(modpath .. "/default_words.txt")
- mobkit.remember(self, "words", self.talker.nodes)
- else
- --initialize with loaded words
- local meta = getmetatable(self.talker.nodes)
- self.talker.nodes = words
- setmetatable(self.talker.nodes, meta)
- end
-
- --initialize self.player_relations
- --self.player_relations maps names of known players to numbers
- --the number indicates how much the parrot likes the player
- self.player_relations = mobkit.recall(self, "player_relations")
- if not self.player_relations
- then
- self.player_relations = {}
- mobkit.remember(self, "player_relations", self.player_relations)
- end
- do
- --accessing relation with unknown player returns 0 instead of nil
- local meta =
- {
- __index = function(self, key)
- return 0
- end,
- }
- setmetatable(self.player_relations, meta)
- end
- end
- local function lq_animate_for_duration(self, duration, animation)
- local time = 0
- mobkit.animate(self, animation)
- local function func()
- time = time + self.dtime
- if time > duration
- then
- return true
- end
- end
- mobkit.queue_low(self, func)
- end
- local function hq_nope(self, prty)
- local function func()
- if self.isonground
- then
- mobkit.clear_queue_low(self)
- mobkit.make_sound(self, "squawk")
- lq_animate_for_duration(self, 0.5, "flap")
- return true
- else
- return true
- end
- end
- mobkit.queue_high(self, func, prty)
- end
- --this doesn't make 100% sense because it makes
- --assumptions about how the player model looks
- local attach_positions =
- player_api and
- {
- {"Body", {x = 4, y = 6, z = 0}, {x = 0, y = 180, z = 0}},
- {"Body", {x = 4, y = 6, z = 0}, {x = 0, y = 0, z = 0}},
- {"Body", {x = -4, y = 6, z = 0}, {x = 0, y = 180, z = 0}},
- {"Body", {x = -4, y = 6, z = 0}, {x = 0, y = 0, z = 0}},
- } or
- {
- --alternative if player_api is not installed
- {"", {x = 4, y = 14, z = 0}, {x = 0, y = 0, z = 0}},
- {"", {x = 4, y = 14, z = 0}, {x = 0, y = 180, z = 0}},
- {"", {x = -4, y = 14, z = 0}, {x = 0, y = 0, z = 0}},
- {"", {x = -4, y = 14, z = 0}, {x = 0, y = 180, z = 0}},
- }
- --TODO: use low level queue properly
- local function hq_perch(self, prty, mount)
- --TODO: make this only work if there is no parrot on the shoulder yet
- local mountpos = attach_positions[math.random(#attach_positions)]
- self.object:set_attach(mount, mountpos[1], mountpos[2], mountpos[3])
-
-
- local trouble = 0
- local troubletimer = 0
-
- local function func()
- --if mount is jumping, increase trouble value
- if mobkit.is_alive(mount)
- then
- trouble = trouble + math.abs(mount:get_player_velocity().y)
- end
- --decay trouble value
- trouble = trouble * 0.9
- if trouble > 3
- then
- --flap if trouble value is high
- troubletimer = troubletimer + self.dtime
- mobkit.clear_queue_low(self)
- lq_animate_for_duration(self, 0.1, "perch_flap")
- else
- if not mobkit.is_queue_empty_low(self)
- then
- return false
- end
- --reset trouble timer
- troubletimer = 0
-
- --dance randomly
- local dance = math.random()
- if dance < 0.3
- then
- lq_animate_for_duration(self, 1, "perch_dance")
- return false
- end
- lq_animate_for_duration(self, 1, "perch")
- end
- --dismount if jumping or falling for long / fast enough
- if troubletimer * trouble > 300 or not self.object:get_attach()
- then
- self.object:set_detach()
- return true
- end
- end
- mobkit.queue_high(self, func, prty)
- end
- local function eat_food(self, clicker)
- --only eat from non-bad people
- local name = clicker:get_player_name()
- local rel = self.player_relations[name]
- if rel < 0 or
- math.random() > rel
- then
- return false
- end
-
-
- local wielditem = clicker:get_wielded_item()
- if foods[wielditem:get_name()]
- then
- wielditem:take_item()
- clicker:set_wielded_item(wielditem)
- self.player_relations[name] = rel + math.random()
- return true
- end
- end
- local function on_rightclick(self, clicker)
- if eat_food(self, clicker)
- then
- return
- end
-
- local clickername = clicker:get_player_name()
- local rel = self.player_relations[clickername]
- if rel > 2 and math.random() < rel
- then
- --perch on shoulder if lucky
- hq_perch(self, 20, clicker)
- return
- end
- --play flap animation
- mobkit.clear_queue_high(self)
- hq_nope(self, 10)
- end
- local parrot =
- {
- physical = true,
- stepheight = 0.1, --EVIL!
- collide_with_objects = true,
- collisionbox = {-0.3, -0.01, -0.3, 0.3, 0.7, 0.3},
- visual = "mesh",
- mesh = "markov_macaws_macaw.b3d",
- textures = {"markov_macaws_macaw.png"},
- static_save = true,
- makes_footstep_sound = true,
-
- timeout = 0,
- buoyancy = -1,
- lung_capacity = 10,
- max_hp = 10,
- jump_height = 1,
- max_speed = 3,
- view_range = 10,
- armor_groups = {fleshy = 3},
- color = "red", --TODO: make more colors
-
- animation =
- {
- walk = {range = {x = 1, y = 29}, speed = 30, loop = true},
- stand = {range = {x = 101, y = 129}, speed = 30, loop = true},
- emote =
- {
- {range = {x = 61, y = 69}, speed = 30, loop = true}, --hop
- {range = {x = 131, y = 139}, speed = 30, loop = true} --dance
- },
- flap = {range = {x = 171, y = 185}, speed = 30, loop = true},
-
- perch = {range = {x = 31, y = 59}, speed = 30, loop = true},
- perch_flap = {range = {x = 155, y = 169}, speed = 30, loop = true},
- perch_dance = {range = {x = 141, y = 153}, speed = 15, loop = true},
-
- },
- sounds =
- {
- squawk =
- {
- name = "markov_macaws_squawk",
- gain = {1, 1.5},
- pitch = {0.7, 1.5},
- },
- talk =
- {
- name = "markov_macaws_talk",
- pitch = {0.7, 1.3},
- gain = {0.5, 1.5},
- },
- playful =
- {
- {
- name = "markov_macaws_whistle",
- pitch = {0.7, 1.5},
- gain = {0.3, 1.5},
- },
- {
- name = "markov_macaws_clack",
- pitch = {0.9, 1.3},
- gain = {0.3, 1.5},
- },
- {
- name = "markov_macaws_squawk",
- pitch = {0.7, 1.3},
- gain = {1, 1.5},
- },
- {
- name = "markov_macaws_talk",
- pitch = {0.7, 1.3},
- gain = {0.5, 1.5},
- },
- }
- },
-
- logic = parrot_brain,
-
- on_step = mobkit.stepfunc, -- required
- on_activate = init_parrot,
- get_staticdata = mobkit.statfunc,
- on_rightclick = on_rightclick,
- on_punch = function(self, puncher, time_from_last_punch, tool_capabilities, dir)
- if mobkit.is_alive(self) then
- local hvel = vector.multiply(vector.normalize({x=dir.x,y=0,z=dir.z}),4)
- self.object:set_velocity({x=hvel.x,y=2,z=hvel.z})
-
- mobkit.hurt(self,tool_capabilities.damage_groups.fleshy or 1)
- if type(puncher)=='userdata' and puncher:is_player() then -- if hit by a player
- mobkit.clear_queue_high(self) -- abandon whatever they've been doing
- mobkit.hq_runfrom(self, 50, puncher) -- flee
- end
- end
- end
- }
- minetest.register_entity("markov_macaws:macaw", parrot)
|