2000-11-27 18:28:20 +00:00
|
|
|
#This file is distributed under the terms of the GNU General Public license.
|
|
|
|
|
#Copyright (C) 1999 Aloril (See the file COPYING for details).
|
|
|
|
|
|
2009-04-21 23:02:14 -07:00
|
|
|
import random
|
2013-04-07 23:18:29 +02:00
|
|
|
import traceback
|
2009-04-21 23:02:14 -07:00
|
|
|
|
2000-11-27 18:28:20 +00:00
|
|
|
from atlas import *
|
2004-12-19 01:46:23 +00:00
|
|
|
from physics import *
|
2009-05-20 20:50:26 +01:00
|
|
|
from physics import Quaternion
|
2000-11-27 18:28:20 +00:00
|
|
|
from common import const
|
2000-12-01 00:13:39 +00:00
|
|
|
from types import *
|
2000-11-27 18:28:20 +00:00
|
|
|
|
2009-05-20 19:34:01 +01:00
|
|
|
from physics import Vector3D
|
2009-07-01 17:03:24 +01:00
|
|
|
|
|
|
|
|
import server
|
2000-11-27 18:28:20 +00:00
|
|
|
|
|
|
|
|
from mind.Memory import Memory
|
|
|
|
|
from mind.Knowledge import Knowledge
|
|
|
|
|
from mind.panlingua import interlinguish,ontology
|
2005-01-09 01:16:16 +00:00
|
|
|
from mind.compass import vector_to_compass
|
2000-11-27 18:28:20 +00:00
|
|
|
from common import log,const
|
2006-01-06 21:39:00 +00:00
|
|
|
import dictlist
|
2000-11-27 18:28:20 +00:00
|
|
|
import mind.goals
|
|
|
|
|
import mind.goals.common
|
|
|
|
|
|
|
|
|
|
reverse_cmp={'>':'<'}
|
|
|
|
|
|
2005-12-05 21:40:45 +00:00
|
|
|
def get_dict_func(self, func_str, func_undefined):
|
|
|
|
|
"""get method by name from instance or return default handler"""
|
|
|
|
|
try:
|
|
|
|
|
func=getattr(self,func_str)
|
|
|
|
|
except AttributeError:
|
|
|
|
|
func=func_undefined
|
|
|
|
|
return func
|
|
|
|
|
|
2009-07-01 17:03:24 +01:00
|
|
|
class NPCMind(server.Mind):
|
2006-10-09 18:30:23 +00:00
|
|
|
"""Mind class for most mobile entities in the game.
|
|
|
|
|
|
|
|
|
|
An NPCMind object is associated with all NPC and similar entities on a
|
|
|
|
|
game server. It handles perception data from the world, tracks what
|
|
|
|
|
the NPC knows about, and handles its goals.
|
|
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
The data is organized into three key data structures:
|
2006-10-09 18:30:23 +00:00
|
|
|
|
|
|
|
|
self.map is handled by the underlying C++ code, and contains a copy of
|
|
|
|
|
all the entities in the world that this NPC is currently able to perceive.
|
|
|
|
|
|
|
|
|
|
self.knowledge contains data triples which define relations between
|
|
|
|
|
entities.
|
|
|
|
|
|
|
|
|
|
self.goals and self.trigger_goals contain trees of goals which represent
|
|
|
|
|
current and potential activities that NPC might engage in. self.goals are
|
|
|
|
|
goals which are checked each tick, self.trigger_goals are goals which
|
|
|
|
|
are activated by an event."""
|
2000-11-27 18:28:20 +00:00
|
|
|
########## Initialization
|
2005-12-01 02:17:06 +00:00
|
|
|
def __init__(self, cppthing):
|
2009-07-01 17:03:24 +01:00
|
|
|
self.mind = cppthing
|
2000-11-27 18:28:20 +00:00
|
|
|
|
|
|
|
|
self.knowledge=Knowledge()
|
|
|
|
|
self.mem=Memory(map=self.map)
|
|
|
|
|
self.things={}
|
2008-09-17 15:14:08 +00:00
|
|
|
self.pending_things=[]
|
2000-12-01 00:13:39 +00:00
|
|
|
self._reverse_knowledge()
|
2000-11-27 18:28:20 +00:00
|
|
|
self.goals=[]
|
|
|
|
|
self.money_transfers=[]
|
2005-12-16 02:33:31 +00:00
|
|
|
self.transfers=[]
|
2000-11-27 18:28:20 +00:00
|
|
|
self.trigger_goals={}
|
2009-04-21 23:02:14 -07:00
|
|
|
self.jitter=random.uniform(-0.1, 0.1)
|
2000-11-27 18:28:20 +00:00
|
|
|
#???self.debug=debug(self.name+".mind.log")
|
|
|
|
|
self.message_queue=None
|
|
|
|
|
#This is going to be really tricky
|
2000-12-30 23:16:57 +00:00
|
|
|
self.map.add_hooks_append("add_map")
|
|
|
|
|
self.map.update_hooks_append("update_map")
|
|
|
|
|
self.map.delete_hooks_append("delete_map")
|
2013-09-09 20:04:00 +02:00
|
|
|
self.goal_id_counter=0
|
2016-06-08 21:45:02 +02:00
|
|
|
def print_debug(self, message):
|
|
|
|
|
"""Prints a debug message using 'print', prepending the message with a description of the entity."""
|
|
|
|
|
print self.describeEntity() + ": " + message
|
2005-12-05 21:40:45 +00:00
|
|
|
def find_op_method(self, op_id, prefix="",undefined_op_method=None):
|
|
|
|
|
"""find right operation to invoke"""
|
|
|
|
|
if not undefined_op_method: undefined_op_method=self.undefined_op_method
|
|
|
|
|
return get_dict_func(self, prefix+op_id+"_operation",undefined_op_method)
|
|
|
|
|
def undefined_op_method(self, op):
|
|
|
|
|
"""this operation is used when no other matching operation is found"""
|
|
|
|
|
pass
|
|
|
|
|
def get_op_name_and_sub(self, op):
|
|
|
|
|
event_name = op.id
|
|
|
|
|
sub_op = op
|
2008-12-07 03:54:44 +00:00
|
|
|
# I am not quite sure why this is while, as it's only over true
|
|
|
|
|
# for one iteration.
|
2005-12-05 21:40:45 +00:00
|
|
|
while len(sub_op) and sub_op[0].get_name()=="op":
|
|
|
|
|
sub_op = sub_op[0]
|
|
|
|
|
event_name = event_name + "_" + sub_op.id
|
|
|
|
|
return event_name, sub_op
|
2011-09-02 16:04:07 +02:00
|
|
|
def is_talk_op_addressed_to_me_or_none(self, op):
|
|
|
|
|
"""Checks whether a Talk op is addressed either to none or to me.
|
2011-09-14 16:29:56 +01:00
|
|
|
This is useful is we want to avoid replying to queries addressed
|
|
|
|
|
to other entities."""
|
2011-09-02 16:04:07 +02:00
|
|
|
talk_entity=op[0]
|
|
|
|
|
if hasattr(talk_entity, "address"):
|
|
|
|
|
addressElement = talk_entity.address
|
|
|
|
|
if len(addressElement) == 0:
|
|
|
|
|
return True
|
|
|
|
|
return self.id in addressElement
|
|
|
|
|
return True
|
2000-11-27 18:28:20 +00:00
|
|
|
########## Map updates
|
|
|
|
|
def add_map(self, obj):
|
2006-10-09 18:30:23 +00:00
|
|
|
"""Hook called by underlying map code when an entity is added."""
|
2000-11-27 18:28:20 +00:00
|
|
|
#print "Map add",obj
|
|
|
|
|
pass
|
|
|
|
|
def update_map(self, obj):
|
2009-06-23 15:38:50 +01:00
|
|
|
"""Hook called by underlying map code when an entity is updated.
|
2006-10-09 18:30:23 +00:00
|
|
|
|
|
|
|
|
Fix ownership category for objects owned temporary under 'Foo' type."""
|
2000-11-27 18:28:20 +00:00
|
|
|
#print "Map update",obj
|
|
|
|
|
foo_lst = self.things.get('Foo',[])
|
|
|
|
|
for foo in foo_lst[:]: #us copy in loop, because it might get modified
|
2005-06-23 17:01:27 +00:00
|
|
|
print "Oh MY GOD! We have a Foo thing!"
|
2000-11-27 18:28:20 +00:00
|
|
|
if foo.id==obj.id:
|
|
|
|
|
self.remove_thing(foo)
|
|
|
|
|
self.add_thing(obj)
|
|
|
|
|
def delete_map(self, obj):
|
2009-06-23 15:38:50 +01:00
|
|
|
"""Hook called by underlying map code when an entity is deleted."""
|
2000-11-27 18:28:20 +00:00
|
|
|
#print "Map delete",obj
|
|
|
|
|
self.remove_thing(obj)
|
|
|
|
|
########## Operations
|
|
|
|
|
def setup_operation(self, op):
|
|
|
|
|
"""called once by world after object has been made
|
2015-05-12 21:12:11 +02:00
|
|
|
send first tick operation to object
|
|
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_operation name."""
|
2000-11-27 18:28:20 +00:00
|
|
|
#CHEAT!: add memory, etc... initialization (or some of it to __init__)
|
2015-05-24 13:29:42 +02:00
|
|
|
|
|
|
|
|
#Setup a tick operation for thinking
|
|
|
|
|
thinkTickOp = Operation("tick")
|
|
|
|
|
thinkTickOp.setArgs([Entity(name="think")])
|
2015-09-25 21:25:04 +02:00
|
|
|
|
|
|
|
|
#Setup a tick operation for moving
|
|
|
|
|
moveTickOp = Operation("tick")
|
|
|
|
|
moveTickOp.setArgs([Entity(name="move")])
|
|
|
|
|
moveTickOp.setFutureSeconds(0.2)
|
2015-05-24 13:29:42 +02:00
|
|
|
|
2015-05-26 22:28:14 +02:00
|
|
|
#Setup a tick operation for periodical persistence of thoughts to the server
|
|
|
|
|
sendThoughtsTickOp = Operation("tick")
|
|
|
|
|
sendThoughtsTickOp.setArgs([Entity(name="persistthoughts")])
|
|
|
|
|
sendThoughtsTickOp.setFutureSeconds(5)
|
|
|
|
|
|
2015-09-25 21:25:04 +02:00
|
|
|
return Operation("look")+thinkTickOp+moveTickOp+sendThoughtsTickOp
|
2000-11-27 18:28:20 +00:00
|
|
|
def tick_operation(self, op):
|
2015-05-12 21:12:11 +02:00
|
|
|
"""periodically reasses situation
|
|
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_operation name.
|
|
|
|
|
"""
|
2015-05-24 13:29:42 +02:00
|
|
|
args=op.getArgs()
|
|
|
|
|
if len(args) != 0:
|
|
|
|
|
if args[0].name == "think":
|
2015-05-26 22:28:14 +02:00
|
|
|
#It's a "thinking" op, which is the base of the AI behaviour.
|
|
|
|
|
#At regular intervals the AI needs to assess its goals; this is one through "thinkning" ops.
|
2015-05-24 13:29:42 +02:00
|
|
|
opTick=Operation("tick")
|
|
|
|
|
#just copy the args from the previous tick
|
|
|
|
|
opTick.setArgs(args)
|
|
|
|
|
opTick.setFutureSeconds(const.basic_tick + self.jitter)
|
|
|
|
|
for t in self.pending_things:
|
|
|
|
|
thing = self.map.get(t)
|
|
|
|
|
if thing and thing.type[0]:
|
|
|
|
|
self.add_thing(thing)
|
|
|
|
|
self.pending_things=[]
|
|
|
|
|
result=self.think()
|
|
|
|
|
if self.message_queue:
|
|
|
|
|
result = self.message_queue + result
|
|
|
|
|
self.message_queue = None
|
|
|
|
|
return opTick+result
|
2015-05-26 22:28:14 +02:00
|
|
|
elif args[0].name == "persistthoughts":
|
|
|
|
|
#It's a periodic tick for sending thoughts to the server (so that they can be persisted)
|
|
|
|
|
#TODO: only send thoughts when they have changed.
|
|
|
|
|
opTick=Operation("tick")
|
|
|
|
|
#just copy the args from the previous tick
|
|
|
|
|
opTick.setArgs(args)
|
|
|
|
|
#Persist the thoughts to the server at 30 second intervals.
|
|
|
|
|
opTick.setFutureSeconds(30)
|
|
|
|
|
result = self.commune_all_thoughts(op, "persistthoughts")
|
|
|
|
|
return opTick+result
|
|
|
|
|
|
2000-11-27 18:28:20 +00:00
|
|
|
########## Sight operations
|
2007-11-23 16:06:52 +00:00
|
|
|
def sight_create_operation(self, op):
|
2015-05-12 21:12:11 +02:00
|
|
|
"""Note our ownership of entities we created.
|
|
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_*_operation name."""
|
2000-11-27 18:28:20 +00:00
|
|
|
#BaseMind version overridden!
|
2004-07-21 23:52:58 +00:00
|
|
|
obj=self.map.add(op[0], op.getSeconds())
|
2007-11-23 16:06:52 +00:00
|
|
|
if op.to==self.id:
|
2000-11-27 18:28:20 +00:00
|
|
|
self.add_thing(obj)
|
2007-11-23 16:06:52 +00:00
|
|
|
def sight_move_operation(self, op):
|
2015-05-12 21:12:11 +02:00
|
|
|
"""change position in our local map
|
|
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_*_operation name."""
|
2004-07-21 23:52:58 +00:00
|
|
|
obj=self.map.update(op[0], op.getSeconds())
|
2000-12-11 13:06:12 +00:00
|
|
|
if obj.location.parent.id==self.id:
|
2000-11-27 18:28:20 +00:00
|
|
|
self.add_thing(obj)
|
2007-11-23 16:06:52 +00:00
|
|
|
if op.to != self.id:
|
2005-12-16 02:33:31 +00:00
|
|
|
self.transfers.append((op.from_, obj.id))
|
2007-04-29 02:11:25 +00:00
|
|
|
if obj.type[0]=="coin" and op.from_ != self.id:
|
2005-09-11 Al Riddoch <alriddoch@zepler.org>
* rulesets/Py_Operation.h, rulesets/Py_Operation.cpp: Remove reference
ownership flag, and from and to pointers from the python operation
structure.
* rulesets/Py_Map.cpp, rulesets/Py_Oplist.cpp,
rulesets/PythonMindScript.cpp, rulesets/PythonThingScript.cpp,
rulesets/Python_API.cpp: Remove all usage of own, from and to.
* rulesets/basic/mind/NPCMind.py,
rulesets/basic/mind/goals/common/misc_goal.py,
rulesets/basic/mind/goals/humanoid/mason.py,
rulesets/basic/mind/goals/humanoid/transaction.py,
rulesets/mason/world/objects/clothing/Garment.py,
rulesets/mason/world/objects/plants/seeds/Acorn.py,
rulesets/mason/world/objects/plants/seeds/Apple.py,
rulesets/mason/world/objects/plants/seeds/Fircone.py,
rulesets/mason/world/objects/tools/Bucksaw.py,
rulesets/mason/world/objects/tools/Cleaver.py,
rulesets/mason/world/objects/tools/Trowel.py: Modify all scripts
so they expect from and to of ops to be an ID string rather than
an entity.
2005-09-11 18:51:55 +00:00
|
|
|
self.money_transfers.append([op.from_, 1])
|
2005-12-16 02:33:31 +00:00
|
|
|
return Operation("imaginary", Entity(description="accepts"))
|
2015-05-12 21:12:11 +02:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def think_get_operation(self, op):
|
|
|
|
|
"""A Think op wrapping a Get op is used to inquire about the status of a mind.
|
2013-08-21 23:32:25 +02:00
|
|
|
It's often sent from authoring clients, as well as the server itself when
|
|
|
|
|
it wants to persist the thoughts of a mind.
|
2015-05-12 21:12:11 +02:00
|
|
|
A Get op without any args means that the mind should dump all its thoughts.
|
2013-08-21 23:32:25 +02:00
|
|
|
If there are args however, the meaning of what's to return differs depending on the
|
|
|
|
|
args.
|
|
|
|
|
* If "goal" is specified, a "think" operation only pertaining to goals is returned. The
|
|
|
|
|
"goal" arg should be a map, where the keys and values are used to specify exactly what goals
|
|
|
|
|
to return. An empty map returns all goals.
|
2015-05-12 21:12:11 +02:00
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_*_operation name."""
|
2013-02-04 20:15:29 +01:00
|
|
|
|
2013-08-21 23:32:25 +02:00
|
|
|
args=op.getArgs()
|
|
|
|
|
#If there are no args we should send all of our thoughts
|
|
|
|
|
if len(args) == 0:
|
2015-05-26 22:28:14 +02:00
|
|
|
return self.commune_all_thoughts(op, None)
|
2013-08-21 23:32:25 +02:00
|
|
|
else:
|
|
|
|
|
argEntity=args[0]
|
2013-02-10 12:50:45 +01:00
|
|
|
|
2013-08-21 23:32:25 +02:00
|
|
|
if hasattr(argEntity, "goal"):
|
|
|
|
|
goal_entity = argEntity.goal
|
|
|
|
|
return self.commune_goals(op, goal_entity)
|
2015-09-27 16:50:43 +02:00
|
|
|
if hasattr(argEntity, "path"):
|
|
|
|
|
return self.commune_path(op)
|
|
|
|
|
|
2013-08-21 23:32:25 +02:00
|
|
|
#TODO: allow for finer grained query of specific thoughts
|
2015-09-27 16:50:43 +02:00
|
|
|
def commune_path(self, op):
|
|
|
|
|
"""Sends back information about the path."""
|
|
|
|
|
thinkOp = Operation("think")
|
|
|
|
|
path = []
|
2016-06-08 21:45:02 +02:00
|
|
|
myPath = self.path
|
|
|
|
|
#self.print_debug("path size: " + str(len(myPath)))
|
|
|
|
|
for point in myPath:
|
2015-09-27 16:50:43 +02:00
|
|
|
path.append([point.x, point.y, point.z])
|
2013-02-10 12:50:45 +01:00
|
|
|
|
2015-09-27 18:00:53 +02:00
|
|
|
thinkOp.setArgs([Entity(path=path)])
|
2015-09-27 16:50:43 +02:00
|
|
|
|
|
|
|
|
res = Oplist()
|
|
|
|
|
res = res + thinkOp
|
|
|
|
|
return res
|
|
|
|
|
|
2013-08-21 23:32:25 +02:00
|
|
|
def commune_goals(self, op, goal_entity):
|
|
|
|
|
"""Sends back information about goals only."""
|
|
|
|
|
thinkOp = Operation("think")
|
2015-05-12 21:12:11 +02:00
|
|
|
setOp = Operation("set")
|
2013-08-21 23:32:25 +02:00
|
|
|
thoughts = []
|
|
|
|
|
|
2013-09-09 20:04:00 +02:00
|
|
|
#It's important that the order of the goals is retained
|
|
|
|
|
for goal in self.goals:
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString = ""
|
2013-09-09 20:04:00 +02:00
|
|
|
if hasattr(goal, "str"):
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString = goal.str
|
2013-09-09 20:04:00 +02:00
|
|
|
else:
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString = goal.__class__.__name__
|
2013-09-09 20:04:00 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
thoughts.append(Entity(goal=goalString, id=goalString))
|
2013-09-09 20:04:00 +02:00
|
|
|
|
|
|
|
|
for (trigger, goallist) in sorted(self.trigger_goals.items()):
|
2013-08-21 23:32:25 +02:00
|
|
|
for goal in goallist:
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString = ""
|
2013-09-09 20:04:00 +02:00
|
|
|
if hasattr(goal, "str"):
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString = goal.str
|
2013-09-09 20:04:00 +02:00
|
|
|
else:
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString = goal.__class__.__name__
|
2013-09-09 20:04:00 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
thoughts.append(Entity(goal=goalString, id=goalString))
|
2013-09-09 20:04:00 +02:00
|
|
|
|
2013-08-21 23:32:25 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
setOp.setArgs(thoughts)
|
|
|
|
|
thinkOp.setArgs([setOp])
|
2013-08-21 23:32:25 +02:00
|
|
|
thinkOp.setRefno(op.getSerialno())
|
|
|
|
|
res = Oplist()
|
|
|
|
|
res = res + thinkOp
|
2013-02-04 20:15:29 +01:00
|
|
|
return res
|
2013-09-09 20:04:00 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
def find_goal(self, definition):
|
|
|
|
|
"""Searches for a goal, with the specified id"""
|
2013-09-09 20:04:00 +02:00
|
|
|
#Goals are either stored in "self.goals" or "self.trigger_goals", so we need
|
|
|
|
|
#to check both
|
|
|
|
|
for goal in self.goals:
|
2015-05-12 21:12:11 +02:00
|
|
|
if goal.str == definition:
|
2013-09-09 20:04:00 +02:00
|
|
|
return goal
|
|
|
|
|
for (trigger, goallist) in sorted(self.trigger_goals.items()):
|
|
|
|
|
for goal in goallist:
|
2015-05-12 21:12:11 +02:00
|
|
|
if goal.str == definition:
|
2013-09-09 20:04:00 +02:00
|
|
|
return goal
|
|
|
|
|
return None
|
2013-08-21 23:32:25 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
def think_look_operation(self, op):
|
2013-08-21 23:32:25 +02:00
|
|
|
"""Sends back information about goals. This is mainly to be used for debugging minds.
|
|
|
|
|
If no arguments are specified all goals will be reported, else a match will be done
|
2015-05-12 21:12:11 +02:00
|
|
|
using 'id'.
|
|
|
|
|
The information will be sent back as a Think operation wrapping an Info operation.
|
|
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_*_operation name.
|
2013-08-21 23:32:25 +02:00
|
|
|
"""
|
2015-05-12 21:12:11 +02:00
|
|
|
thinkOp = Operation("think")
|
2013-08-21 23:32:25 +02:00
|
|
|
goalInfoOp = Operation("info")
|
|
|
|
|
goal_infos = []
|
2013-04-13 23:25:16 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
if not op.getArgs():
|
2013-08-21 23:32:25 +02:00
|
|
|
#get all goals
|
2013-09-09 20:04:00 +02:00
|
|
|
for goal in self.goals:
|
2015-05-12 21:12:11 +02:00
|
|
|
goal_infos.append(Entity(id=goal.str, report=goal.report()))
|
2013-09-09 20:04:00 +02:00
|
|
|
for (trigger, goallist) in sorted(self.trigger_goals.items()):
|
2013-08-21 23:32:25 +02:00
|
|
|
for goal in goallist:
|
2015-05-12 21:12:11 +02:00
|
|
|
goal_infos.append(Entity(id=goal.str, report=goal.report()))
|
|
|
|
|
else:
|
|
|
|
|
for arg in op.getArgs():
|
|
|
|
|
goal = self.find_goal(arg.id)
|
2016-06-08 21:44:33 +02:00
|
|
|
if goal and goal is not None:
|
2015-05-12 21:12:11 +02:00
|
|
|
goal_infos.append(Entity(id=goal.str, report=goal.report()))
|
2013-08-21 23:32:25 +02:00
|
|
|
|
|
|
|
|
goalInfoOp.setArgs(goal_infos)
|
2015-05-12 21:12:11 +02:00
|
|
|
thinkOp.setRefno(op.getSerialno())
|
|
|
|
|
thinkOp.setArgs([goalInfoOp])
|
2013-08-21 23:32:25 +02:00
|
|
|
res = Oplist()
|
2015-05-12 21:12:11 +02:00
|
|
|
res = res + thinkOp
|
2013-08-21 23:32:25 +02:00
|
|
|
return res
|
2013-08-14 00:06:25 +02:00
|
|
|
|
2013-08-21 23:32:25 +02:00
|
|
|
|
2015-05-26 22:28:14 +02:00
|
|
|
def commune_all_thoughts(self, op, name):
|
2013-08-21 23:32:25 +02:00
|
|
|
"""Sends back information on all thoughts. This includes knowledge and goals,
|
|
|
|
|
as well as known things.
|
2015-05-12 21:12:11 +02:00
|
|
|
The thoughts will be sent back as a "think" operation, wrapping a Set operation, in a manner such that if the
|
2013-08-21 23:32:25 +02:00
|
|
|
same think operation is sent back to the mind all thoughts will be restored. In
|
|
|
|
|
this way the mind can support server side persistence of its thoughts.
|
2015-05-26 22:28:14 +02:00
|
|
|
A name can optionally be supplied, which will be set on the Set operation.
|
2013-08-21 23:32:25 +02:00
|
|
|
"""
|
2013-08-16 00:02:36 +02:00
|
|
|
thinkOp = Operation("think")
|
2015-05-12 21:12:11 +02:00
|
|
|
setOp = Operation("set")
|
2013-08-16 00:02:36 +02:00
|
|
|
thoughts = []
|
2013-08-14 00:06:25 +02:00
|
|
|
|
2013-09-08 23:04:04 +02:00
|
|
|
for attr in sorted(dir(self.knowledge)):
|
2013-08-14 00:06:25 +02:00
|
|
|
d=getattr(self.knowledge, attr)
|
|
|
|
|
if getattr(d, '__iter__', False):
|
2013-09-08 23:04:04 +02:00
|
|
|
for key in sorted(d):
|
2013-08-14 00:06:25 +02:00
|
|
|
if attr!="goal":
|
|
|
|
|
objectVal=d[key]
|
|
|
|
|
if type(objectVal) is Location:
|
|
|
|
|
#Serialize Location as tuple, with parent if available
|
2015-04-29 22:35:22 +02:00
|
|
|
if (objectVal.parent is None):
|
2013-08-14 00:06:25 +02:00
|
|
|
location=objectVal.coordinates
|
|
|
|
|
else:
|
2015-04-29 22:35:22 +02:00
|
|
|
location=("$eid:" + objectVal.parent.id,objectVal.coordinates)
|
2013-08-14 00:06:25 +02:00
|
|
|
object=str(location)
|
|
|
|
|
else:
|
|
|
|
|
object=str(d[key])
|
|
|
|
|
|
2013-08-16 00:02:36 +02:00
|
|
|
thoughts.append(Entity(predicate=attr, subject=str(key), object=object))
|
2013-08-14 00:06:25 +02:00
|
|
|
|
2013-09-09 20:04:00 +02:00
|
|
|
#It's important that the order of the goals is retained
|
|
|
|
|
for goal in self.goals:
|
|
|
|
|
if hasattr(goal, "str"):
|
2015-05-12 21:12:11 +02:00
|
|
|
thoughts.append(Entity(goal=goal.str, id=goal.str))
|
2013-09-09 20:04:00 +02:00
|
|
|
|
|
|
|
|
for (trigger, goallist) in sorted(self.trigger_goals.items()):
|
2013-08-14 00:06:25 +02:00
|
|
|
for goal in goallist:
|
2013-09-09 20:04:00 +02:00
|
|
|
if hasattr(goal, "str"):
|
2015-05-12 21:12:11 +02:00
|
|
|
thoughts.append(Entity(goal=goal.str, id=goal.str))
|
2013-09-09 20:04:00 +02:00
|
|
|
|
2013-08-14 00:06:25 +02:00
|
|
|
if len(self.things) > 0:
|
|
|
|
|
things={}
|
2013-09-08 23:04:04 +02:00
|
|
|
for (id, thinglist) in sorted(self.things.items()):
|
2013-08-14 00:06:25 +02:00
|
|
|
idlist=[]
|
|
|
|
|
for thing in thinglist:
|
|
|
|
|
idlist.append(thing.id)
|
|
|
|
|
things[id] = idlist
|
2013-08-21 23:32:25 +02:00
|
|
|
thoughts.append(Entity(things=things))
|
2013-08-14 00:06:25 +02:00
|
|
|
|
|
|
|
|
if len(self.pending_things) > 0:
|
2013-08-21 23:32:25 +02:00
|
|
|
thoughts.append(Entity(pending_things=self.pending_things))
|
2013-08-14 00:06:25 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
setOp.setArgs(thoughts)
|
|
|
|
|
thinkOp.setArgs([setOp])
|
2015-05-26 22:28:14 +02:00
|
|
|
if not op.isDefaultSerialno():
|
|
|
|
|
thinkOp.setRefno(op.getSerialno())
|
|
|
|
|
if name:
|
|
|
|
|
setOp.setName(name)
|
2013-08-16 00:02:36 +02:00
|
|
|
res = Oplist()
|
|
|
|
|
res = res + thinkOp
|
2013-08-14 00:06:25 +02:00
|
|
|
return res
|
2015-05-12 21:12:11 +02:00
|
|
|
|
|
|
|
|
def think_delete_operation(self, op):
|
|
|
|
|
"""Deletes a thought, or all thoughts if no argument is specified.
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_*_operation name."""
|
2013-08-14 00:06:25 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
if not op.getArgs():
|
|
|
|
|
self.goals = []
|
|
|
|
|
self.trigger_goals = {}
|
|
|
|
|
else:
|
|
|
|
|
args=op.getArgs()
|
|
|
|
|
for thought in args:
|
|
|
|
|
for goal in self.goals:
|
|
|
|
|
if goal.str == thought.id:
|
|
|
|
|
self.goals.remove(goal)
|
|
|
|
|
return
|
|
|
|
|
for (trigger, goallist) in sorted(self.trigger_goals.items()):
|
|
|
|
|
for goal in goallist:
|
|
|
|
|
if goal.str == thought.id:
|
|
|
|
|
goallist.remove(goal)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
def think_set_operation(self, op):
|
|
|
|
|
"""Sets a new thought, or updates an existing one
|
|
|
|
|
|
|
|
|
|
This method is automatically invoked by the C++ BaseMind code, due to its *_*_operation name."""
|
2015-05-26 22:28:14 +02:00
|
|
|
|
|
|
|
|
#If the Set op has the name "peristthoughts" it's a Set op sent to ourselves meant for the server
|
|
|
|
|
#(so it can persist the thoughts in the database). We should ignore it.
|
|
|
|
|
if op.getName() == "persistthoughts":
|
|
|
|
|
return
|
|
|
|
|
|
2013-08-16 00:02:36 +02:00
|
|
|
args=op.getArgs()
|
|
|
|
|
for thought in args:
|
2013-08-14 00:06:25 +02:00
|
|
|
#Check if there's a 'predicate' set; if so handle it as knowledge.
|
|
|
|
|
#Else check if it's things that we know we own or ought to own.
|
|
|
|
|
if hasattr(thought, "predicate") == False:
|
|
|
|
|
if hasattr(thought, "things"):
|
|
|
|
|
things=thought.things
|
|
|
|
|
for (id, thinglist) in things.items():
|
|
|
|
|
#We can't iterate directly over the list, as it's of type atlas.Message; we must first "pythonize" it.
|
|
|
|
|
#This should be reworked into a better way.
|
|
|
|
|
thinglist=thinglist.pythonize()
|
|
|
|
|
for thingId in thinglist:
|
|
|
|
|
thingId=str(thingId)
|
|
|
|
|
thing = self.map.get(thingId)
|
|
|
|
|
if thing and thing.type[0]:
|
|
|
|
|
self.add_thing(thing)
|
|
|
|
|
else:
|
|
|
|
|
self.pending_things.append(thingId)
|
|
|
|
|
elif hasattr(thought, "pending_things"):
|
|
|
|
|
for id in thought.pending_things:
|
|
|
|
|
self.pending_things.append(str(id))
|
2013-09-09 20:04:00 +02:00
|
|
|
elif hasattr(thought, "goal"):
|
2015-05-12 21:12:11 +02:00
|
|
|
goalString=str(thought.goal)
|
|
|
|
|
if hasattr(thought, "id"):
|
|
|
|
|
id=str(thought.id)
|
|
|
|
|
goal = self.find_goal(id)
|
|
|
|
|
if goal:
|
|
|
|
|
self.update_goal(goal, goalString)
|
2013-09-09 20:04:00 +02:00
|
|
|
else:
|
2015-05-12 21:12:11 +02:00
|
|
|
self.add_goal(goalString)
|
|
|
|
|
else:
|
|
|
|
|
self.add_goal(goalString)
|
|
|
|
|
|
2013-08-14 00:06:25 +02:00
|
|
|
else:
|
|
|
|
|
subject=thought.subject
|
|
|
|
|
predicate=thought.predicate
|
|
|
|
|
object=thought.object
|
2015-04-29 22:35:22 +02:00
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
#Handle locations.
|
|
|
|
|
if len(object) > 0 and object[0]=='(':
|
|
|
|
|
#CHEAT!: remove eval
|
|
|
|
|
locdata=eval(object)
|
|
|
|
|
#If only coords are supplied, it's handled as a location within the same parent space as ourselves
|
|
|
|
|
if (len(locdata) == 3):
|
|
|
|
|
loc=self.location.copy()
|
|
|
|
|
loc.coordinates=Vector3D(list(locdata))
|
|
|
|
|
elif (len(locdata) == 2):
|
|
|
|
|
entity_id_string = locdata[0]
|
|
|
|
|
#A prefix of "$eid:" denotes an entity id; it should be stripped first.
|
|
|
|
|
if entity_id_string.startswith("$eid:"):
|
|
|
|
|
entity_id_string = entity_id_string[5:]
|
|
|
|
|
where=self.map.get_add(entity_id_string)
|
|
|
|
|
coords=Point3D(list(locdata[1]))
|
|
|
|
|
loc=Location(where, coords)
|
|
|
|
|
self.add_knowledge(predicate,subject,loc)
|
2013-04-13 23:25:16 +02:00
|
|
|
else:
|
2015-05-12 21:12:11 +02:00
|
|
|
self.add_knowledge(predicate,subject,object)
|
2013-02-09 19:43:43 +01:00
|
|
|
|
2000-11-27 18:28:20 +00:00
|
|
|
########## Talk operations
|
|
|
|
|
def admin_sound(self, op):
|
2007-11-23 16:06:52 +00:00
|
|
|
assert(op.from_ == op.to)
|
|
|
|
|
return op.from_ == self.id
|
2000-11-27 18:28:20 +00:00
|
|
|
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_warning(self, op, say, msg):
|
2000-11-27 18:28:20 +00:00
|
|
|
log.debug(1,str(self.id)+" interlinguish_warning: "+str(msg)+\
|
2007-11-23 16:06:52 +00:00
|
|
|
": "+str(say[0].lexlink.id[1:]),op)
|
|
|
|
|
def interlinguish_desire_verb3_buy_verb1_operation(self, op, say):
|
2015-05-12 21:12:11 +02:00
|
|
|
"""Handle a sentence of the form 'I would like to buy a ....'
|
2006-10-09 18:30:23 +00:00
|
|
|
|
|
|
|
|
Check if we have any of the type of thing the other character is
|
|
|
|
|
interested in, and whether we know what price to sell at. If so
|
|
|
|
|
set up the transaction goal, which offers to sell it."""
|
2000-11-27 18:28:20 +00:00
|
|
|
object=say[1].word
|
|
|
|
|
thing=self.things.get(object)
|
|
|
|
|
if thing:
|
|
|
|
|
price=self.get_knowledge("price", object)
|
|
|
|
|
if not price:
|
|
|
|
|
return
|
2007-11-23 16:06:52 +00:00
|
|
|
goal=mind.goals.common.misc_goal.transaction(object, op.to, price)
|
|
|
|
|
who=self.map.get(op.to)
|
2000-11-27 18:28:20 +00:00
|
|
|
self.goals.insert(0,goal)
|
2007-11-28 20:51:43 +00:00
|
|
|
return Operation("talk", Entity(say=self.thing_name(who)+" one "+object+" will be "+str(price)+" coins")) + self.face(who)
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_desire_verb3_operation(self, op, say):
|
2006-10-09 18:30:23 +00:00
|
|
|
"""Handle a sentence of the form 'I would like to ...'"""
|
2000-11-27 18:28:20 +00:00
|
|
|
object=say[2:]
|
|
|
|
|
verb=interlinguish.get_verb(object)
|
2005-05-12 00:28:01 +00:00
|
|
|
operation_method=self.find_op_method(verb,"interlinguish_desire_verb3_",
|
2000-11-27 18:28:20 +00:00
|
|
|
self.interlinguish_undefined_operation)
|
2009-05-21 16:34:27 +01:00
|
|
|
res = Oplist()
|
2007-11-23 16:06:52 +00:00
|
|
|
res = res + self.call_interlinguish_triggers(verb, "interlinguish_desire_verb3_", op, object)
|
|
|
|
|
res = res + operation_method(op, object)
|
2000-11-27 18:28:20 +00:00
|
|
|
return res
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_be_verb1_operation(self, op, say):
|
2008-12-07 03:54:44 +00:00
|
|
|
"""Handle sentences of the form '... is more important that ...'
|
|
|
|
|
|
|
|
|
|
Accept instructions about the priority of goals relative to each
|
|
|
|
|
based on key verbs associated with those goals."""
|
2007-11-23 16:06:52 +00:00
|
|
|
if not self.admin_sound(op):
|
|
|
|
|
return self.interlinguish_warning(op,say,"You are not admin")
|
2000-11-27 18:28:20 +00:00
|
|
|
res=interlinguish.match_importance(say)
|
|
|
|
|
if res:
|
|
|
|
|
return self.add_importance(res['sub'].id,'>',res['obj'].id)
|
|
|
|
|
else:
|
2007-11-23 16:06:52 +00:00
|
|
|
return self.interlinguish_warning(op,say,"Unkown assertion")
|
|
|
|
|
def interlinguish_know_verb1_operation(self, op, say):
|
2006-10-09 18:30:23 +00:00
|
|
|
"""Handle a sentence of the form 'know subject predicate object'
|
|
|
|
|
|
|
|
|
|
Accept admin instruction about knowledge, and store the triple
|
|
|
|
|
in our knowledge base."""
|
2007-11-23 16:06:52 +00:00
|
|
|
if not self.admin_sound(op):
|
|
|
|
|
return self.interlinguish_warning(op,say,"You are not admin")
|
2000-11-27 18:28:20 +00:00
|
|
|
subject=say[1].word
|
2004-06-20 19:24:36 +00:00
|
|
|
predicate=say[2].word
|
|
|
|
|
object=say[3].word
|
|
|
|
|
## print "know:",subject,predicate,object
|
2000-11-27 18:28:20 +00:00
|
|
|
if object[0]=='(':
|
|
|
|
|
#CHEAT!: remove eval
|
|
|
|
|
xyz=list(eval(object))
|
|
|
|
|
loc=self.location.copy()
|
|
|
|
|
loc.coordinates=Vector3D(xyz)
|
2004-06-20 19:24:36 +00:00
|
|
|
self.add_knowledge(predicate,subject,loc)
|
2000-11-27 18:28:20 +00:00
|
|
|
else:
|
2004-06-20 19:24:36 +00:00
|
|
|
self.add_knowledge(predicate,subject,object)
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_tell_verb1_operation(self, op, say):
|
2006-10-09 18:30:23 +00:00
|
|
|
"""Handle a sentence of the form 'Tell (me) ....'
|
|
|
|
|
|
|
|
|
|
Accept queries about what we know. Mostly this is for debugging
|
|
|
|
|
and for the time being it is useful to answer these queries no matter
|
|
|
|
|
who hasks."""
|
2011-09-02 16:04:07 +02:00
|
|
|
|
|
|
|
|
# Ignore messages addressed to others
|
|
|
|
|
if not self.is_talk_op_addressed_to_me_or_none(op):
|
|
|
|
|
return None
|
|
|
|
|
|
2005-01-09 01:16:16 +00:00
|
|
|
# Currently no checking for trus here.
|
|
|
|
|
# We are being liberal with interpretation of "subject" and "object"
|
|
|
|
|
subject=say[1].word
|
|
|
|
|
predicate=say[2].word
|
|
|
|
|
object=say[3].word
|
|
|
|
|
k=self.get_knowledge(predicate, object)
|
2005-11-02 02:06:38 +00:00
|
|
|
if k==None: pass
|
|
|
|
|
# return Operation('talk',Entity(say="I know nothing about the "+predicate+" of "+object))
|
2005-01-09 01:16:16 +00:00
|
|
|
else:
|
|
|
|
|
k_type = type(k)
|
|
|
|
|
if k_type==type(Location()):
|
|
|
|
|
dist = distance_to(self.location, k)
|
2005-01-09 21:24:57 +00:00
|
|
|
dist.z = 0
|
|
|
|
|
distmag = dist.mag()
|
|
|
|
|
if distmag < 8:
|
|
|
|
|
k = 'right here'
|
|
|
|
|
else:
|
|
|
|
|
# Currently this assumes dist is relative to TLVE
|
|
|
|
|
k='%f metres %s' % (distmag, vector_to_compass(dist))
|
2005-01-09 01:16:16 +00:00
|
|
|
elif k_type!=StringType:
|
|
|
|
|
k='difficult to explain'
|
2005-11-25 03:21:09 +00:00
|
|
|
elif predicate=='about':
|
2011-09-02 22:52:01 +02:00
|
|
|
return self.face_and_address(op.to, k)
|
|
|
|
|
return self.face_and_address(op.to, "The " + predicate + " of " +
|
|
|
|
|
object + " is " + k)
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_list_verb1_operation(self, op, say):
|
2007-09-06 22:17:57 +00:00
|
|
|
"""Handle a sentence of the form 'List (me) ....'
|
|
|
|
|
|
|
|
|
|
Accept queries about what we know. Mostly this is for debugging
|
|
|
|
|
and for the time being it is useful to answer these queries no matter
|
2011-06-26 22:11:02 +02:00
|
|
|
who asks.
|
|
|
|
|
Querying for "all knowledge" will list all knowledge.
|
|
|
|
|
"""
|
|
|
|
|
|
2011-09-02 16:04:07 +02:00
|
|
|
# Ignore messages addressed to others
|
|
|
|
|
if not self.is_talk_op_addressed_to_me_or_none(op):
|
|
|
|
|
return None
|
|
|
|
|
|
2007-09-06 22:17:57 +00:00
|
|
|
# Currently no checking for trus here.
|
|
|
|
|
# We are being liberal with interpretation of "subject" and "object"
|
|
|
|
|
subject=say[1].word
|
|
|
|
|
predicate=say[2].word
|
2011-06-26 22:11:02 +02:00
|
|
|
if predicate == 'all knowledge':
|
|
|
|
|
res = Oplist()
|
|
|
|
|
res = res + self.face(self.map.get(op.to))
|
|
|
|
|
for attr in dir(self.knowledge):
|
|
|
|
|
d=getattr(self.knowledge, attr)
|
|
|
|
|
if getattr(d, '__iter__', False):
|
|
|
|
|
for key in d:
|
|
|
|
|
#print attr + " of "+key+": " +str(d[key])
|
|
|
|
|
res = res + self.address(op.to, "The " + attr + " of " +
|
|
|
|
|
key + " is " + str(d[key]))
|
|
|
|
|
return res
|
|
|
|
|
else:
|
|
|
|
|
if not hasattr(self.knowledge, predicate):
|
|
|
|
|
return None
|
|
|
|
|
d=getattr(self.knowledge, predicate)
|
|
|
|
|
res = Oplist()
|
|
|
|
|
res = res + self.face(self.map.get(op.to))
|
|
|
|
|
for key in d:
|
|
|
|
|
res = res + self.address(op.to, "The " + predicate + " of " +
|
|
|
|
|
key + " is " + str(d[key]))
|
|
|
|
|
return res
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_own_verb1_operation(self, op, say):
|
2006-10-09 18:30:23 +00:00
|
|
|
"""Handle a sentence of the form ' own ...'
|
|
|
|
|
|
|
|
|
|
Sentences of this form from the admin inform us that we own an
|
|
|
|
|
entity. This is essential when an entity needs to be used as a
|
|
|
|
|
tool, or raw material."""
|
2007-11-23 16:06:52 +00:00
|
|
|
if not self.admin_sound(op):
|
|
|
|
|
return self.interlinguish_warning(op,say,"You are not admin")
|
2000-11-27 18:28:20 +00:00
|
|
|
## print self,"own:",say[1].word,say[2].word
|
|
|
|
|
subject=self.map.get_add(say[1].word)
|
|
|
|
|
## print "subject found:",subject
|
|
|
|
|
object=self.map.get_add(say[2].word)
|
|
|
|
|
## print "object found:",object
|
|
|
|
|
## if subject.id==self.id:
|
|
|
|
|
## foo
|
2000-12-11 13:06:12 +00:00
|
|
|
if subject.id==self.id:
|
2000-11-27 18:28:20 +00:00
|
|
|
self.add_thing(object)
|
2007-11-23 16:06:52 +00:00
|
|
|
def interlinguish_undefined_operation(self, op, say):
|
2000-11-27 18:28:20 +00:00
|
|
|
#CHEAT!: any way to handle these?
|
2007-11-23 16:06:52 +00:00
|
|
|
log.debug(2,str(self.id)+" interlinguish_undefined_operation:",op)
|
2000-11-27 18:28:20 +00:00
|
|
|
log.debug(2,str(say))
|
|
|
|
|
########## Sound operations
|
2007-11-23 16:06:52 +00:00
|
|
|
def sound_talk_operation(self, op):
|
2006-10-23 11:46:44 +00:00
|
|
|
"""Handle the sound of a talk operation from another character.
|
|
|
|
|
|
|
|
|
|
The spoken sentence comes in as a sentence string, which
|
|
|
|
|
is converted into a structure representation by the interlinguish
|
|
|
|
|
code. Embedded in the structure is the interlinguish string which
|
|
|
|
|
is then used to call methods and activate triggers, such as
|
|
|
|
|
dynamic goals."""
|
|
|
|
|
|
2000-11-27 18:28:20 +00:00
|
|
|
talk_entity=op[0]
|
2008-12-07 03:54:44 +00:00
|
|
|
if interlinguish.convert_english_to_interlinguish(self, talk_entity):
|
2000-11-27 18:28:20 +00:00
|
|
|
say=talk_entity.interlinguish
|
|
|
|
|
verb=interlinguish.get_verb(say)
|
2005-05-12 00:28:01 +00:00
|
|
|
operation_method=self.find_op_method(verb,"interlinguish_",
|
2000-11-27 18:28:20 +00:00
|
|
|
self.interlinguish_undefined_operation)
|
2008-12-07 03:54:44 +00:00
|
|
|
res = self.call_interlinguish_triggers(verb, "interlinguish_", op, say)
|
|
|
|
|
res2 = operation_method(op,say)
|
|
|
|
|
if res:
|
|
|
|
|
res += res2
|
|
|
|
|
else:
|
|
|
|
|
res = res2
|
|
|
|
|
return res
|
2000-11-27 18:28:20 +00:00
|
|
|
########## Other operations
|
|
|
|
|
def call_interlinguish_triggers(self, verb, prefix, op, say):
|
2006-10-23 11:46:44 +00:00
|
|
|
"""Call trigger goals that have registered a trigger string that
|
|
|
|
|
matches the current interlinguish string.
|
|
|
|
|
|
|
|
|
|
Given an interlinguish verb string, and a prefix, find any trigger
|
|
|
|
|
goals that should be activated by the combined trigger string, and
|
|
|
|
|
activate them."""
|
|
|
|
|
# FIXME Don't need this call to get_op_name_and_sub, as we don't use
|
|
|
|
|
# the result.
|
2000-11-27 18:28:20 +00:00
|
|
|
null_name, sub_op = self.get_op_name_and_sub(op)
|
|
|
|
|
event_name = prefix+verb
|
2009-05-21 16:34:27 +01:00
|
|
|
reply = Oplist()
|
2000-11-27 18:28:20 +00:00
|
|
|
for goal in self.trigger_goals.get(event_name,[]):
|
2008-12-07 03:54:44 +00:00
|
|
|
reply += goal.event(self, op, say)
|
2000-11-27 18:28:20 +00:00
|
|
|
return reply
|
2002-09-03 23:23:43 +00:00
|
|
|
def call_triggers_operation(self, op):
|
2000-11-27 18:28:20 +00:00
|
|
|
event_name, sub_op = self.get_op_name_and_sub(op)
|
2009-05-21 16:34:27 +01:00
|
|
|
reply = Oplist()
|
2000-11-27 18:28:20 +00:00
|
|
|
for goal in self.trigger_goals.get(event_name,[]):
|
2008-12-07 03:54:44 +00:00
|
|
|
reply += goal.event(self, op, sub_op)
|
2000-11-27 18:28:20 +00:00
|
|
|
return reply
|
|
|
|
|
########## Generic knowledge
|
2000-12-01 00:13:39 +00:00
|
|
|
def _reverse_knowledge(self):
|
2000-11-27 18:28:20 +00:00
|
|
|
"""normally location: tell where items reside
|
|
|
|
|
reverse location tells what resides in this spot"""
|
|
|
|
|
self.reverse_knowledge=Knowledge()
|
|
|
|
|
for (k,v) in self.knowledge.location.items():
|
|
|
|
|
if not self.reverse_knowledge.location.get(v):
|
|
|
|
|
self.reverse_knowledge.add("location",v,k)
|
|
|
|
|
def get_reverse_knowledge(self, what, key):
|
|
|
|
|
"""get certain reverse knowledge value
|
|
|
|
|
what: what kind of knowledge (location only so far)"""
|
|
|
|
|
d=getattr(self.reverse_knowledge,what)
|
|
|
|
|
return d.get(key)
|
|
|
|
|
def get_knowledge(self, what, key):
|
|
|
|
|
"""get certain knowledge value
|
|
|
|
|
what: what kind of knowledge (see Knowledge.py for list)"""
|
2004-10-11 01:04:27 +00:00
|
|
|
if not hasattr(self.knowledge, what):
|
|
|
|
|
return None
|
2000-11-27 18:28:20 +00:00
|
|
|
d=getattr(self.knowledge,what)
|
|
|
|
|
return d.get(key)
|
|
|
|
|
def add_knowledge(self,what,key,value):
|
|
|
|
|
"""add certain type of knowledge"""
|
|
|
|
|
self.knowledge.add(what,key,value)
|
|
|
|
|
#forward thought
|
|
|
|
|
if type(value)==InstanceType:
|
|
|
|
|
if what=="goal":
|
|
|
|
|
thought_value = value.info()
|
|
|
|
|
else:
|
|
|
|
|
thought_value = `value`
|
|
|
|
|
else:
|
|
|
|
|
thought_value = value
|
|
|
|
|
desc="%s knowledge about %s is %s" % (what,key,thought_value)
|
2013-04-13 23:38:07 +02:00
|
|
|
# ent = Entity(description=desc, what=what, key=key, value=thought_value)
|
|
|
|
|
# self.send(Operation("thought",ent))
|
2000-11-27 18:28:20 +00:00
|
|
|
if what=="location":
|
|
|
|
|
#and reverse too
|
|
|
|
|
self.reverse_knowledge.add("location",value,key)
|
2005-07-03 23:08:38 +00:00
|
|
|
def remove_knowledge(self,what,key):
|
|
|
|
|
"""remove certain type of knowledge"""
|
|
|
|
|
self.knowledge.remove(what,key)
|
2000-11-27 18:28:20 +00:00
|
|
|
########## Importance: Knowledge about how things compare in urgency, etc..
|
|
|
|
|
def add_importance(self, sub, cmp, obj):
|
|
|
|
|
"""add importance: both a>b and b<a"""
|
|
|
|
|
self.add_knowledge('importance',(sub,obj),cmp)
|
|
|
|
|
self.add_knowledge('importance',(obj,sub),reverse_cmp[cmp])
|
|
|
|
|
def cmp_goal_importance(self, g1, g2):
|
|
|
|
|
"""which of goals is more important?
|
|
|
|
|
also handle more generic ones:
|
|
|
|
|
for example if you are comparing breakfast to sleeping
|
|
|
|
|
it will note that having breakfast is a (isa) type of eating"""
|
|
|
|
|
try:
|
|
|
|
|
id1=g1.key[1]
|
|
|
|
|
id2=g2.key[1]
|
|
|
|
|
except AttributeError:
|
|
|
|
|
return 1
|
|
|
|
|
l1=ontology.get_isa(id1)
|
|
|
|
|
l2=ontology.get_isa(id2)
|
|
|
|
|
for s1 in l1:
|
|
|
|
|
for s2 in l2:
|
|
|
|
|
cmp=self.knowledge.importance.get((s1.id,s2.id))
|
|
|
|
|
if cmp:
|
|
|
|
|
return cmp=='>'
|
|
|
|
|
return 1
|
|
|
|
|
########## things we own
|
2007-11-28 20:51:43 +00:00
|
|
|
def thing_name(self,thing):
|
|
|
|
|
if hasattr(thing, 'name'):
|
|
|
|
|
return thing.name
|
|
|
|
|
return thing.type[0]
|
|
|
|
|
########## things we own
|
2006-10-23 11:46:44 +00:00
|
|
|
def add_thing(self,thing):
|
2000-11-27 18:28:20 +00:00
|
|
|
"""I own this thing"""
|
|
|
|
|
#CHEAT!: this feature not yet supported
|
|
|
|
|
## if not thing.location:
|
|
|
|
|
## thing.location=self.get_knowledge("location",thing.place)
|
|
|
|
|
log.debug(3,str(self)+" "+str(thing)+" before add_thing: "+str(self.things))
|
|
|
|
|
#thought about owing thing
|
2007-11-28 20:51:43 +00:00
|
|
|
name = self.thing_name(thing)
|
2008-09-17 15:14:08 +00:00
|
|
|
if not name:
|
|
|
|
|
self.pending_things.append(thing.id)
|
|
|
|
|
return
|
2013-04-21 11:30:48 +02:00
|
|
|
# desc="I own %s." % name
|
|
|
|
|
# what=thing.as_entity()
|
2013-04-13 23:38:07 +02:00
|
|
|
# ent = Entity(description=desc, what=what)
|
|
|
|
|
# self.send(Operation("thought",ent))
|
2004-11-18 01:51:40 +00:00
|
|
|
dictlist.add_value(self.things,name,thing)
|
2000-11-27 18:28:20 +00:00
|
|
|
log.debug(3,"\tafter: "+str(self.things))
|
|
|
|
|
def find_thing(self, thing):
|
|
|
|
|
if StringType==type(thing):
|
|
|
|
|
#return found list or empty list
|
|
|
|
|
return self.things.get(thing,[])
|
|
|
|
|
found=[]
|
2007-11-28 20:51:43 +00:00
|
|
|
for t in self.things.get(self.thing_name(thing),[]):
|
2000-11-27 18:28:20 +00:00
|
|
|
if t==thing: found.append(t)
|
|
|
|
|
return found
|
|
|
|
|
def remove_thing(self, thing):
|
|
|
|
|
"""I don't own this anymore (it may not exist)"""
|
|
|
|
|
dictlist.remove_value(self.things, thing)
|
|
|
|
|
########## goals
|
2015-05-12 21:12:11 +02:00
|
|
|
def add_goal(self, str_goal):
|
2000-11-27 18:28:20 +00:00
|
|
|
"""add goal..."""
|
2015-05-12 21:12:11 +02:00
|
|
|
try:
|
|
|
|
|
goal = self.create_goal(str_goal)
|
|
|
|
|
except BaseException as e:
|
|
|
|
|
print("Error when adding goal: " + str(e))
|
|
|
|
|
return
|
|
|
|
|
|
2013-09-09 20:04:00 +02:00
|
|
|
self.insert_goal(goal)
|
2015-05-12 21:12:11 +02:00
|
|
|
return goal
|
2013-09-09 20:04:00 +02:00
|
|
|
def insert_goal(self, goal, id=None):
|
|
|
|
|
if not id:
|
|
|
|
|
self.goal_id_counter = self.goal_id_counter + 1
|
|
|
|
|
id=str(self.goal_id_counter)
|
|
|
|
|
|
|
|
|
|
goal.id = id
|
2000-11-27 18:28:20 +00:00
|
|
|
if hasattr(goal,"trigger"):
|
|
|
|
|
dictlist.add_value(self.trigger_goals, goal.trigger(), goal)
|
|
|
|
|
return
|
|
|
|
|
for i in range(len(self.goals)-1,-1,-1):
|
|
|
|
|
if self.cmp_goal_importance(self.goals[i],goal):
|
|
|
|
|
self.goals.insert(i+1,goal)
|
|
|
|
|
return
|
|
|
|
|
self.goals.insert(0,goal)
|
2013-03-30 22:56:20 +01:00
|
|
|
|
2013-09-09 20:04:00 +02:00
|
|
|
def update_goal(self, goal, str_goal):
|
2015-05-12 21:12:11 +02:00
|
|
|
try:
|
|
|
|
|
new_goal = self.create_goal(goal.key, str_goal)
|
|
|
|
|
except BaseException as e:
|
|
|
|
|
print("Error when updating goal: " + str(e))
|
|
|
|
|
return
|
|
|
|
|
|
2013-09-09 20:04:00 +02:00
|
|
|
new_goal.id = goal.id
|
|
|
|
|
#We need to handle the case where a goal which had a trigger is replaced by one
|
|
|
|
|
#that hasn't, and the opposite
|
|
|
|
|
if hasattr(goal,"trigger"):
|
|
|
|
|
dictlist.remove_value(self.trigger_goals, goal)
|
|
|
|
|
self.insert_goal(new_goal, goal.id)
|
|
|
|
|
else:
|
|
|
|
|
if hasattr(new_goal,"trigger"):
|
|
|
|
|
self.goals.remove(goal)
|
|
|
|
|
self.insert_goal(new_goal, goal.id)
|
|
|
|
|
else:
|
|
|
|
|
index=self.goals.index(goal)
|
|
|
|
|
self.goals[index] = new_goal
|
2013-03-30 22:56:20 +01:00
|
|
|
|
|
|
|
|
|
2015-05-12 21:12:11 +02:00
|
|
|
def create_goal(self, str_goal):
|
2013-09-09 20:04:00 +02:00
|
|
|
#CHEAT!: remove eval (this and later)
|
|
|
|
|
goal=eval("mind.goals."+str_goal)
|
|
|
|
|
if const.debug_thinking:
|
|
|
|
|
goal.debug=1
|
|
|
|
|
goal.str=str_goal
|
|
|
|
|
return goal
|
|
|
|
|
def remove_goal(self, goal):
|
|
|
|
|
"""Removes a goal."""
|
|
|
|
|
if hasattr(goal,"trigger"):
|
|
|
|
|
dictlist.remove_value(self.trigger_goals, goal)
|
|
|
|
|
else:
|
|
|
|
|
self.goals.remove(goal)
|
2013-03-30 22:56:20 +01:00
|
|
|
|
2000-11-27 18:28:20 +00:00
|
|
|
def fulfill_goals(self,time):
|
|
|
|
|
"see if all goals are fulfilled: if not try to fulfill them"
|
|
|
|
|
for g in self.goals[:]:
|
|
|
|
|
if g.irrelevant:
|
|
|
|
|
self.goals.remove(g)
|
|
|
|
|
continue
|
2013-04-07 23:18:29 +02:00
|
|
|
#Don't process goals which have had three errors in them.
|
|
|
|
|
#The idea is to allow for some leeway in goal processing, but to punish repeat offenders.
|
|
|
|
|
if g.errors > 3:
|
|
|
|
|
continue
|
|
|
|
|
try:
|
|
|
|
|
res=g.check_goal(self,time)
|
|
|
|
|
if res: return res
|
|
|
|
|
except:
|
|
|
|
|
stacktrace=traceback.format_exc()
|
|
|
|
|
g.errors += 1
|
|
|
|
|
g.lastError=stacktrace
|
|
|
|
|
#If there's an error, print to the log, mark the goal, and continue with the next goal
|
2013-09-07 00:24:56 +02:00
|
|
|
#Some goals have a "str" attribute which represents the constructor; if so use that
|
|
|
|
|
if hasattr(g, "str"):
|
|
|
|
|
goalstring=g.str
|
|
|
|
|
else:
|
|
|
|
|
goalstring=g.__class__.__name__
|
2016-03-12 23:37:52 +01:00
|
|
|
if hasattr(self, "name"):
|
|
|
|
|
print "Error in NPC with id " + self.id + " of type " + str(self.type) + " and name '" + self.name + "' when checking goal " + goalstring + "\n" + stacktrace
|
|
|
|
|
else:
|
|
|
|
|
print "Error in NPC with id " + self.id + " of type " + str(self.type) + " when checking goal " + goalstring + "\n" + stacktrace
|
2013-04-07 23:18:29 +02:00
|
|
|
continue
|
2000-12-06 18:21:35 +00:00
|
|
|
# if res!=None: return res
|
2000-11-27 18:28:20 +00:00
|
|
|
def teach_children(self, child):
|
2009-05-21 16:34:27 +01:00
|
|
|
res=Oplist()
|
2000-11-27 18:28:20 +00:00
|
|
|
for k in self.knowledge.location.keys():
|
|
|
|
|
es=Entity(verb='know',subject=k,object=self.knowledge.location[k])
|
|
|
|
|
res.append(Operation('say',es,to=child))
|
|
|
|
|
for k in self.knowledge.place.keys():
|
|
|
|
|
es=Entity(verb='know',subject=k,object=self.knowledge.place[k])
|
|
|
|
|
res.append(Operation('say',es,to=child))
|
|
|
|
|
for g in self.goals:
|
|
|
|
|
es=Entity(verb='learn',subject=g.key,object=g.str)
|
|
|
|
|
res.append(Operation('say',es,to=child))
|
|
|
|
|
for im in self.knowledge.importance.keys():
|
|
|
|
|
cmp=self.knowledge.importance[im]
|
|
|
|
|
if cmp=='>':
|
|
|
|
|
s,i=il.importance(im[0],cmp,im[1])
|
|
|
|
|
es=Entity(say=s,interlinguish=i)
|
|
|
|
|
res.append(Operation('say',es,to=child))
|
|
|
|
|
return res
|
|
|
|
|
########## thinking (needs rewrite)
|
|
|
|
|
def think(self):
|
|
|
|
|
if const.debug_thinking:
|
2013-05-29 23:22:31 +02:00
|
|
|
log.thinking("think: "+str(self.id))
|
2000-11-27 18:28:20 +00:00
|
|
|
output=self.fulfill_goals(self.time)
|
2013-06-23 00:08:34 +02:00
|
|
|
# if output and const.debug_thinking:
|
|
|
|
|
# log.thinking(str(self)+" result at "+str(self.time)+": "+output[-1][0].description)
|
2000-11-27 18:28:20 +00:00
|
|
|
return output
|
|
|
|
|
########## communication: here send it locally
|
|
|
|
|
def send(self, op):
|
|
|
|
|
if not self.message_queue:
|
2009-05-21 16:34:27 +01:00
|
|
|
self.message_queue=Oplist(op)
|
2000-11-27 18:28:20 +00:00
|
|
|
else:
|
|
|
|
|
self.message_queue.append(op)
|
2004-12-17 02:00:19 +00:00
|
|
|
########## turn to face other entity
|
|
|
|
|
def face(self, other):
|
2004-12-19 01:46:23 +00:00
|
|
|
vector = distance_to(self.location, other.location)
|
|
|
|
|
vector.z = 0
|
2005-10-19 20:44:41 +00:00
|
|
|
if vector.square_mag() < 0.1:
|
|
|
|
|
return
|
2004-12-19 01:46:23 +00:00
|
|
|
vector = vector.unit_vector()
|
|
|
|
|
newloc = Location(self.location.parent)
|
|
|
|
|
newloc.orientation = Quaternion(Vector3D(1,0,0), vector)
|
|
|
|
|
return Operation("move", Entity(self.id, location=newloc))
|
2011-09-02 22:52:01 +02:00
|
|
|
def address(self, entity_id, message):
|
|
|
|
|
"""Creates a new Talk op which is addressed to an entity"""
|
|
|
|
|
return Operation('talk', Entity(say=message, address=[entity_id]))
|
|
|
|
|
def face_and_address(self, entity_id, message):
|
|
|
|
|
"""Utility method for generating ops for both letting the NPC face
|
2011-09-14 16:17:16 +01:00
|
|
|
as well as address another entity. In most cases this is what you
|
|
|
|
|
want to do when conversing."""
|
2011-09-02 22:52:01 +02:00
|
|
|
return self.address(entity_id, message) + \
|
2011-09-14 16:29:41 +01:00
|
|
|
self.face(self.map.get(entity_id))
|
2011-09-02 22:52:01 +02:00
|
|
|
|