cyphesis/rulesets/basic/scripts/mind/Goal.py
2018-12-20 14:51:25 +01:00

188 lines
7.1 KiB
Python

# This file is distributed under the terms of the GNU General Public license.
# Copyright (C) 1999 Aloril (See the file COPYING for details).
from types import *
from atlas import *
from common import log
import importlib
## \defgroup PythonGoals Goal Classes
## \brief Base class for all goals
## \ingroup PythonGoals
class Goal:
def __init__(self, desc="some goal", fulfilled=None, subgoals=None,
validity=None, time=None, debug=0):
if subgoals is None:
subgoals = []
self.desc = desc
# mind sets these:
# self.str
# self.key
if fulfilled:
self.fulfilled = fulfilled
else:
self.fulfilled = lambda me: False
# If no validity function is supplied the goal is always considered valid
if validity:
self.validity = validity
else:
self.validity = lambda me: True
# filter out any None subgoals
self.subgoals = [item for item in subgoals if item is not None]
self.time = time
self.debug = debug
self.vars = []
# keeps track of whether the goal is fulfilled or not
# this is mainly of use for inspection and diagnosis
self.is_fulfilled = False
# any subgoal/function/method can set this and
# it's checked at start of check_goal_rec
# and NPCMind.py fulfill_goals uses it too to remove goals from list
self.irrelevant = False
# Tracks the number of errors this goal (or any of its subgoals) has produced.
# This is used by NPC code to remove troublesome goals from the processing
self.errors = 0
# If an error has occurred, the description of the last one is stored here.
# This is mainly of use for debugging.
self.lastError = ""
self.lastProcessedGoals = None
def __repr__(self):
return self.info()
def info(self):
name = self.__class__.__name__
if name == "Goal":
return name + "(" + repr(self.desc) + ")"
var = ""
for v in self.vars:
if var: var = var + ","
var = var + repr(getattr(self, v))
return name + "(" + var + ")"
def is_valid(self, me):
"""Checks if this goal is valid. By this we mean whether the goal is possible to fulfill.
If no validity function was supplied at goal creation time the goal is always considered valid. """
return self.validity(me)
def check_goal(self, me, time):
"executes goal, see top of file"
if self.debug:
log.thinking("GOAL desc: " + self.str)
res, debugInfo = self.check_goal_rec(me, time, 0, "")
if len(debugInfo) != 0:
# Keep a copy of the debug info for the "report" method.
self.lastProcessedGoals = debugInfo
if res is not None:
info_ent = Entity(op=res, description=debugInfo)
return res + Operation("goal_info", info_ent)
def check_goal_rec(self, me, time, depth, debugInfo):
"""check (sub)goal recursively.
This is done by iterating over all subgoals, breaking if any subgoal returns an operation."""
res = None
if self.irrelevant: return res, debugInfo
# is it right time range?
if self.time and not time.is_now(self.time): return res, debugInfo
if self.debug:
log.thinking("\t" * depth + "GOAL: bef fulfilled: " + self.desc + " " + repr(self.fulfilled))
if self.fulfilled(me):
self.is_fulfilled = 1
if self.debug:
log.thinking("\t" * depth + "GOAL: is fulfilled: " + self.desc + " " + repr(self.fulfilled))
return res, debugInfo
else:
if self.debug:
log.thinking("\t" * depth + "GOAL: is not fulfilled: " + self.desc + " " + repr(self.fulfilled))
self.is_fulfilled = 0
debugInfo = debugInfo + "." + self.info()
# Iterate over all subgoals, but break if any goal returns an operation
for sg in self.subgoals:
if sg is None:
continue
if type(sg) == FunctionType or type(sg) == MethodType:
if self.debug:
log.thinking("\t" * depth + "GOAL: bef function: " + repr(sg) + " " + repr(res))
res = sg(me)
if self.debug:
log.thinking("\t" * depth + "GOAL: aft function: " + repr(sg) + " " + repr(res))
debugInfo = debugInfo + "." + sg.__name__ + "()"
if res is not None:
# If the function generated an op, stop iterating here and return
return res, debugInfo
else:
if self.debug:
log.thinking("\t" * depth + "GOAL: bef sg: " + sg.desc)
# If the subgoal is irrelevant, remove it
if sg.irrelevant:
self.subgoals.remove(sg)
continue
res, debugInfo = sg.check_goal_rec(me, time, depth + 1, debugInfo)
if self.debug:
log.thinking("\t" * depth + "GOAL: aft sg: " + sg.desc + ", Result: " + str(res))
# If the subgoal generated an op, stop iterating here and return
if res is not None:
return res, debugInfo
return res, debugInfo
def report(self):
"""provides extended information about the goal,
as well as all subgoals"""
name = self.__class__.__name__
report = {"name": name, "description": self.desc, "fulfilled": self.is_fulfilled}
if self.lastProcessedGoals:
report["lastProcessedGoals"] = self.lastProcessedGoals
subgoals = []
for sg in self.subgoals:
if type(sg) != FunctionType and type(sg) != MethodType and sg is not None:
subgoals.append(sg.report())
if len(subgoals) > 0:
report["subgoals"] = subgoals
if len(self.vars) > 0:
variables = {}
for v in self.vars:
variables[v] = str(getattr(self, v))
report["variables"] = variables
if self.errors > 0:
report["errors"] = self.errors
report["lastError"] = self.lastError
return report
def goal_create(goal_element):
if goal_element is None:
return None
if hasattr(goal_element, 'class'):
goal_class = goal_element['class']
splits = goal_class.split('.')
module_name = '.'.join(splits[0:-1])
class_name = splits[-1]
module = importlib.import_module(module_name)
class_ = getattr(module, class_name)
params = {}
if hasattr(goal_element, 'params'):
params = goal_element['params']
print('Creating an instance of {}'.format(goal_class))
try:
instance = class_(**params)
if instance:
return instance
else:
print('Could not create goal from data\n {}'.format(str(goal_element)))
except:
print('Error when creating goal from data\n {}'.format(str(goal_element)))
raise
return None