def query_object_properties(typeclass_key, object_key): """ Query all properties of the given object. Args: typeclass_key: (string) typeclass' key. object_key: (string) object' key. """ # Get fields. fields = [] fields.append({ "name": "level", "label": _("Level"), "help_text": _("Properties's level.") }) properties_info = TYPECLASS(typeclass_key).get_properties_info() for key, info in properties_info.items(): if info["mutable"]: continue fields.append({ "name": key, "label": info["name"], "help_text": info["desc"] }) if len(fields) == 1: # No custom properties. table = { "fields": [], "records": [], } return table # Get rows. levels = [] data = {} records = OBJECT_PROPERTIES.get_properties_all_levels(object_key) for record in records: if record.level not in levels: levels.append(record.level) data[record.level] = {"level": record.level} data[record.level][record.property] = record.value rows = [] for level in levels: line = [data[level].get(field["name"], "") for field in fields] rows.append(line) table = { "fields": fields, "records": rows, } return table
def set_typeclass(self, typeclass_key): """ Set object's typeclass. Args: typeclass_key: (string) Typeclass's key. """ typeclass_cls = TYPECLASS(typeclass_key) if not typeclass_cls: logger.log_errmsg("Can not get %s's typeclass: %s." % (self.get_data_key(), typeclass_key)) return if type(self) == typeclass_cls: # No change. return if not hasattr(self, 'swap_typeclass'): logger.log_errmsg("%s cannot have a type at all!" % self.get_data_key()) return # Set new typeclass. # It will call target typeclass's at_object_creation hook. # You should prevent at_object_creation rewrite current attributes. self.swap_typeclass(typeclass_cls, clean_attributes=False) if typeclass_cls.path != self.typeclass_path: logger.log_errmsg("%s's typeclass %s is wrong!" % (self.get_data_key(), typeclass_cls.path)) return
class MudderyWorldNPC(TYPECLASS("NPC")): """ The character not controlled by players. """ typeclass_key = "WORLD_NPC" typeclass_name = _("World NPC", "typeclasses") model_name = "world_npcs"
def get_object_record(obj_key): """ Query the object's record. Args: obj_key: (string) The key of the object. Returns: The object's data record. """ record = None model_name = TYPECLASS("OBJECT").model_name try: # Get record. model_obj = apps.get_model(settings.WORLD_DATA_APP, model_name) record = model_obj.objects.get(key=obj_key) except Exception as e: ostring = "Can not get record %s: %s." % (obj_key, e) print(ostring) print(traceback.print_exc()) if not record: ostring = "Can not get record %s." % obj_key print(ostring) print(traceback.print_exc()) return record
class MudderyReverseExit(TYPECLASS("EXIT")): """ This is the reverse side of the two way exit. """ typeclass_key = "REVERSE_EXIT" typeclass_name = _("Reverse Exit", "typeclasses") def after_data_loaded(self): """ Called after self.data_loaded(). """ self.set_name(getattr(self.dfield, "reverse_name", "")) # reverse location and destination if not self.location: self.set_location(getattr(self.dfield, "destination", "")) # set exit's destination self.set_obj_destination(getattr(self.dfield, "location", "")) self.condition = getattr(self.dfield, "condition", "") # set icon self.set_icon(getattr(self.dfield, "icon", "")) def reset_location(self): """ Set object's location to its default location. Returns: None """ if hasattr(self.dfield, "destination"): self.set_location(self.dfield.destination)
class MudderyCommonNPC(TYPECLASS("BASE_NPC")): """ The character not controlled by players. """ typeclass_key = "COMMON_NPC" typeclass_name = _("Common NPC", "typeclasses") model_name = "common_npcs"
def update_object_key(typeclass_key, old_key, new_key): """ Update an object's key in other tables. Args: typeclass: (string) object's typeclass. old_key: (string) object's old key. new_key: (string) object's new key """ # The object's key has changed. typeclass = TYPECLASS(typeclass_key) if issubclass(typeclass, TYPECLASS("AREA")): # Update relative room's location. model_name = TYPECLASS("ROOM").model_name if model_name: general_query_mapper.filter_records(model_name, location=old_key).update(location=new_key) elif issubclass(typeclass, TYPECLASS("ROOM")): # Update relative exit's location. model_name = TYPECLASS("EXIT").model_name if model_name: general_query_mapper.filter_records(model_name, location=old_key).update(location=new_key) general_query_mapper.filter_records(model_name, destination=old_key).update(destination=new_key) # Update relative world object's location. model_name = TYPECLASS("WORLD_OBJECT").model_name if model_name: general_query_mapper.filter_records(model_name, location=old_key).update(location=new_key) # Update relative world NPC's location. model_name = TYPECLASS("WORLD_NPC").model_name if model_name: general_query_mapper.filter_records(model_name, location=old_key).update(location=new_key)
class MudderyMonster(TYPECLASS("NON_PLAYER")): """ Default mob. Monsters are hostile to players, they can be attacked. """ typeclass_key = "MONSTER" def after_data_loaded(self): """ Init the character. """ super(MudderyMonster, self).after_data_loaded() # set level self.db.level = getattr(self.dfield, "level", 1) # Character can auto fight. self.auto_fight = True # set home self.home = self.location # load dialogues. self.load_dialogues() def load_dialogues(self): """ Load dialogues. """ dialogues = NPC_DIALOGUES.filter(self.get_data_key()) self.default_dialogues = [ dialogue.dialogue for dialogue in dialogues if dialogue.default ] self.dialogues = [ dialogue.dialogue for dialogue in dialogues if not dialogue.default ] def get_available_commands(self, caller): """ This returns a list of available commands. """ commands = [] if self.is_alive(): if self.dialogues or self.default_dialogues: # If the character have something to talk, add talk command. commands.append({ "name": _("Talk"), "cmd": "talk", "args": self.dbref }) commands.append({ "name": _("Attack"), "cmd": "attack", "args": self.dbref }) return commands
def query_typeclass_table(typeclass_key): """ Query a table of objects of the same typeclass. Args: typeclass_key: (string) typeclass's key. """ typeclass_cls = TYPECLASS(typeclass_key) if not typeclass_cls: raise MudderyError(ERR.no_table, "Can not find typeclass %s" % typeclass_key) # get all tables' name tables = typeclass_cls.get_models() if not tables: raise MudderyError(ERR.no_table, "Can not get tables of %s" % typeclass_key) # get all tables' fields # add the first table table_fields = query_fields(tables[0]) fields = [field for field in table_fields if field["name"] != "id"] # add other tables for table in tables[1:]: table_fields = query_fields(table) fields.extend([ field for field in table_fields if field["name"] != "id" and field["name"] != "key" ]) # get all tables' data records = general_query_mapper.get_all_from_tables(tables) rows = [] for record in records: line = [str(record[field["name"]]) for field in fields] rows.append(line) table = { "fields": fields, "records": rows, } return table
class MudderyRoom(TYPECLASS("OBJECT"), DefaultRoom): """ Rooms are like any Object, except their location is None (which is default). They also use basetype_setup() to add locks so they cannot be puppeted or picked up. (to change that, use at_object_creation instead) See examples/object.py for a list of properties and methods available on all Objects. """ typeclass_key = "ROOM" typeclass_name = _("Room", "typeclasses") model_name = "world_rooms" def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyRoom, self).at_object_creation() self.peaceful = False self.position = None self.background = None def after_data_loaded(self): """ Set data_info to the object. """ super(MudderyRoom, self).after_data_loaded() self.peaceful = getattr(self.dfield, "peaceful", False) self.position = None try: # set position position = getattr(self.dfield, "position", None) if position: self.position = ast.literal_eval(position) except Exception, e: logger.log_tracemsg("load position error: %s" % e) # get background self.background = None resource = getattr(self.dfield, "background", None) if resource: try: resource_info = IMAGE_RESOURCES.get(resource) self.background = { "resource": resource_info.resource, "width": resource_info.image_width, "height": resource_info.image_height } except Exception, e: logger.log_tracemsg("Load background %s error: %s" % (resource, e))
def query_object_form(base_typeclass, obj_typeclass, obj_key): """ Query all data of an object. Args: base_typeclass: (string) candidate typeclass group. obj_typeclass: (string, optional) object's typeclass. If it is empty, use the typeclass of the object or use base typeclass as object's typeclass. obj_key: (string) object's key. If it is empty, query an empty form. """ candidate_typeclasses = TYPECLASS_SET.get_group(base_typeclass) if not candidate_typeclasses: raise MudderyError(ERR.no_table, "Can not find typeclass: %s" % base_typeclass) if not obj_typeclass: if obj_key: # Get typeclass from the object's record table_name = TYPECLASS("OBJECT").model_name record = general_query_mapper.get_record_by_key( table_name, obj_key) obj_typeclass = record.typeclass else: # Or use the base typeclass obj_typeclass = base_typeclass typeclass = TYPECLASS_SET.get(obj_typeclass) if not typeclass: raise MudderyError(ERR.no_table, "Can not find typeclass: %s" % obj_typeclass) table_names = typeclass.get_models() forms = [] for table_name in table_names: if obj_key: object_form = query_form(table_name, key=obj_key) else: object_form = query_form(table_name) forms.append({"table": table_name, "fields": object_form}) # add typeclasses if len(forms) > 0: for field in forms[0]["fields"]: if field["name"] == "typeclass": # set typeclass to the new value field["value"] = obj_typeclass field["type"] = "Select" field["choices"] = [ (key, cls.typeclass_name + " (" + key + ")") for key, cls in candidate_typeclasses.items() ] break return forms
def save_object_form(tables, obj_typeclass, obj_key): """ Save all data of an object. Args: tables: (list) a list of table data. [{ "table": (string) table's name. "record": (string, optional) record's id. If it is empty, add a new record. }] obj_typeclass: (string) object's typeclass. obj_key: (string) current object's key. If it is empty or changed, query an empty form. """ if not tables: raise MudderyError(ERR.invalid_form, "Invalid form.", data="Empty form.") try: typeclass = TYPECLASS(obj_typeclass) except: raise MudderyError(ERR.invalid_form, "Invalid form.", data="No typeclass: %s" % obj_typeclass) # Get object's new key from the first form. new_obj = not (obj_key and obj_key == tables[0]["values"]["key"]) if new_obj: try: obj_key = tables[0]["values"]["key"] except KeyError: pass if not obj_key: # Generate a new key. try: # Get object table's last id. query = general_query_mapper.get_the_last_record( typeclass.model_name) if query: index = int(query.id) + 1 else: index = 1 except Exception, e: raise MudderyError(ERR.invalid_form, "Invalid form.", data="No typeclass model: %s" % obj_typeclass) obj_key = obj_typeclass + "_" + str(index) for table in tables: table["values"]["key"] = obj_key
class MudderySkillBook(TYPECLASS("COMMON_OBJECT")): """ This is a skill book. Players can use it to learn a new skill. """ typeclass_key = "SKILL_BOOK" typeclass_name = _("Skill Book", "typeclasses") model_name = "skill_books" def get_available_commands(self, caller): """ This returns a list of available commands. "args" must be a string without ' and ", usually it is self.dbref. """ commands = [] if self.db.number > 0: commands.append({ "name": _("Use"), "cmd": "use", "args": self.dbref }) if self.location and self.can_discard: commands.append({ "name": _("Discard"), "cmd": "discard", "args": self.dbref }) return commands def take_effect(self, user, number): """ Use this object. Args: user: (object) the object who uses this number: (int) the number of the object to use Returns: (result, number): result: (string) a description of the result number: (int) actually used number """ if not user: raise ValueError("User should not be None.") skill_key = getattr(self.dfield, "skill", None) if not skill_key: return _("No effect."), 0 if user.learn_skill(skill_key, False, False): return _("You learned skill."), 1 else: return _("No effect."), 0
def query_typeclass_table(typeclass_key): """ Query a table of objects of the same typeclass. Args: typeclass_key: (string) typeclass's key. """ typeclass_cls = TYPECLASS(typeclass_key) if not typeclass_cls: raise MudderyError(ERR.no_table, "Can not find typeclass %s" % typeclass_key) # get all tables' name tables = typeclass_cls.get_models() if not tables: raise MudderyError(ERR.no_table, "Can not get tables of %s" % typeclass_key) # get all tables' fields # add the first table table_fields = query_fields(tables[0]) fields = [field for field in table_fields if field["name"] != "id"] # add other tables for table in tables[1:]: table_fields = query_fields(table) fields.extend([field for field in table_fields if field["name"] != "id" and field["name"] != "key"]) # get all tables' data records = general_query_mapper.get_all_from_tables(tables) rows = [] for record in records: line = [str(record[field["name"]]) for field in fields] rows.append(line) table = { "fields": fields, "records": rows, } return table
class MudderyArea(TYPECLASS("OBJECT")): """ Areas are compose the whole map. Rooms are belongs to areas. """ typeclass_key = "AREA" typeclass_name = _("Area", "typeclasses") def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyArea, self).at_object_creation() self.background = None self.background_point = None self.corresp_map_pos = None def after_data_loaded(self): """ Set data_info to the object. """ super(MudderyArea, self).after_data_loaded() # get background self.background = None resource = getattr(self.dfield, "background", None) if resource: try: resource_info = IMAGE_RESOURCES.get(resource) self.background = { "resource": resource_info.resource, "width": resource_info.image_width, "height": resource_info.image_height } except Exception, e: logger.log_tracemsg("Load background %s error: %s" % (resource, e)) self.background_point = None background_point = getattr(self.dfield, "background_point", None) try: # set background point if background_point: self.background_point = ast.literal_eval(background_point) except Exception, e: logger.log_tracemsg("load background point '%s' error: %s" % (background_point, e))
class MudderyArea(TYPECLASS("OBJECT")): """ Areas are compose the whole map. Rooms are belongs to areas. """ typeclass_key = "AREA" typeclass_name = _("Area", "typeclasses") model_name = "world_areas" def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyArea, self).at_object_creation() self.background = None def after_data_loaded(self): """ Set data_info to the object. """ super(MudderyArea, self).after_data_loaded() # get background self.background = None resource = getattr(self.system, "background", None) if resource: try: resource_info = IMAGE_RESOURCES.get(resource) self.background = { "resource": resource_info.resource, "width": resource_info.image_width, "height": resource_info.image_height } except Exception as e: logger.log_tracemsg("Load background %s error: %s" % (resource, e)) def get_appearance(self, caller): """ This is a convenient hook for a 'look' command to call. """ info = super(MudderyArea, self).get_appearance(caller) # add background info["background"] = getattr(self, "background", None) return info
class MudderyObjectCreator(TYPECLASS("WORLD_OBJECT")): """ This object loads attributes from world data on init automatically. """ typeclass_key = "WORLD_OBJECT_CREATOR" typeclass_name = _("Object Creator", "typeclasses") model_name = "object_creators" # initialize loot handler in a lazy fashion @lazy_property def loot_handler(self): return LootHandler(self, CREATOR_LOOT_LIST.filter(self.get_data_key())) def after_data_loaded(self): """ Set data_info to the object." """ super(MudderyObjectCreator, self).after_data_loaded() # Load creator info. self.loot_verb = getattr(self.dfield, "loot_verb", None) if not self.loot_verb: self.loot_verb = _("Loot") self.loot_condition = getattr(self.dfield, "loot_condition", None) def get_available_commands(self, caller): """ This returns a list of available commands. "args" must be a string without ' and ", usually it is self.dbref. """ if not STATEMENT_HANDLER.match_condition(self.loot_condition, caller, self): return [] commands = [{ "name": self.loot_verb, "cmd": "loot", "args": self.dbref }] return commands def loot(self, caller): """ Loot objects. """ self.loot_handler.loot(caller)
class MudderyWorldNPC(TYPECLASS("BASE_NPC")): """ The character not controlled by players. """ typeclass_key = "WORLD_NPC" typeclass_name = _("World NPC", "typeclasses") model_name = "world_npcs" def after_data_loaded(self): """ Init the character. """ super(MudderyWorldNPC, self).after_data_loaded() # if it is dead, reborn at init. if not self.is_alive(): if not self.is_temp and self.reborn_time > 0: self.reborn()
def delete_object(obj_key, base_typeclass=None): """ Delete an object from all tables under the base typeclass. """ if not base_typeclass: table_name = TYPECLASS("OBJECT").model_name record = general_query_mapper.get_record_by_key(table_name, obj_key) base_typeclass = record.typeclass typeclasses = TYPECLASS_SET.get_group(base_typeclass) tables = set() for key, value in typeclasses.items(): tables.update(value.get_models()) with transaction.atomic(): for table in tables: try: general_query_mapper.delete_record_by_key(table, obj_key) except ObjectDoesNotExist: pass
def attack_target(self, target, desc=""): """ Attack a target. Args: target: (object) the target object. desc: (string) string to describe this attack Returns: (boolean) attack begins """ if self.is_in_combat(): # already in battle logger.log_errmsg("%s is already in battle." % self.dbref) return False # search target if not target: logger.log_errmsg("Can not find the target.") return False if not target.is_typeclass(TYPECLASS( settings.GENERAL_CHARACTER_TYPECLASS_KEY), exact=False): # Target is not a character. logger.log_errmsg("Can not attack the target %s." % target.dbref) return False if target.is_in_combat(): # obj is already in battle logger.log_errmsg("%s is already in battle." % target.dbref) return False # create a new combat handler chandler = create_script(settings.NORMAL_COMBAT_HANDLER) # set combat team and desc chandler.set_combat({1: [target], 2: [self]}, desc, 0) return True
def match_condition(self, quest_key): """ Check if the quest matches its condition. Args: quest_key: (string) quest's key Returns: (boolean) result """ # Get quest's record. model_name = TYPECLASS("QUEST").model_name if not model_name: return False model_quest = apps.get_model(settings.WORLD_DATA_APP, model_name) try: record = model_quest.objects.get(key=quest_key) return STATEMENT_HANDLER.match_condition(record.condition, self.owner, None) except Exception, e: logger.log_errmsg("Can't get quest %s's condition: %s" % (quest_key, e))
class MudderyExit(TYPECLASS("OBJECT"), DefaultExit): """ Exits are connectors between rooms. Exits are normal Objects except they defines the `destination` property. It also does work in the following methods: basetype_setup() - sets default exit locks (to change, use `at_object_creation` instead). at_cmdset_get(**kwargs) - this is called when the cmdset is accessed and should rebuild the Exit cmdset along with a command matching the name of the Exit object. Conventionally, a kwarg `force_init` should force a rebuild of the cmdset, this is triggered by the `@alias` command when aliases are changed. at_failed_traverse() - gives a default error message ("You cannot go there") if exit traversal fails and an attribute `err_traverse` is not defined. Relevant hooks to overload (compared to other types of Objects): at_before_traverse(traveller) - called just before traversing. at_after_traverse(traveller, source_loc) - called just after traversing. at_failed_traverse(traveller) - called if traversal failed for some reason. Will not be called if the attribute `err_traverse` is defined, in which case that will simply be echoed. """ typeclass_key = "EXIT" typeclass_name = _("Exit", "typeclasses") model_name = "world_exits" def after_data_loaded(self): """ Load exit data. Returns: None """ super(MudderyExit, self).after_data_loaded() # set exit's destination self.set_obj_destination(getattr(self.system, "destination", None)) # set action verb self.verb = getattr(self.system, "verb", _("GOTO")) def at_before_traverse(self, traversing_object): """ Called just before an object uses this object to traverse to another object (i.e. this object is a type of Exit) Args: traversing_object (Object): The object traversing us. Notes: The target destination should normally be available as `self.destination`. If this method returns False/None, the traverse is cancelled before it is even started. """ # trigger event if traversing_object.has_account: return self.event.at_character_traverse(traversing_object) return True def at_failed_traverse(self, traversing_object, **kwargs): """ Overloads the default hook to implement a simple default error message. Args: traversing_object (Object): The object that failed traversing us. Notes: Using the default exits, this hook will not be called if an Attribute `err_traverse` is defined - this will in that case be read for an error string instead. """ traversing_object.msg({"alert": "You cannot go there."}) @classmethod def get_event_trigger_types(cls): """ Get an object's available event triggers. """ return [defines.EVENT_TRIGGER_TRAVERSE] def get_name(self): """ Get exit's name. """ if self.name: return self.name elif self.destination: return self.destination.get_name() else: return "" def get_available_commands(self, caller): """ This returns a list of available commands. "args" must be a string without ' and ", usually it is self.dbref. """ commands = [{"name": self.verb, "cmd": "goto", "args": self.dbref}] return commands
class MudderySkill(TYPECLASS("OBJECT")): """ A skill of the character. """ typeclass_key = "SKILL" msg_escape = re.compile(r'%[%|n|c|t]') @staticmethod def escape_fun(word): """ Change escapes to target words. """ escape_word = word.group() char = escape_word[1] if char == "%": return char else: return "%(" + char + ")s" def at_object_creation(self): """ Set default values. Returns: None """ super(MudderySkill, self).at_object_creation() # set status if not self.attributes.has("owner"): self.db.owner = None if not self.attributes.has("cd_finish_time"): self.db.cd_finish_time = 0 if not self.attributes.has("is_default"): self.db.is_default = False def set_default(self, is_default): """ Set this skill as the character's default skill. When skills in table default_skills changes, character's relative skills will change too. Args: is_default: (boolean) if the is default or not. """ self.db.is_default = is_default def is_default(self): """ Check if this skill is the character's default skill. Returns: (boolean) is default or not """ return self.db.is_default def after_data_loaded(self): """ Set data_info to the object. Returns: None """ super(MudderySkill, self).after_data_loaded() # set data self.function = getattr(self.dfield, "function", "") self.cd = getattr(self.dfield, "cd", 0) self.passive = getattr(self.dfield, "passive", False) self.main_type = getattr(self.dfield, "main_type", "") self.sub_type = getattr(self.dfield, "sub_type", "") message_model = getattr(self.dfield, "message", "") self.message_model = self.msg_escape.sub(self.escape_fun, message_model) def get_available_commands(self, caller): """ This returns a list of available commands. Args: caller: (object) command's caller Returns: commands: (list) a list of available commands """ if self.passive: return commands = [{ "name": _("Cast"), "cmd": "castskill", "args": self.get_data_key() }] return commands def set_owner(self, owner): """ Set the owner of the skill. Args: owner: (object) skill's owner Returns: None """ self.db.owner = owner if not self.passive: # Set skill cd. Add gcd to new the skill. gcd = GAME_SETTINGS.get("global_cd") if gcd > 0: self.db.cd_finish_time = time.time() + gcd def cast_skill(self, target, passive): """ Cast this skill. Args: target: (object) skill's target. passive: (boolean) cast a passive skill. Returns: (result, cd): result: (dict) skill's result cd: (dict) skill's cd """ owner = self.db.owner message = {} not_available = self.check_available(passive) if not_available: message = {"cast": not_available} else: results = self.do_skill(target) if not passive: # set message message = { "caller": owner.dbref, "skill": self.get_data_key(), "cast": self.cast_message(target) } if target: message["target"] = target.dbref if results: message["result"] = " ".join(results) # set status status = {} if owner.is_in_combat(): for char in owner.ndb.combat_handler.get_all_characters(): status[char.dbref] = char.get_combat_status() elif owner.location: status[owner.dbref] = owner.get_combat_status() message["status"] = status if not passive and message: # send message if owner.is_in_combat(): owner.ndb.combat_handler.msg_all({"skill_cast": message}) elif owner.location: owner.location.msg_contents({"skill_cast": message}) return True def do_skill(self, target): """ Do this skill. """ # set cd if not self.passive: # set cd time_now = time.time() if self.cd > 0: self.db.cd_finish_time = time_now + self.cd # call skill function return STATEMENT_HANDLER.do_skill(self.function, self.db.owner, target) def check_available(self, passive): """ Check this skill. Args: passive: (boolean) cast a passive skill. Returns: message: (string) If the skill is not available, returns a string of reason. If the skill is available, return "". """ if not passive and self.passive: return _("{c%s{n is a passive skill!") % self.get_name() if self.is_cooling_down(): return _("{c%s{n is not ready yet!") % self.get_name() return "" def is_available(self, passive): """ If this skill is available. Args: passive: (boolean) cast a passive skill. Returns: (boolean) available or not. """ if not passive and self.passive: return False if self.is_cooling_down(): return False return True def is_cooling_down(self): """ If this skill is cooling down. """ if self.cd > 0: if self.db.cd_finish_time: if time.time() < self.db.cd_finish_time: return True return False def get_remain_cd(self): """ Get skill's CD. Returns: (float) Remain CD in seconds. """ remain_cd = self.db.cd_finish_time - time.time() if remain_cd < 0: remain_cd = 0 return remain_cd def cast_message(self, target): """ Create skill's result message. """ caller_name = "" target_name = "" message = "" if self.db.owner: caller_name = self.db.owner.get_name() if target: target_name = target.get_name() if self.message_model: values = {"n": self.name, "c": caller_name, "t": target_name} message = self.message_model % values return message def get_appearance(self, caller): """ This is a convenient hook for a 'look' command to call. """ info = super(MudderySkill, self).get_appearance(caller) info["passive"] = self.passive info["cd_remain"] = self.get_remain_cd() return info
class MudderyBaseNPC(TYPECLASS("CHARACTER")): """ The character not controlled by players. """ typeclass_key = "BASE_NPC" typeclass_name = _("Base None Player Character", "typeclasses") model_name = "base_npcs" def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyBaseNPC, self).at_object_creation() # NPC's shop if not self.attributes.has("shops"): self.db.shops = {} def after_data_loaded(self): """ Init the character. """ super(MudderyBaseNPC, self).after_data_loaded() # Character can auto fight. self.auto_fight = True # set home self.home = self.location # load dialogues. self.load_dialogues() # load shops self.load_shops() def load_dialogues(self): """ Load dialogues. """ dialogues = NPC_DIALOGUES.filter(self.get_data_key()) self.default_dialogues = [ dialogue.dialogue for dialogue in dialogues if dialogue.default ] self.dialogues = [ dialogue.dialogue for dialogue in dialogues if not dialogue.default ] def load_shops(self): """ Load character's shop. """ # shops records shop_records = NPC_SHOPS.filter(self.get_data_key()) shop_keys = set([record.shop for record in shop_records]) # remove old shops for shop_key in self.db.shops: if shop_key not in shop_keys: # remove this shop self.db.shops[shop_key].delete() del self.db.shops[shop_key] # add new shop for shop_record in shop_records: shop_key = shop_record.shop if shop_key not in self.db.shops: # Create shop object. shop_obj = build_object(shop_key) if not shop_obj: logger.log_errmsg("Can't create shop: %s" % shop_key) continue self.db.shops[shop_key] = shop_obj shop_obj.set_owner(self) def get_available_commands(self, caller): """ This returns a list of available commands. """ commands = [] if self.is_alive(): if self.dialogues or self.default_dialogues: # If the character have something to talk, add talk command. commands.append({ "name": _("Talk"), "cmd": "talk", "args": self.dbref }) # Add shops. for shop_obj in self.db.shops.values(): if not shop_obj.is_visible(caller): continue verb = shop_obj.verb if not verb: verb = shop_obj.get_name() commands.append({ "name": verb, "cmd": "shopping", "args": shop_obj.dbref }) if self.friendly <= 0: commands.append({ "name": _("Attack"), "cmd": "attack", "args": self.dbref }) return commands def have_quest(self, caller): """ If the npc can complete or provide quests. Returns (can_provide_quest, can_complete_quest). """ return DIALOGUE_HANDLER.have_quest(caller, self) def leave_combat(self): """ Leave the current combat. """ status = None opponents = None if self.ndb.combat_handler: result = self.ndb.combat_handler.get_combat_result(self) if result: status, opponents = result if not self.is_temp: if status == defines.COMBAT_LOSE: self.die(opponents) super(MudderyBaseNPC, self).leave_combat() if not self.is_temp: if status != defines.COMBAT_LOSE: self.recover()
class MudderyCharacter(TYPECLASS("OBJECT"), DefaultCharacter): """ The Character defaults to implementing some of its hook methods with the following standard functionality: at_basetype_setup - always assigns the DefaultCmdSet to this object type (important!)sets locks so character cannot be picked up and its commands only be called by itself, not anyone else. (to change things, use at_object_creation() instead) at_after_move - launches the "look" command at_post_puppet(player) - when Player disconnects from the Character, we store the current location, so the "unconnected" character object does not need to stay on grid but can be given a None-location while offline. at_pre_puppet - just before Player re-connects, retrieves the character's old location and puts it back on the grid with a "charname has connected" message echoed to the room """ typeclass_key = "CHARACTER" typeclass_name = _("Character", "typeclasses") model_name = "characters" # initialize loot handler in a lazy fashion @lazy_property def loot_handler(self): return LootHandler(self, CHARACTER_LOOT_LIST.filter(self.get_data_key())) @lazy_property def body_properties_handler(self): return DataFieldHandler(self) # @property body stores character's body properties before using equipments and skills. def __body_get(self): """ A non-attr_obj store (ndb: NonDataBase). Everything stored to this is guaranteed to be cleared when a server is shutdown. Syntax is same as for the _get_db_holder() method and property, e.g. obj.ndb.attr = value etc. """ try: return self._body_holder except AttributeError: self._body_holder = DbHolder( self, "body_properties", manager_name='body_properties_handler') return self._body_holder # @body.setter def __body_set(self, value): "Stop accidentally replacing the ndb object" string = "Cannot assign directly to ndb object! " string += "Use self.body.name=value instead." raise Exception(string) # @body.deleter def __body_del(self): "Stop accidental deletion." raise Exception("Cannot delete the body object!") body = property(__body_get, __body_set, __body_del) def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyCharacter, self).at_object_creation() # set default values if not self.attributes.has("team"): self.db.team = 0 # init equipments if not self.attributes.has("equipments"): self.db.equipments = {} if not self.attributes.has("position_names"): self.db.position_names = {} self.reset_equip_positions() if not self.attributes.has("skills"): self.db.skills = {} # set quests if not self.attributes.has("finished_quests"): self.db.finished_quests = set() if not self.attributes.has("current_quests"): self.db.current_quests = {} # set closed events if not self.attributes.has("closed_events"): self.db.closed_events = set() # skill's gcd self.skill_gcd = GAME_SETTINGS.get("global_cd") self.auto_cast_skill_cd = GAME_SETTINGS.get("auto_cast_skill_cd") self.gcd_finish_time = 0 # loop for auto cast skills self.auto_cast_loop = None self.target = None self.reborn_time = 0 # A temporary character will be deleted after the combat finished. self.is_temp = False def at_object_delete(self): """ Called just before the database object is permanently delete()d from the database. If this method returns False, deletion is aborted. All skills, contents will be removed too. """ result = super(MudderyCharacter, self).at_object_delete() if not result: return result # leave combat if self.ndb.combat_handler: self.ndb.combat_handler.remove_character(self) # stop auto casting self.stop_auto_combat_skill() # delete all skills for skill in self.db.skills.values(): skill.delete() # delete all contents for content in self.contents: content.delete() return True def load_custom_properties(self, level): """ Load body properties from db. Body properties do no include mutable properties. """ # Get object level. if level is None: level = self.db.level # Load values from db. values = {} for record in OBJECT_PROPERTIES.get_properties(self.get_data_key(), level): key = record.property serializable_value = record.value if serializable_value == "": value = None else: try: value = ast.literal_eval(serializable_value) except (SyntaxError, ValueError) as e: # treat as a raw string value = serializable_value values[key] = value # Set body values. for key, info in self.get_properties_info().items(): if not info["mutable"]: self.custom_properties_handler.add(key, values.get(key, None)) self.body_properties_handler.add(key, values.get(key, None)) # Set default mutable custom properties. self.set_default_custom_properties() def set_default_custom_properties(self): """ Set default mutable custom properties. """ for key, info in self.get_properties_info().items(): if info["mutable"]: # Set default mutable properties to prop. if not self.custom_properties_handler.has(key): self.custom_properties_handler.add(key, "") def after_data_loaded(self): """ Init the character. """ super(MudderyCharacter, self).after_data_loaded() # get level if not self.db.level: self.db.level = getattr(self.system, "level", 1) # friendly self.friendly = getattr(self.system, "friendly", 0) # skill's ai ai_choose_skill_class = class_from_module(settings.AI_CHOOSE_SKILL) self.ai_choose_skill = ai_choose_skill_class() # skill's gcd self.skill_gcd = GAME_SETTINGS.get("global_cd") self.auto_cast_skill_cd = GAME_SETTINGS.get("auto_cast_skill_cd") self.gcd_finish_time = 0 # loop for auto cast skills self.auto_cast_loop = None # clear target self.target = None # set reborn time self.reborn_time = getattr(self.system, "reborn_time", 0) # A temporary character will be deleted after the combat finished. self.is_temp = False # update equipment positions self.reset_equip_positions() # load default skills self.load_default_skills() # load default objects self.load_default_objects() # refresh the character's properties. self.refresh_properties() def set_level(self, level): """ Set object's level. Args: level: object's new level Returns: None """ super(MudderyCharacter, self).set_level(level) self.refresh_properties() def reset_equip_positions(self): """ Reset equipment's position data. Returns: None """ positions = [] self.db.position_names = {} # reset equipment's position for record in CM.EQUIPMENT_POSITIONS.all(): positions.append(record.key) self.db.position_names[record.key] = record.name for position in self.db.equipments: if position not in positions: del self.db.equipments[position] for position in positions: if position not in self.db.equipments: self.db.equipments[position] = None # reset equipments status equipped = set() equipments = self.db.equipments for position in equipments: if equipments[position]: equipped.add(equipments[position]) for content in self.contents: if content.dbref in equipped: content.equipped = True def refresh_properties(self): """ Refresh character's final properties. """ # Load body properties. for key, value in self.body_properties_handler.all(True): self.custom_properties_handler.add(key, value) # load equips self.wear_equipments() # load passive skills self.cast_passive_skills() @classmethod def get_event_trigger_types(cls): """ Get an object's available event triggers. """ return [defines.EVENT_TRIGGER_KILL, defines.EVENT_TRIGGER_DIE] def close_event(self, event_key): """ If an event is closed, it will never be triggered. Args: event_key: (string) event's key """ self.db.closed_events.add(event_key) def is_event_closed(self, event_key): """ Return True If this event is closed. Args: event_key: (string) event's key """ return event_key in self.db.closed_events def change_properties(self, increments): """ Change values of specified properties. Args: increments: (dict) values to change. Return: (dict) values that actually changed. """ changes = {} properties_info = self.get_properties_info() for key, increment in increments.items(): changes[key] = 0 if not self.custom_properties_handler.has(key): continue origin_value = getattr(self.prop, key) # check limits max_key = "max_" + key if self.custom_properties_handler.has(max_key): max_value = getattr(self.prop, max_key) if origin_value + increment > max_value: increment = max_value - origin_value # Default minimum value is 0. min_value = 0 min_key = "min_" + key if self.custom_properties_handler.has(min_key): min_value = getattr(self.prop, min_key) if origin_value + increment < min_value: increment = min_value - origin_value # Set the value. if increment != 0: value = origin_value + increment self.custom_properties_handler.add(key, value) changes[key] = increment return changes def set_properties(self, values): """ Set values of specified properties. Args: values: (dict) values to set. Return: (dict) values that actually set. """ actual = {} properties_info = self.get_properties_info() for key, value in values.items(): actual[key] = 0 if not self.custom_properties_handler.has(key): continue # check limits max_key = "max_" + key if self.custom_properties_handler.has(max_key): max_value = getattr(self.prop, max_key) if value > max_value: value = max_value # Default minimum value is 0. min_key = "min_" + key if self.custom_properties_handler.has(min_key): min_value = getattr(self.prop, min_key) if value < min_value: value = min_value # Set the value. setattr(self.prop, key, value) actual[key] = value return actual def get_combat_status(self): """ Get character status used in combats. """ return {} def search_inventory(self, obj_key): """ Search specified object in the inventory. """ result = [ item for item in self.contents if item.get_data_key() == obj_key ] return result def set_equips(self): """ Load equipments data. """ # set equipments status equipped = set() equipments = self.db.equipments for position in equipments: if equipments[position]: equipped.add(equipments[position]) for content in self.contents: if content.dbref in equipped: content.equipped = True # set character's attributes self.refresh_properties() def wear_equipments(self): """ Add equipment's attributes to the character """ # find equipments equipped = set( [equip_id for equip_id in self.db.equipments.values() if equip_id]) # add equipment's attributes for content in self.contents: if content.dbref in equipped: content.equip_to(self) def load_default_skills(self): """ Load character's default skills. """ # default skills skill_records = DEFAULT_SKILLS.filter(self.get_data_key()) default_skill_ids = set([record.skill for record in skill_records]) # remove old default skills for key, skill in self.db.skills.items(): if not skill: del self.db.skills[key] elif skill.is_default() and key not in default_skill_ids: # remove this skill skill.delete() del self.db.skills[key] # add new default skills for skill_record in skill_records: if skill_record.skill not in self.db.skills: self.learn_skill(skill_record.skill, True, True) def load_default_objects(self): """ Load character's default objects. """ pass def at_after_move(self, source_location, **kwargs): """ Called after move has completed, regardless of quiet mode or not. Allows changes to the object due to the location it is now in. Args: source_location : (Object) Where we came from. This may be `None`. """ pass ######################################## # # Skill methods. # ######################################## def learn_skill(self, skill_key, is_default, silent): """ Learn a new skill. Args: skill_key: (string) skill's key is_default: (boolean) if it is a default skill silent: (boolean) do not show messages to the player Returns: (boolean) learned skill """ if skill_key in self.db.skills: self.msg({"msg": _("You have already learned this skill.")}) return False # Create skill object. skill_obj = build_object(skill_key) if not skill_obj: self.msg({"msg": _("Can not learn this skill.")}) return False # set default if is_default: skill_obj.set_default(is_default) # Store new skill. skill_obj.set_owner(self) self.db.skills[skill_key] = skill_obj # If it is a passive skill, player's status may change. if skill_obj.passive: self.refresh_properties() # Notify the player if not silent and self.has_account: self.show_status() self.show_skills() self.msg( {"msg": _("You learned skill {c%s{n.") % skill_obj.get_name()}) return True def cast_skill(self, skill_key, target): """ Cast a skill. Args: skill_key: (string) skill's key. target: (object) skill's target. """ time_now = time.time() if time_now < self.gcd_finish_time: # In GCD. self.msg({"skill_cast": {"cast": _("Global cooling down!")}}) return if skill_key not in self.db.skills: self.msg( {"skill_cast": { "cast": _("You do not have this skill.") }}) return skill = self.db.skills[skill_key] if not skill.cast_skill(target, passive=False): return if self.skill_gcd > 0: # set GCD self.gcd_finish_time = time_now + self.skill_gcd # send CD to the player cd = { "skill": skill_key, # skill's key "cd": skill.cd, # skill's cd "gcd": self.skill_gcd } self.msg({"skill_cd": cd}) return def auto_cast_skill(self): """ Cast a new skill automatically. """ if not self.is_alive(): return if not self.is_in_combat(): # combat is finished, stop ticker self.stop_auto_combat_skill() return # Choose a skill and the skill's target. result = self.ai_choose_skill.choose(self) if result: skill, target = result self.ndb.combat_handler.prepare_skill(skill, self, target) def cast_passive_skills(self): """ Cast all passive skills. """ for skill in self.db.skills.values(): if skill.passive: skill.cast_skill(self, passive=True) def start_auto_combat_skill(self): """ Start auto cast skill. """ if self.auto_cast_loop and self.auto_cast_loop.running: return # Cast a skill immediately # self.auto_cast_skill() # Set timer of auto cast. self.auto_cast_loop = task.LoopingCall(self.auto_cast_skill) self.auto_cast_loop.start(self.auto_cast_skill_cd) def stop_auto_combat_skill(self): """ Stop auto cast skill. """ if hasattr(self, "auto_cast_loop" ) and self.auto_cast_loop and self.auto_cast_loop.running: self.auto_cast_loop.stop() ######################################## # # Attack a target. # ######################################## def set_target(self, target): """ Set character's target. Args: target: (object) character's target Returns: None """ self.target = target def clear_target(self): """ Clear character's target. """ self.target = None def attack_target(self, target, desc=""): """ Attack a target. Args: target: (object) the target object. desc: (string) string to describe this attack Returns: (boolean) attack begins """ if self.is_in_combat(): # already in battle logger.log_errmsg("%s is already in battle." % self.dbref) return False # search target if not target: logger.log_errmsg("Can not find the target.") return False if not target.is_typeclass(TYPECLASS( settings.GENERAL_CHARACTER_TYPECLASS_KEY), exact=False): # Target is not a character. logger.log_errmsg("Can not attack the target %s." % target.dbref) return False if target.is_in_combat(): # obj is already in battle logger.log_errmsg("%s is already in battle." % target.dbref) return False # create a new combat handler chandler = create_script(settings.NORMAL_COMBAT_HANDLER) # set combat team and desc chandler.set_combat({1: [target], 2: [self]}, desc, 0) return True def attack_current_target(self, desc=""): """ Attack current target. Args: desc: (string) string to describe this attack Returns: None """ self.attack_target(self.target, desc) def attack_target_dbref(self, target_dbref, desc=""): """ Attack a target by dbref. Args: target_dbref: (string) the dbref of the target. desc: (string) string to describe this attack Returns: None """ target = self.search_dbref(target_dbref) self.attack_target(target, desc) def attack_temp_current_target(self, desc=""): """ Attack current target's temporary clone object. Args: desc: (string) string to describe this attack Returns: None """ self.attack_temp_target(self.target.get_data_key(), self.target.db.level, desc) def attack_temp_target(self, target_key, target_level=0, desc=""): """ Attack a temporary clone of a target. This creates a new character object for attack. The origin target will not be affected. Args: target_key: (string) the info key of the target object. target_level: (int) target object's level desc: (string) string to describe this attack Returns: (boolean) fight begins """ if target_level == 0: # Find the target and get its level. obj = search_obj_data_key(target_key) if obj: obj = obj[0] target_level = obj.db.level # Create a target. target = build_object(target_key, target_level, reset_location=False) if not target: logger.log_errmsg("Can not create the target %s." % target_key) return False target.is_temp = True return self.attack_target(target, desc) def is_in_combat(self): """ Check if the character is in combat. Returns: (boolean) is in combat or not """ return bool(self.ndb.combat_handler) def set_team(self, team_id): """ Set character's team id in combat. Args: team_id: team's id Returns: None """ self.db.team = team_id def get_team(self): """ Get character's team id in combat. Returns: team id """ return self.db.team def is_alive(self): """ Check if the character is alive. Returns: (boolean) the character is alive or not """ return True def die(self, killers): """ This character die. Args: killers: (list of objects) characters who kill this Returns: None """ # trigger event self.event.at_character_die() self.event.at_character_kill(killers) if not self.is_temp and self.reborn_time > 0: # Set reborn timer. self.defer = deferLater(reactor, self.reborn_time, self.reborn) def reborn(self): """ Reborn after being killed. """ # Reborn at its home. if self.home: self.move_to(self.home, quiet=True) def get_combat_commands(self): """ This returns a list of combat commands. Returns: (list) available commands for combat """ commands = [] for key, skill in self.db.skills.items(): if skill.passive: # exclude passive skills continue command = { "name": skill.get_name(), "key": key, "icon": getattr(skill, "icon", None) } commands.append(command) return commands def provide_exp(self, killer): """ Calculate the exp provide to the killer. Args: killer: (object) the character who kills it. Returns: (int) experience give to the killer """ return 0 def add_exp(self, exp): """ Add character's exp. Args: exp: the exp value to add. Returns: None """ pass def level_up(self): """ Upgrade level. Returns: None """ self.set_level(self.db.level + 1) def show_status(self): """ Show character's status. """ pass
class MudderyShopGoods(TYPECLASS("OBJECT")): """ This is a shop goods. Shops show these objects to players. It contains a common object to sell and additional shop information. """ typeclass_key = "SHOP_GOODS" typeclass_name = _("Goods", "typeclasses") def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyShopGoods, self).at_object_creation() # Set default values. if not self.attributes.has("goods"): self.db.goods = None self.available = False def after_data_loaded(self): """ Load goods data. Returns: None """ self.available = False self.shop_key = getattr(self.dfield, "shop", "") self.goods_key = getattr(self.dfield, "goods", "") if not self.shop_key or not self.goods_key: if self.db.goods: self.db.goods.delete() self.db.goods = None return # set goods information self.price = getattr(self.dfield, "price", 0) self.unit_key = getattr(self.dfield, "unit", "") self.number = getattr(self.dfield, "number", 0) self.condition = getattr(self.dfield, "condition", "") # get price unit information unit_record = get_object_record(self.unit_key) if not unit_record: logger.log_errmsg("Can not find %s price unit %s." % (self.goods_key, self.unit_key)) return self.unit_name = unit_record.name # load goods object goods = self.db.goods if goods: if goods.get_data_key() == self.goods_key: goods.load_data() else: goods.set_data_key(self.goods_key) else: goods = build_object(self.goods_key) if goods: self.db.goods = goods else: logger.log_err("Can not create goods %s." % self.goods_key) return self.name = goods.get_name() self.desc = goods.db.desc self.icon = getattr(goods, "icon", None) self.available = True def sell_to(self, caller): """ Buy this goods. Args: caller: the buyer Returns: """ # check price unit_number = caller.get_object_number(self.unit_key) if unit_number < self.price: caller.msg( {"alert": _("Sorry, %s is not enough.") % self.unit_name}) return # check if can get these objects if not caller.can_get_object(self.db.goods.get_data_key(), self.number): caller.msg({ "alert": _("Sorry, you can not take more %s.") % self.db.goods.get_name() }) return # Reduce price units. if not caller.remove_object(self.unit_key, self.price): caller.msg( {"alert": _("Sorry, %s is not enough.") % self.unit_name}) return # Give goods. obj_list = [{ "object": self.db.goods.get_data_key(), "number": self.number }] caller.receive_objects(obj_list)
class MudderyNPC(TYPECLASS("NON_PLAYER")): """ Neutral or friendly NPC. They can not be attacked. """ typeclass_key = "NPC" def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyNPC, self).at_object_creation() # NPC's shop if not self.attributes.has("shops"): self.db.shops = {} def after_data_loaded(self): """ Init the character. """ super(MudderyNPC, self).after_data_loaded() # set level self.db.level = getattr(self.dfield, "level", 1) # Character can auto fight. self.auto_fight = True # set home self.home = self.location # load dialogues. self.load_dialogues() # load shops self.load_shops() def load_dialogues(self): """ Load dialogues. """ dialogues = NPC_DIALOGUES.filter(self.get_data_key()) self.default_dialogues = [ dialogue.dialogue for dialogue in dialogues if dialogue.default ] self.dialogues = [ dialogue.dialogue for dialogue in dialogues if not dialogue.default ] def load_shops(self): """ Load character's shop. """ # shops records shop_records = NPC_SHOPS.filter(self.get_data_key()) shop_keys = set([record.shop for record in shop_records]) # remove old shops for shop_key in self.db.shops: if shop_key not in shop_keys: # remove this shop self.db.shops[shop_key].delete() del self.db.shops[shop_key] # add new shop for shop_record in shop_records: shop_key = shop_record.shop if shop_key not in self.db.shops: # Create shop object. shop_obj = build_object(shop_key) if not shop_obj: logger.log_errmsg("Can't create shop: %s" % shop_key) continue self.db.shops[shop_key] = shop_obj def get_available_commands(self, caller): """ This returns a list of available commands. """ commands = [] if self.dialogues or self.default_dialogues: # If the character have something to talk, add talk command. commands.append({ "name": _("Talk"), "cmd": "talk", "args": self.dbref }) # Add shops. for shop_obj in self.db.shops.values(): if not shop_obj.is_visible(caller): continue verb = shop_obj.verb if not verb: verb = shop_obj.get_name() commands.append({ "name": verb, "cmd": "shopping", "args": shop_obj.dbref }) return commands def have_quest(self, caller): """ If the npc can complete or provide quests. Returns (can_provide_quest, can_complete_quest). """ return DIALOGUE_HANDLER.have_quest(caller, self)
class MudderyWorldObject(TYPECLASS("OBJECT")): typeclass_key = "WORLD_OBJECT" typeclass_name = _("World Object", "typeclasses") model_name = "world_objects"
class MudderyPlayerCharacter(TYPECLASS("CHARACTER")): """ The Character defaults to implementing some of its hook methods with the following standard functionality: at_basetype_setup - always assigns the DefaultCmdSet to this object type (important!)sets locks so character cannot be picked up and its commands only be called by itself, not anyone else. (to change things, use at_object_creation() instead) at_after_move - launches the "look" command at_post_puppet(player) - when Player disconnects from the Character, we store the current location, so the "unconnected" character object does not need to stay on grid but can be given a None-location while offline. at_pre_puppet - just before Player re-connects, retrieves the character's old location and puts it back on the grid with a "charname has connected" message echoed to the room """ typeclass_key = "PLAYER_CHARACTER" # initialize all handlers in a lazy fashion @lazy_property def quest_handler(self): return QuestHandler(self) # attributes used in statements @lazy_property def statement_attr(self): return StatementAttributeHandler(self) def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyPlayerCharacter, self).at_object_creation() # honour if not HONOURS_MAPPER.has_info(self): if self.db.level >= settings.MIN_HONOUR_LEVEL: HONOURS_MAPPER.set_honour(self, 0) else: HONOURS_MAPPER.set_honour(self, -1) # Set default data. if not self.attributes.has("nickname"): self.db.nickname = "" if not self.attributes.has("unlocked_exits"): self.db.unlocked_exits = set() if not self.attributes.has("revealed_map"): self.db.revealed_map = set() # set custom attributes if not self.attributes.has("attributes"): self.db.attributes = {} """ # Choose a random career. if not self.attributes.has("career"): self.db.career = "" try: careers = DATA_SETS.character_careers.objects.all() if careers: career = random.choice(careers) self.db.career = career.key except Exception, e: pass """ def after_data_loaded(self): """ """ super(MudderyPlayerCharacter, self).after_data_loaded() self.solo_mode = GAME_SETTINGS.get("solo_mode") self.available_channels = {} # refresh data self.refresh_data() def move_to(self, destination, quiet=False, emit_to_obj=None, use_destination=True, to_none=False, move_hooks=True, **kwargs): """ Moves this object to a new location. """ if not quiet and self.solo_mode: # If in solo mode, move quietly. quiet = True return super(MudderyPlayerCharacter, self).move_to(destination, quiet, emit_to_obj, use_destination, to_none, move_hooks) def at_object_receive(self, moved_obj, source_location, **kwargs): """ Called after an object has been moved into this object. Args: moved_obj (Object): The object moved into this one source_location (Object): Where `moved_object` came from. """ super(MudderyPlayerCharacter, self).at_object_receive(moved_obj, source_location) # send latest inventory data to player self.msg({"inventory": self.return_inventory()}) def at_object_left(self, moved_obj, target_location): """ Called after an object has been removed from this object. Args: moved_obj (Object): The object leaving target_location (Object): Where `moved_obj` is going. """ super(MudderyPlayerCharacter, self).at_object_left(moved_obj, target_location) # send latest inventory data to player self.msg({"inventory": self.return_inventory()}) def at_after_move(self, source_location): """ We make sure to look around after a move. """ self.msg({"msg": _("Moving to %s ...") % self.location.name}) self.show_location() def at_post_puppet(self): """ Called just after puppeting has been completed and all Player<->Object links have been established. """ self.available_channels = self.get_available_channels() # Send puppet info to the client first. self.msg({"puppet": {"dbref": self.dbref, "name": self.get_name(), "icon": getattr(self, "icon", None)}}) # send character's data to player message = {"status": self.return_status(), "equipments": self.return_equipments(), "inventory": self.return_inventory(), "skills": self.return_skills(), "quests": self.quest_handler.return_quests(), "revealed_map": self.get_revealed_map(), "channels": self.available_channels} self.msg(message) self.show_location() # notify its location if not self.solo_mode: if self.location: change = {"dbref": self.dbref, "name": self.get_name()} self.location.msg_contents({"player_online": change}, exclude=[self]) self.resume_last_dialogue() self.resume_combat() def at_pre_unpuppet(self): """ Called just before beginning to un-connect a puppeting from this Player. """ if not self.solo_mode: # notify its location if self.location: change = {"dbref": self.dbref, "name": self.get_name()} self.location.msg_contents({"player_offline":change}, exclude=self) MATCH_QUEUE_HANDLER.remove(self) def set_nickname(self, nickname): """ Set player character's nickname. """ self.db.nickname = nickname def get_name(self): """ Get player character's name. """ # Use nick name instead of normal name. return self.db.nickname def get_available_commands(self, caller): """ This returns a list of available commands. """ commands = [] if self.is_alive(): commands.append({"name": _("Attack"), "cmd": "attack", "args": self.dbref}) return commands def get_available_channels(self): """ Get available channel's info. Returns: (dict) channels """ channels = {"": _("Say", category="channels"), "Public": _("Public", category="channels")} commands = False if self.account: if self.is_superuser: commands = True else: for perm in self.account.permissions.all(): if perm in settings.PERMISSION_COMMANDS: commands = True break if commands: channels["cmd"] = _("Cmd") return channels def get_revealed_map(self): """ Get the map that the character has revealed. Return value: { "rooms": {room1's key: {"name": name, "icon": icon, "area": area, "pos": position}, room2's key: {"name": name, "icon": icon, "area": area, "pos": position}, ...}, "exits": {exit1's key: {"from": room1's key, "to": room2's key}, exit2's key: {"from": room3's key, "to": room4's key}, ...} } """ rooms = {} exits = {} for room_key in self.db.revealed_map: # get room's information room = utils.search_obj_data_key(room_key) if room: room = room[0] rooms[room_key] = {"name": room.get_name(), "icon": room.icon, "area": room.location and room.location.get_data_key(), "pos": room.position} new_exits = room.get_exits() if new_exits: exits.update(new_exits) for path in exits.values(): # add room's neighbours if not path["to"] in rooms: neighbour = utils.search_obj_data_key(path["to"]) if neighbour: neighbour = neighbour[0] rooms[neighbour.get_data_key()] = {"name": neighbour.get_name(), "icon": neighbour.icon, "area": neighbour.location and neighbour.location.get_data_key(), "pos": neighbour.position} return {"rooms": rooms, "exits": exits} def show_location(self): """ show character's location """ if self.location: location_key = self.location.get_data_key() area = self.location.location and self.location.location.get_appearance(self) msg = {"current_location": {"key": location_key, "area": area}} """ reveal_map: { "rooms": {room1's key: {"name": name, "icon": icon, "area": area, "pos": position}, room2's key: {"name": name, "icon": icon, "area": area, "pos": position}, ...}, "exits": {exit1's key: {"from": room1's key, "to": room2's key}, exit2's key: {"from": room3's key, "to": room4's key}, ...} } """ reveal_map = None if not location_key in self.db.revealed_map: # reveal map self.db.revealed_map.add(self.location.get_data_key()) rooms = {location_key: {"name": self.location.get_name(), "icon": self.location.icon, "area": self.location.location and self.location.location.get_data_key(), "pos": self.location.position}} exits = self.location.get_exits() for path in exits.values(): # add room's neighbours if not path["to"] in rooms: neighbour = utils.search_obj_data_key(path["to"]) if neighbour: neighbour = neighbour[0] rooms[neighbour.get_data_key()] = {"name": neighbour.get_name(), "icon": neighbour.icon, "area": neighbour.location and neighbour.location.get_data_key(), "pos": neighbour.position} msg["reveal_map"] = {"rooms": rooms, "exits": exits} # get appearance appearance = self.location.get_appearance(self) appearance.update(self.location.get_surroundings(self)) msg["look_around"] = appearance self.msg(msg) def load_default_objects(self): """ Load character's default objects. """ # get character's model name model_name = getattr(self.dfield, "model", None) if not model_name: model_name = self.get_data_key() # default objects object_records = DEFAULT_OBJECTS.filter(model_name) default_object_ids = set([record.object for record in object_records]) # add new default objects for object_record in object_records: if not self.search_inventory(object_record.object): obj_list = [{"object": object_record.object, "number": object_record.number}] self.receive_objects(obj_list, mute=True) def receive_objects(self, obj_list, mute=False, combat=False): """ Add objects to the inventory. Args: obj_list: (list) a list of object keys and there numbers. list item: {"object": object's key "number": object's number} mute: (boolean) do not send messages to the owner combat: (boolean) get objects in combat. Returns: (dict) a list of objects that not have been received and their reasons. """ accepted_keys = {} # the keys of objects that have been accepted accepted_names = {} # the names of objects that have been accepted rejected_keys = {} # the keys of objects that have been rejected reject_reason = {} # the reasons of why objects have been rejected # check what the character has now inventory = {} for item in self.contents: key = item.get_data_key() if key in inventory: # if the character has more than one item of the same kind, # get the smallest stack. if inventory[key].db.number > item.db.number: inventory[key] = item else: inventory[key] = item for obj in obj_list: key = obj["object"] available = obj["number"] number = available accepted = 0 name = "" unique = False if number == 0: # it is an empty object if key in inventory: # already has this object accepted_keys[key] = 0 accepted_names[name] = 0 continue object_record = get_object_record(key) if not object_record: # can not find object's data record reason = _("Can not get %s.") % name rejected_keys[key] = 0 reject_reason[name] = reason continue if object_record.can_remove: # remove this empty object accepted_keys[key] = 0 accepted_names[name] = 0 continue # create a new content new_obj = build_object(key) if not new_obj: reason = _("Can not get %s.") % name rejected_keys[key] = 0 reject_reason[name] = reason continue name = new_obj.get_name() # move the new object to the character if not new_obj.move_to(self, quiet=True, emit_to_obj=self): new_obj.delete() reason = _("Can not get %s.") % name rejected_keys[key] = 0 reject_reason[name] = reason break # accept this object accepted_keys[key] = 0 accepted_names[name] = 0 else: # common number # if already has this kind of object if key in inventory: # add to current object name = inventory[key].name unique = inventory[key].unique add = number if add > inventory[key].max_stack - inventory[key].db.number: add = inventory[key].max_stack - inventory[key].db.number if add > 0: # increase stack number inventory[key].increase_num(add) number -= add accepted += add # if does not have this kind of object, or stack is full reason = "" while number > 0: if unique: # can not have more than one unique objects reason = _("Can not get more %s.") % name break # create a new content new_obj = build_object(key) if not new_obj: reason = _("Can not get %s.") % name break name = new_obj.get_name() unique = new_obj.unique # move the new object to the character if not new_obj.move_to(self, quiet=True, emit_to_obj=self): new_obj.delete() reason = _("Can not get %s.") % name break # Get the number that actually added. add = number if add > new_obj.max_stack: add = new_obj.max_stack if add <= 0: break new_obj.increase_num(add) number -= add accepted += add if accepted > 0: accepted_keys[key] = accepted accepted_names[name] = accepted if accepted < available: rejected_keys[key] = available - accepted reject_reason[name] = reason if not mute: # Send results to the player. message = {"get_objects": {"accepted": accepted_names, "rejected": reject_reason, "combat": combat}} self.msg(message) self.show_inventory() # call quest handler for key in accepted_keys: self.quest_handler.at_objective(defines.OBJECTIVE_OBJECT, key, accepted_keys[key]) return rejected_keys def get_object_number(self, obj_key): """ Get the number of this object. Args: obj_key: (String) object's key Returns: int: object number """ objects = self.search_inventory(obj_key) # get total number sum = 0 for obj in objects: obj_num = obj.get_number() sum += obj_num return sum def can_get_object(self, obj_key, number): """ Check if the character can get these objects. Args: obj_key: (String) object's key number: (int) object's number Returns: boolean: can get Notice: If the character does not have this object, the return will be always true, despite of the number! """ objects = self.search_inventory(obj_key) if not objects: return True obj = objects[0] if not obj.unique: return True if obj.get_number() + number <= obj.max_stack: return True return False def use_object(self, obj, number=1): """ Use an object. Args: obj: (object) object to use number: (int) number to use Returns: result: (string) the description of the result """ if not obj: return _("Can not find this object.") if obj.db.number < number: return _("Not enough number.") # take effect try: result, used = obj.take_effect(self, number) if used > 0: # remove used object self.remove_object(obj.get_data_key(), used) return result except Exception, e: ostring = "Can not use %s: %s" % (obj.get_data_key(), e) logger.log_tracemsg(ostring) return _("No effect.")
class MudderyShopGoods(TYPECLASS("OBJECT")): """ This is a shop goods. Shops show these objects to players. It contains a common object to sell and additional shop information. """ typeclass_key = "SHOP_GOODS" typeclass_name = _("Goods", "typeclasses") model_name = "shop_goods" def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. """ super(MudderyShopGoods, self).at_object_creation() # Set default values. if not self.attributes.has("goods"): self.db.goods = None self.available = False def after_data_loaded(self): """ Load goods data. Returns: None """ self.available = False self.shop_key = getattr(self.system, "shop", "") self.goods_key = getattr(self.system, "goods", "") self.goods_level = getattr(self.system, "level", 0) if not self.shop_key or not self.goods_key: if self.db.goods: self.db.goods.delete() self.db.goods = None return # set goods information self.price = getattr(self.system, "price", 0) self.unit_key = getattr(self.system, "unit", "") self.number = getattr(self.system, "number", 0) self.condition = getattr(self.system, "condition", "") # get price unit information unit_record = get_object_record(self.unit_key) if not unit_record: logger.log_errmsg("Can not find %s price unit %s." % (self.goods_key, self.unit_key)) return self.unit_name = unit_record.name # load goods object goods = self.db.goods if goods: if goods.get_data_key() == self.goods_key: # Load data. try: # Load db data. goods.load_data() except Exception, e: logger.log_errmsg("%s(%s) can not load data:%s" % (self.goods_key, self.dbref, e)) else: goods.set_data_key(self.goods_key, self.goods_level) else:
try: # set data obj.load_data() # put obj to its default location obj.reset_location() except Exception, e: ostring = "%s can not load data:%s" % (obj.dbref, e) print(ostring) print(traceback.print_exc()) if caller: caller.msg(ostring) current_obj_keys.add(obj_key) # Create new objects. object_model_name = TYPECLASS("OBJECT").model_name object_model = apps.get_model(settings.WORLD_DATA_APP, object_model_name) for record in objects_data: if not record.key in current_obj_keys: # Create new objects. ostring = "Creating %s." % record.key print(ostring) if caller: caller.msg(ostring) try: object_record = object_model.objects.get(key=record.key) typeclass_path = TYPECLASS_SET.get_module( object_record.typeclass) obj = create.create_object(typeclass_path, object_record.name) count_create += 1