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
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 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
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
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 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) # 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 try: # Get record. obj_model_name = TYPECLASS("OBJECT").model_name unit_record = WorldData.get_table_data(obj_model_name, key=self.unit_key) unit_record = unit_record[0] except Exception as e: logger.log_errmsg("Can not find %s's price unit %s." % (self.goods_key, self.unit_key)) return self.unit_name = unit_record.name # load goods try: obj_record = WorldData.get_table_data(obj_model_name, key=self.goods_key) obj_record = obj_record[0] goods_models = TYPECLASS_SET.get_class_modeles( obj_record.typeclass) goods_data = WorldData.get_tables_data(goods_models, key=self.goods_key) except Exception as e: logger.log_errmsg("Can not find goods %s." % self.goods_key) return self.name = goods_data["name"] self.desc = goods_data["desc"] self.icon = goods_data.get("icon", None) self.available = True
def calc_combat_rewards(self, winners, losers): """ Called when the character wins the combat. Args: winners: (List) all combat winners. losers: (List) all combat losers. Returns: (dict) reward dict """ rewards = {} for winner in winners: winner_char = self.characters[winner]["char"] exp = 0 loots = [] for loser in losers: loser_char = self.characters[loser]["char"] exp += loser_char.provide_exp(self) obj_list = loser_char.loot_handler.get_obj_list(winner_char) if obj_list: loots.extend(obj_list) obj_list = [] if loots: obj_model_name = TYPECLASS("OBJECT").model_name for obj_info in loots: try: obj_record = WorldData.get_table_data(obj_model_name, key=obj_info["object"]) obj_record = obj_record[0] goods_models = TYPECLASS_SET.get_class_modeles(obj_record.typeclass) goods_data = WorldData.get_tables_data(goods_models, key=obj_info["object"]) obj_list.append({ "object": obj_info["object"], "number": obj_info["number"], "name": goods_data["name"], "icon": goods_data.get("icon", None), "reject": "", }) except Exception as e: logger.log_errmsg("Can not loot object %s." % obj_info["object"]) pass rewards[winner] = { "exp": exp, "loots": obj_list, } return rewards
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 }) commands.extend( super(MudderySkillBook, self).get_available_commands(caller)) 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.system, "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
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 = ImageResource.get(resource) resource_info = resource_info[0] 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
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(combat_type=CombatType.NORMAL, teams={ 1: [target], 2: [self] }, desc=desc, timeout=0) return True
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, CreatorLootList.get(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.system, "loot_verb", None) if not self.loot_verb: self.loot_verb = _("Loot") self.loot_condition = getattr(self.system, "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 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 try: record = WorldData.get_table_data(model_name, key=quest_key) record = record[0] return STATEMENT_HANDLER.match_condition(record.condition, self.owner, None) except Exception as e: logger.log_errmsg("Can't get quest %s's condition: %s" % (quest_key, e)) return False
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. record = WorldData.get_table_data(model_name, key=obj_key) record = record[0] except Exception as e: ostring = "Can not get record %s in %s: %s." % (obj_key, model_name, e) print(ostring) print(traceback.print_exc()) return record
def load_data(self, level=None, reset_location=True): """ Set data to the object. """ key = self.get_data_key() if key: base_model = TYPECLASS("OBJECT").model_name # Get the object's base data self.load_base_data(base_model, key) # reset typeclass typeclass = getattr(self.system, "typeclass", "") if typeclass: self.set_typeclass(typeclass) else: logger.log_errmsg("%s does not have a typeclass." % key) # Load system data except base data. self.load_system_data(base_model, key) # Load custom properties. if level is None: if self.attributes.has("level"): level = self.db.level else: # Use default level. level = getattr(self.system, "level", 0) self.db.level = level self.load_custom_properties(level) self.after_data_loaded() if not self.location and reset_location: self.set_location(getattr(self.system, "location", ""))
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 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 = NPCDialogues.get(self.get_data_key()) self.default_dialogues = [ dialogue.dialogue for dialogue in dialogues if dialogue.dialogue and dialogue.default ] self.dialogues = [ dialogue.dialogue for dialogue in dialogues if dialogue.dialogue and not dialogue.default ] def load_shops(self): """ Load character's shop. """ # shops records shop_records = NPCShops.get(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 rewards = None if self.ndb.combat_handler: result = self.ndb.combat_handler.get_combat_result(self.id) if result: status, opponents, rewards = result if not self.db.is_combat_instance: if status == defines.COMBAT_LOSE: self.die(opponents) super(MudderyBaseNPC, self).leave_combat() if status != defines.COMBAT_LOSE: self.recover() else: super(MudderyBaseNPC, self).leave_combat()
class MudderyLockedExit(TYPECLASS("EXIT")): """ Characters must unlock these exits to pass it. The view and commands of locked exits are different from unlocked exits. """ typeclass_key = "LOCKED_EXIT" typeclass_name = _("Locked Exit", "typeclasses") model_name = "exit_locks" def after_data_loaded(self): """ Set data_info to the object." """ super(MudderyLockedExit, self).after_data_loaded() self.unlock_condition = getattr(self.system, "unlock_condition", "") self.unlock_verb = getattr(self.system, "unlock_verb", "") self.locked_desc = getattr(self.system, "locked_desc", "") self.auto_unlock = getattr(self.system, "auto_unlock", False) self.unlock_forever = getattr(self.system, "unlock_forever", True) 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. """ if not super(MudderyLockedExit, self).at_before_traverse(traversing_object): return False # Only can pass exits which have already been unlocked. if traversing_object.is_exit_unlocked(self.get_data_key()): if not self.unlock_forever: # lock the exit again traversing_object.lock_exit(self) return True if self.auto_unlock and self.can_unlock(traversing_object): # Can unlock the exit automatically. if self.unlock_forever: # Unlock it. traversing_object.unlock_exit(self) return True # Show the object's appearance. appearance = self.get_appearance(traversing_object) traversing_object.msg({"look_obj": appearance}) return False def can_unlock(self, caller): """ Unlock an exit. """ # Only can unlock exits which match there conditions. return STATEMENT_HANDLER.match_condition(self.unlock_condition, caller, self) def get_appearance(self, caller): """ This is a convenient hook for a 'look' command to call. """ # Get name and description. if caller.is_exit_unlocked(self.get_data_key()): # If is unlocked, use common appearance. return super(MudderyLockedExit, self).get_appearance(caller) can_unlock = self.can_unlock(caller) if self.auto_unlock and can_unlock: if self.unlock_forever: # Automatically unlock the exit when a character looking at it. caller.unlock_exit(self) # If is unlocked, use common appearance. return super(MudderyLockedExit, self).get_appearance(caller) cmds = [] if can_unlock: # show unlock command verb = self.unlock_verb if not verb: verb = _("Unlock") cmds = [{"name": verb, "cmd": "unlock_exit", "args": self.dbref}] info = { "dbref": self.dbref, "name": self.name, "desc": self.locked_desc, "cmds": cmds } return info 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 caller.is_exit_unlocked(self.get_data_key()): # If is unlocked, use common commands. return super(MudderyLockedExit, self).get_available_commands(caller) cmds = [] can_unlock = STATEMENT_HANDLER.match_condition(self.unlock_condition, caller, self) if can_unlock: # show unlock command verb = self.unlock_verb if not verb: verb = _("Unlock") cmds = [{"name": verb, "cmd": "unlock", "args": self.dbref}] return cmds
class MudderySkill(TYPECLASS("OBJECT")): """ A skill of the character. """ typeclass_key = "SKILL" typeclass_name = _("Skill", "typeclasses") model_name = "skills" 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() self.owner = None self.owner_dbref = None # set status if not self.attributes.has("owner"): self.db.owner_dbref = 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 at_init(self): """ Load the skill's data. """ super(MudderySkill, self).at_init() self.owner = None self.owner_dbref = self.db.owner_dbref if self.owner_dbref: self.owner = self.search_dbref(self.owner_dbref) 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.system, "function", "") self.cd = getattr(self.system, "cd", 0) self.passive = getattr(self.system, "passive", False) self.main_type = getattr(self.system, "main_type", "") self.sub_type = getattr(self.system, "sub_type", "") message_model = getattr(self.system, "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.owner = owner self.owner_dbref = owner.dbref self.db.owner_dbref = owner.dbref 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): """ Cast this skill. Args: target: (object) skill's target. Returns: skill_cast: (dict) skill's result """ skill_cast = {} not_available = self.check_available() if not_available: skill_cast = {"cast": not_available} else: results = self.do_skill(target) # set message skill_cast = { "caller": self.owner_dbref, "skill": self.get_data_key(), "main_type": self.main_type, "sub_type": self.sub_type, "cast": self.cast_message(target), "status": { self.owner.dbref: self.owner.get_combat_status(), } } if target: skill_cast["target"] = target.dbref skill_cast["status"][target.dbref] = target.get_combat_status() if results: skill_cast["result"] = " ".join(results) return skill_cast 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.owner, target) def check_available(self): """ Check this skill. Returns: message: (string) If the skill is not available, returns a string of reason. If the skill is available, return "". """ 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.owner: caller_name = self.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 MudderyShop(TYPECLASS("OBJECT")): """ A shop. """ typeclass_key = "SHOP" typeclass_name = _("Shop", "typeclasses") model_name = "shops" def at_object_creation(self): """ Called once, when this object is first created. This is the normal hook to overload for most object types. It will be called when swap its typeclass, so it must keep old values. """ super(MudderyShop, self).at_object_creation() # set default values self.db.owner = None if not self.attributes.has("goods"): self.db.goods = {} 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 goods will be removed too. """ result = super(MudderyShop, self).at_object_delete() if not result: return result # delete all goods for goods in self.db.goods.values(): goods.delete() return True def after_data_loaded(self): """ Set data_info to the object. Returns: None """ super(MudderyShop, self).after_data_loaded() # load shop goods self.load_goods() self.verb = getattr(self.system, "verb", None) def load_goods(self): """ Load shop goods. """ # shops records goods_records = ShopGoods.get(self.get_data_key()) goods_keys = set([record.key for record in goods_records]) # search current goods current_goods = {} for key, obj in self.db.goods.items(): if key in goods_keys: current_goods[key] = obj else: # remove goods that is not in goods_keys obj.delete() # add new goods for goods_record in goods_records: goods_key = goods_record.key if goods_key not in current_goods: # Create shop_goods object. goods_obj = build_object(goods_key) if not goods_obj: logger.log_errmsg("Can't create goods: %s" % goods_key) continue current_goods[goods_key] = goods_obj self.db.goods = current_goods def set_owner(self, owner): """ Set the owner of the shop. :param owner: :return: """ self.db.owner = owner def show_shop(self, caller): """ Send shop data to the caller. Args: caller (obj): the custom """ if not caller: return info = self.return_shop_info(caller) caller.msg({"shop": info}) def return_shop_info(self, caller): """ Get shop information. Args: caller (obj): the custom """ info = { "dbref": self.dbref, "name": self.get_name(), "desc": self.get_desc(caller), } icon = self.icon if not icon and self.db.owner: icon = self.db.owner.icon info["icon"] = icon goods_list = self.return_shop_goods(caller) info["goods"] = goods_list return info def return_shop_goods(self, caller): """ Get shop's information. Args: caller (obj): the custom """ goods_list = [] # Get shop goods for obj in self.db.goods.values(): if not obj.is_available(caller): continue goods = { "dbref": obj.dbref, "name": obj.name, "desc": obj.desc, "number": obj.number, "price": obj.price, "unit": obj.unit_name, "icon": obj.icon } goods_list.append(goods) return goods_list
def receive_objects(self, obj_list, mute=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 Returns: (list) a list of objects that not have been received and their reasons. [{ "key": key, "name": name, "level": level, "number": number, "icon": icon, "reject": reason, }] """ objects = [] # objects that have been accepted # 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"] level = obj.get("level") available = obj["number"] name = "" icon = "" number = available accepted = 0 reject = False unique = False if number == 0: # it is an empty object if key in inventory: # already has this object continue object_record = None try: common_model_name = TYPECLASS("COMMON_OBJECT").model_name object_record = WorldData.get_table_data(common_model_name, key=key) object_record = object_record[0] except Exception as e: pass if not object_record: # can not find object's data record continue if object_record.can_remove: # remove this empty object continue # create a new content new_obj = build_object(key, level=level) if not new_obj: reject = _("Can not get %s.") % key else: name = new_obj.get_name() icon = new_obj.icon # move the new object to the character if not new_obj.move_to(self, quiet=True, emit_to_obj=self): new_obj.delete() reject = _("Can not get %s.") % name else: # common number # if already has this kind of object if key in inventory: # add to current object name = inventory[key].name icon = inventory[key].icon 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 while number > 0: if unique: # can not have more than one unique objects reject = _("Can not get more %s.") % name break # create a new content new_obj = build_object(key, level=level) if not new_obj: reject = _("Can not get %s.") % name break name = new_obj.get_name() icon = new_obj.icon 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() reject = _("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 objects.append({ "key": key, "name": name, "icon": icon, "number": accepted, "reject": reject, }) if not mute: # Send results to the player. message = {"get_objects": objects} self.msg(message) self.show_inventory() # call quest handler for item in objects: if not item["reject"]: self.quest_handler.at_objective(defines.OBJECTIVE_OBJECT, item["key"], item["number"]) return 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" typeclass_name = _("Player Character", "typeclasses") model_name = "player_characters" # 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() # 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 = {} def at_object_delete(self): """ called just before deleting an object. """ # remove the character's honour HONOURS_MAPPER.remove_honour(self.id) # remove all quests self.quest_handler.remove_all() return super(MudderyPlayerCharacter, self).at_object_delete() 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_properties(True) # if it is dead, reborn at init. if not self.is_alive(): if not self.db.is_combat_instance and self.reborn_time > 0: self.reborn() 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_before_move(self, destination, **kwargs): """ Called just before starting to move this object to destination. """ # trigger event if self.has_account: self.location.event.at_character_move_out(self) return True def at_after_move(self, source_location): """ We make sure to look around after a move. """ self.msg({"msg": _("Moved to %s ...") % self.location.name}) self.show_location() # trigger event if self.has_account: self.location.event.at_character_move_in(self) 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() allow_commands = False if self.account: if self.is_superuser: allow_commands = True else: for perm in self.account.permissions.all(): if perm in settings.PERMISSION_COMMANDS: allow_commands = True break # Django's superuser even it is quelled. if not allow_commands: allow_commands = self.db_account and self.db_account.is_superuser # Send puppet info to the client first. output = { "dbref": self.dbref, "name": self.get_name(), "icon": getattr(self, "icon", None), } if allow_commands: output["allow_commands"] = True self.msg({"puppet": output}) # 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() # Resume all scripts. scripts = self.scripts.all() for script in scripts: script.unpause() def at_pre_unpuppet(self): """ Called just before beginning to un-connect a puppeting from this Player. """ # Pause all scripts. scripts = self.scripts.all() for script in scripts: script.pause() 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_COMBAT_HANDLER.remove(self) def get_data_key(self, default=""): """ Get data's key. Args: default: (string) default value if can not find the data key. """ key = GAME_SETTINGS.get("default_player_character_key") if not key: key = self.attributes.get(key="key", category=settings.DATA_KEY_CATEGORY, strattr=True) if not key: key = default return key 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 = {} channel = ChannelDB.objects.get_channel( settings.DEFAULT_CHANNELS[0]["key"]) if channel.has_connection(self): channels[channel.key] = { "type": "CHANNEL", "name": _("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 # Django's superuser even it is quelled. if not commands: commands = self.db_account and self.db_account.is_superuser if commands: channels["CMD"] = { "type": "CMD", "name": _("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.system, "model", None) if not model_name: model_name = self.get_data_key() # default objects object_records = DefaultObjects.get(model_name) # add new default objects obj_list = [] for object_record in object_records: if not self.search_inventory(object_record.object): obj_list.append({ "object": object_record.object, "level": object_record.level, "number": object_record.number, }) if obj_list: self.receive_objects(obj_list, mute=True) def receive_objects(self, obj_list, mute=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 Returns: (list) a list of objects that not have been received and their reasons. [{ "key": key, "name": name, "level": level, "number": number, "icon": icon, "reject": reason, }] """ objects = [] # objects that have been accepted # 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"] level = obj.get("level") available = obj["number"] name = "" icon = "" number = available accepted = 0 reject = False unique = False if number == 0: # it is an empty object if key in inventory: # already has this object continue object_record = None try: common_model_name = TYPECLASS("COMMON_OBJECT").model_name object_record = WorldData.get_table_data(common_model_name, key=key) object_record = object_record[0] except Exception as e: pass if not object_record: # can not find object's data record continue if object_record.can_remove: # remove this empty object continue # create a new content new_obj = build_object(key, level=level) if not new_obj: reject = _("Can not get %s.") % key else: name = new_obj.get_name() icon = new_obj.icon # move the new object to the character if not new_obj.move_to(self, quiet=True, emit_to_obj=self): new_obj.delete() reject = _("Can not get %s.") % name else: # common number # if already has this kind of object if key in inventory: # add to current object name = inventory[key].name icon = inventory[key].icon 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 while number > 0: if unique: # can not have more than one unique objects reject = _("Can not get more %s.") % name break # create a new content new_obj = build_object(key, level=level) if not new_obj: reject = _("Can not get %s.") % name break name = new_obj.get_name() icon = new_obj.icon 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() reject = _("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 objects.append({ "key": key, "name": name, "icon": icon, "number": accepted, "reject": reject, }) if not mute: # Send results to the player. message = {"get_objects": objects} self.msg(message) self.show_inventory() # call quest handler for item in objects: if not item["reject"]: self.quest_handler.at_objective(defines.OBJECTIVE_OBJECT, item["key"], item["number"]) return objects 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 as e: ostring = "Can not use %s: %s" % (obj.get_data_key(), e) logger.log_tracemsg(ostring) return _("No effect.") def remove_objects(self, obj_list): """ Remove objects from the inventory. Args: obj_list: (list) a list of object keys and there numbers. list item: {"object": object's key "number": object's number} Returns: boolean: success """ success = True for item in obj_list: if not self.remove_object(item["object"], item["number"], True): success = False self.show_inventory() return success def remove_object(self, obj_key, number, mute=False): """ Remove objects from the inventory. Args: obj_key: object's key number: object's number mute: send inventory information Returns: boolean: success """ objects = self.search_inventory(obj_key) # get total number sum = 0 for obj in objects: obj_num = obj.get_number() sum += obj_num if sum < number: return False # remove objects to_remove = number try: for obj in objects: obj_num = obj.get_number() if obj_num > 0: if obj_num >= to_remove: obj.decrease_num(to_remove) to_remove = 0 else: obj.decrease_num(obj_num) to_remove -= obj_num if obj.get_number() <= 0: # If this object can be removed from the inventor. if obj.can_remove: # if it is an equipment, take off it first if getattr(obj, "equipped", False): self.take_off_equipment(obj) obj.delete() if to_remove <= 0: break except Exception as e: logger.log_tracemsg("Can not remove object %s: %s" % (obj_key, e)) return False if to_remove > 0: logger.log_err("Remove object error: %s" % obj_key) return False if not mute: self.show_inventory() return True 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 show_inventory(self): """ Send inventory data to player. """ inv = self.return_inventory() self.msg({"inventory": inv}) def return_inventory(self): """ Get inventory's data. """ inv = [] for item in self.contents: info = { "dbref": item.dbref, # item's dbref "name": item.name, # item's name "number": item.db.number, # item's number "desc": item.get_desc(self), # item's desc "can_remove": item.can_remove, "icon": getattr(item, "icon", None) } # item's icon if getattr(item, "equipped", False): info["equipped"] = item.equipped inv.append(info) # sort by created time inv.sort(key=lambda x: x["dbref"]) return inv def show_status(self): """ Send status to player. """ status = self.return_status() self.msg({"status": status}) def return_status(self): """ Get character's status. """ status = {} status["level"] = {"name": _("LEVEL"), "value": self.db.level} for key, info in self.get_properties_info().items(): status[key] = { "name": info["name"], "value": getattr(self.prop, key) } return status def show_equipments(self): """ Send equipments to player. """ equipments = self.return_equipments() self.msg({"equipments": equipments}) def return_equipments(self): """ Get equipments' data. """ equipments = {} for position in self.db.equipments: # in order of positions info = None if self.db.equipments[position]: dbref = self.db.equipments[position] for obj in self.contents: if obj.dbref == dbref: info = { "dbref": obj.dbref, "name": obj.name, "desc": obj.get_desc(self), "icon": obj.icon, } equipments[position] = info return equipments def equip_object(self, obj): """ Equip an object. args: obj(object): the equipment object. """ if obj.location != self: raise MudderyError(_("Can not find this equipment.")) type = obj.type position = obj.position if position not in self.db.equipments: raise MudderyError(_("Can not equip it on this position.")) if not EQUIP_TYPE_HANDLER.can_equip(self.db.career, type): raise MudderyError(_("Can not use this equipment.")) # Take off old equipment if self.db.equipments[position]: dbref = self.db.equipments[position] for content in self.contents: if content.dbref == dbref: content.equipped = False # Put on new equipment, store object's dbref. self.db.equipments[position] = obj.dbref # Set object's attribute 'equipped' to True obj.equipped = True # reset character's attributes self.refresh_properties(True) message = { "status": self.return_status(), "equipments": self.return_equipments(), "inventory": self.return_inventory() } self.msg(message) return def take_off_position(self, position): """ Take off an object from position. """ if not position in self.db.equipments: raise MudderyError(_("Can not find this equipment.")) if not self.db.equipments[position]: raise MudderyError(_("Can not find this equipment.")) # Set object's attribute 'equipped' to False dbref = self.db.equipments[position] for obj in self.contents: if obj.dbref == dbref: obj.equipped = False find = True self.db.equipments[position] = None # reset character's attributes self.refresh_properties(True) message = { "status": self.return_status(), "equipments": self.return_equipments(), "inventory": self.return_inventory() } self.msg(message) def take_off_equipment(self, equipment): """ Take off an equipment. args: equipment(object): the equipment object. """ if equipment.location != self: raise MudderyError(_("Can not find this equipment.")) if equipment.position in self.db.equipments: self.db.equipments[equipment.position] = None # Set object's attribute 'equipped' to False equipment.equipped = False # reset character's attributes self.refresh_properties(True) message = { "status": self.return_status(), "equipments": self.return_equipments(), "inventory": self.return_inventory() } self.msg(message) def lock_exit(self, exit): """ Lock an exit. Remove the exit's key from the character's unlock list. """ exit_key = exit.get_data_key() if not self.is_exit_unlocked(exit_key): return self.db.unlocked_exits.remove(exit_key) print(self.db.unlocked_exits) def unlock_exit(self, exit): """ Unlock an exit. Add the exit's key to the character's unlock list. """ exit_key = exit.get_data_key() if self.is_exit_unlocked(exit_key): return True if not exit.can_unlock(self): self.msg({"msg": _("Can not open this exit.")}) return False self.db.unlocked_exits.add(exit_key) return True def is_exit_unlocked(self, exit_key): """ Whether the exit is unlocked. """ return exit_key in self.db.unlocked_exits def show_skills(self): """ Send skills to player. """ skills = self.return_skills() self.msg({"skills": skills}) def return_skills(self): """ Get skills' data. """ skills = [] for key, skill in self.db.skills.items(): skills.append(skill.get_appearance(self)) return skills def resume_combat(self): """ Resume unfinished combat. Returns: None """ combat_handler = getattr(self.ndb, "combat_handler", None) if combat_handler: if not combat_handler.is_finished(): # show combat infomation combat_handler.show_combat(self) else: self.leave_combat() def combat_result(self, combat_type, result, opponents=None, rewards=None): """ Set the combat result. :param combat_type: combat's type :param result: defines.COMBAT_WIN, defines.COMBAT_LOSE, or defines.COMBAT_DRAW :param opponents: combat opponents :param rewards: combat rewards """ combat_result = { "type": combat_type.value, "result": result, "rewards": {}, } # get rewards if rewards: if "exp" in rewards and rewards["exp"]: exp = rewards["exp"] self.add_exp(exp) combat_result["rewards"]["exp"] = exp # give objects to winner if "loots" in rewards and rewards["loots"]: get_objects = self.receive_objects(rewards["loots"], mute=True) combat_result["rewards"]["get_objects"] = get_objects # honours if "honour" in rewards and rewards["honour"]: combat_result["rewards"]["honour"] = rewards["honour"] self.msg({"combat_finish": combat_result}) if combat_type == CombatType.NORMAL: # normal combat # trigger events if result == defines.COMBAT_WIN: for opponent in opponents: opponent.event.at_character_kill(self) opponent.event.at_character_die() # call quest handler for opponent in opponents: self.quest_handler.at_objective(defines.OBJECTIVE_KILL, opponent.get_data_key()) elif result == defines.COMBAT_LOSE: self.die(opponents) elif combat_type == CombatType.HONOUR: if result == defines.COMBAT_WIN: self.honour_win() elif result == defines.COMBAT_LOSE: self.honour_lose() # show status self.show_status() self.show_location() def die(self, killers): """ This character is killed. Move it to it's home. """ # player's character can always reborn if self.reborn_time < 1: self.reborn_time = 1 super(MudderyPlayerCharacter, self).die(killers) self.msg({"msg": _("You died.")}) if self.reborn_time > 0: self.msg({ "msg": _("You will be reborn in {C%(s)s{n seconds.") % { 's': self.reborn_time } }) def honour_win(self): """ The character win in an honour combat. """ # Recover properties. self.recover() self.show_status() def honour_lose(self): """ The character lost in an honour combat. """ # Recover properties. self.recover() self.show_status() def reborn(self): """ Reborn after being killed. """ # Reborn at its home. home = None default_home_key = GAME_SETTINGS.get("default_player_home_key") if default_home_key: rooms = utils.search_obj_data_key(default_home_key) if rooms: home = rooms[0] if not home: rooms = search.search_object(settings.DEFAULT_HOME) if rooms: home = rooms[0] if home: self.move_to(home, quiet=True) # Recover properties. self.recover() self.show_status() if home: self.msg({"msg": _("You are reborn at {C%s{n.") % home.get_name()}) else: self.msg({"msg": _("You are reborn.")}) def save_current_dialogues(self, dialogues, npc): """ Save player's current dialogues. Args: dialogues: the current dialogues npc: NPC whom the player is talking to. Returns: None """ if not GAME_SETTINGS.get("auto_resume_dialogues"): # Can not auto resume dialogues. return if not dialogues: self.clear_current_dialogue() return # Save the dialogue's id. dialogues = [d["dialogue"] for d in dialogues] npc_key = None if npc: npc_key = npc.get_data_key() location_key = None if self.location: location_key = self.location.get_data_key() self.db.current_dialogue = { "dialogues": dialogues, "npc": npc_key, "location": location_key } return def clear_current_dialogue(self): """ Clear player's current dialogues. Returns: None """ self.db.current_dialogue = None return def resume_last_dialogue(self): """ Restore player's dialogues when he return to game. Returns: None """ if not GAME_SETTINGS.get("auto_resume_dialogues"): # Can not auto resume dialogues. return if not self.db.current_dialogue: return current = self.db.current_dialogue if not current["dialogues"]: return # Check dialogue's location if self.location.get_data_key() != current["location"]: # If player's location has changed, return. return # Check npc. npc_talking = None if current["npc"]: npc_list = utils.search_obj_data_key(current["npc"]) npc_in_location = [ npc for npc in npc_list if npc.location == self.location ] if not npc_in_location: # If the NPC has left it's location, return. return npc_talking = npc_in_location[0] dialogues = [ DIALOGUE_HANDLER.get_dialogue(d) for d in current["dialogues"] ] dialogues = DIALOGUE_HANDLER.create_output_sentences( dialogues, self, npc_talking) self.msg({"dialogue": dialogues}) return def talk_to_npc(self, npc): """ Talk to an NPC. Args: npc: NPC's object. Returns: None """ # Set caller's target. self.set_target(npc) # Get NPC's dialogue list. dialogues = DIALOGUE_HANDLER.get_npc_dialogues(self, npc) self.save_current_dialogues(dialogues, npc) self.msg({"dialogue": dialogues}) def show_dialogue(self, dlg_key, npc): """ Show a dialogue. Args: dlg_key: dialogue's key. npc: (optional) NPC's object. Returns: None """ # Get next sentences_list. dialogue = DIALOGUE_HANDLER.get_dialogues_by_key(dlg_key, npc) # Send the dialogue to the player. self.save_current_dialogues(dialogue, npc) self.msg({"dialogue": dialogue}) def finish_dialogue(self, dlg_key, npc): """ Continue current dialogue. Args: dlg_key: current dialogue's key. npc: (optional) NPC's object. Returns: None """ if GAME_SETTINGS.get("auto_resume_dialogues"): # Check current dialogue. if not self.db.current_dialogue: return if dlg_key not in self.db.current_dialogue["dialogue"]: # Can not find specified dialogue in current dialogues. return try: # Finish current dialogue DIALOGUE_HANDLER.finish_dialogue(dlg_key, self, npc) except Exception as e: ostring = "Can not finish dialogue %s: %s" % (dlg_key, e) logger.log_tracemsg(ostring) # Get next dialogue. next_dialogues = DIALOGUE_HANDLER.get_next_dialogues( dlg_key, self, npc) # Send dialogues_list to the player. self.save_current_dialogues(next_dialogues, npc) self.msg({"dialogue": next_dialogues}) if not next_dialogues: # dialogue finished, refresh surroundings self.show_location() def add_exp(self, exp): """ Add character's exp. Args: exp: (number) the exp value to add. Returns: None """ super(MudderyPlayerCharacter, self).add_exp(exp) self.msg({"get_exp": {"exp": exp}}) def level_up(self): """ Upgrade level. Returns: None """ super(MudderyPlayerCharacter, self).level_up() # notify the player self.msg({ "msg": _("{C%s upgraded to level %s.{n") % (self.get_name(), self.db.level) }) def get_message(self, caller, message): """ Receive a message from a character. :param caller: talker. :param message: content. """ output = { "type": ConversationType.PRIVATE.value, "channel": self.get_name(), "from_dbref": caller.dbref, "from_name": caller.get_name(), "msg": message } self.msg({"conversation": output}) caller.msg({"conversation": output}) 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(MudderyPlayerCharacter, self).at_object_delete() if not result: return result self.quest_handler.remove_all() return True def show_rankings(self): """ Show character's rankings. """ honour_settings = HonourSettings.get_first_data() top_rankings = HONOURS_MAPPER.get_top_rankings( honour_settings.top_rankings_number) nearest_rankings = HONOURS_MAPPER.get_nearest_rankings( self, honour_settings.nearest_rankings_number) rankings = top_rankings rankings.extend([ char_id for char_id in nearest_rankings if char_id not in top_rankings ]) characters = [ self.search_dbref("#%s" % char_id) for char_id in rankings ] data = [{ "name": char.get_name(), "dbref": char.dbref, "ranking": HONOURS_MAPPER.get_ranking(char), "honour": HONOURS_MAPPER.get_honour(char) } for char in characters if char] self.msg({"rankings": data})
def __init__(self): self.model_name = TYPECLASS("EXIT").model_name self.model = apps.get_model(settings.WORLD_DATA_APP, self.model_name) self.objects = self.model.objects self.object_model_name = TYPECLASS("OBJECT").model_name
class MudderyQuest(TYPECLASS("OBJECT")): """ This class controls quest's objectives. Hooks are called when a character doing some things. """ typeclass_key = "QUEST" typeclass_name = _("Quest", "typeclasses") model_name = "quests" # initialize loot handler in a lazy fashion @lazy_property def loot_handler(self): return LootHandler(self, QuestLootList.get(self.get_data_key())) def at_object_creation(self): """ Set accomplished objectives to empty. """ super(MudderyQuest, self).at_object_creation() if not self.attributes.has("owner"): self.db.owner = None if not self.attributes.has("accomplished"): self.db.accomplished = {} def set_owner(self, owner): """ Set the owner of the skill. """ self.db.owner = owner def after_data_loaded(self): """ Load quest's data from db. """ super(MudderyQuest, self).after_data_loaded() self.objectives = {} self.not_accomplished = {} key = self.get_data_key() if not key: return # Get objectives. obj_records = QuestObjectives.get(key) for obj_record in obj_records: objective_type = obj_record.type objective = { "ordinal": obj_record.ordinal, "type": objective_type, "object": obj_record.object, "number": obj_record.number, "desc": obj_record.desc } self.objectives[obj_record.ordinal] = objective accomplished = self.db.accomplished.get(key, 0) if accomplished < obj_record.number: if not objective_type in self.not_accomplished: self.not_accomplished[objective_type] = [ obj_record.ordinal ] else: self.not_accomplished[objective_type].append( obj_record.ordinal) def get_appearance(self, caller): """ This is a convenient hook for a 'look' command to call. """ # Get name, description and available commands. info = super(MudderyQuest, self).get_appearance(caller) info["objectives"] = self.return_objectives() return info def get_available_commands(self, caller): """ This returns a list of available commands. """ commands = [] if GAME_SETTINGS.get("can_give_up_quests"): commands.append({ "name": _("Give Up"), "cmd": "giveup_quest", "args": self.get_data_key() }) return commands def return_objectives(self): """ Get the information of all objectives. Set desc to an objective can hide the details of the objective. """ output = [] for ordinal, objective in self.objectives.items(): desc = objective["desc"] if desc: # If an objective has desc, use its desc. output.append({"ordinal": ordinal, "desc": objective["desc"]}) else: # Or make a desc by other data. obj_num = objective["number"] accomplished = self.db.accomplished.get(ordinal, 0) if objective["type"] == defines.OBJECTIVE_TALK: # talking target = _("Talk to") name = DIALOGUE_HANDLER.get_npc_name(objective["object"]) output.append({ "ordinal": ordinal, "target": target, "object": name, "accomplished": accomplished, "total": obj_num, }) elif objective["type"] == defines.OBJECTIVE_OBJECT: # getting target = _("Get") # Get the name of the objective object. object_key = objective["object"] model_name = TYPECLASS("OBJECT").model_name # Get record. try: record = WorldData.get_table_data(model_name, key=object_key) record = record[0] name = record.name except Exception as e: logger.log_err("Can not find the quest object: %s" % object_key) continue output.append({ "ordinal": ordinal, "target": target, "object": name, "accomplished": accomplished, "total": obj_num, }) elif self.objectives[ordinal][ "type"] == defines.OBJECTIVE_KILL: # getting target = _("Kill") # Get the name of the objective character. object_key = self.objectives[ordinal]["object"] model_name = TYPECLASS("OBJECT").model_name # Get record. try: record = WorldData.get_table_data(model_name, key=object_key) record = record[0] name = record.name except Exception as e: logger.log_err("Can not find the quest object: %s" % object_key) continue output.append({ "ordinal": ordinal, "target": target, "object": name, "accomplished": accomplished, "total": obj_num, }) return output def is_accomplished(self): """ All objectives of this quest are accomplished. """ for ordinal in self.objectives: obj_num = self.objectives[ordinal]["number"] accomplished = self.db.accomplished.get(ordinal, 0) if accomplished < obj_num: return False return True def turn_in(self): """ Turn in a quest, do its action. """ owner = self.db.owner # get rewards obj_list = self.loot_handler.get_obj_list(owner) if obj_list: # give objects to winner owner.receive_objects(obj_list) # do quest's action action = getattr(self.system, "action", None) if action: STATEMENT_HANDLER.do_action(action, owner, None) # remove objective objects obj_list = [] for ordinal in self.objectives: if self.objectives[ordinal]["type"] == defines.OBJECTIVE_OBJECT: obj_list.append({ "object": self.objectives[ordinal]["object"], "number": self.objectives[ordinal]["number"] }) if obj_list: owner.remove_objects(obj_list) def at_objective(self, type, object_key, number=1): """ Called when the owner may complete some objectives. Args: type: objective's type defined in defines.py object_key: (string) the key of the relative object number: (int) the number of the object Returns: if the quest status has changed. """ if type not in self.not_accomplished: return False status_changed = False index = 0 # search all object objectives while index < len(self.not_accomplished[type]): ordinal = self.not_accomplished[type][index] index += 1 if self.objectives[ordinal]["object"] == object_key: # if this object matches an objective status_changed = True # add accomplished number accomplished = self.db.accomplished.get(ordinal, 0) accomplished += number self.db.accomplished[ordinal] = accomplished if self.db.accomplished[ordinal] >= self.objectives[ordinal][ "number"]: # if this objectives is accomplished, remove it index -= 1 del (self.not_accomplished[type][index]) if not self.not_accomplished[type]: # if all objectives are accomplished del (self.not_accomplished[type]) break return status_changed
def build_object(obj_key, level=None, caller=None, reset_location=True): """ Build objects of a model. Args: obj_key: (string) The key of the object. level: (number) The object's level. caller: (command caller) If provide, running messages will send to the caller. """ # Get object's information record = None typeclass_path = None try: model_name = TYPECLASS("OBJECT").model_name try: # Get record. record = WorldData.get_table_data(model_name, key=obj_key) record = record[0] except Exception as e: ostring = "Can not get record %s in %s: %s." % (obj_key, model_name, e) print(ostring) print(traceback.print_exc()) # get typeclass model typeclass_path = TYPECLASS_SET.get_module(record.typeclass) except Exception as e: ostring = "Can not get typeclass of %s: %s." % (obj_key, e) print(ostring) print(traceback.print_exc()) pass if not record or not typeclass_path: ostring = "Can not find the data of %s." % obj_key print(ostring) print(traceback.print_exc()) if caller: caller.msg(ostring) return # Create object. try: name = getattr(record, "name", "") obj = create.create_object(typeclass_path, name) except Exception as e: ostring = "Can not create obj %s: %s" % (obj_key, e) print(ostring) print(traceback.print_exc()) if caller: caller.msg(ostring) return try: # Set data info. obj.set_data_key(record.key, level, reset_location=reset_location) obj.after_creation() except Exception as e: ostring = "Can not set data info to obj %s: %s" % (obj_key, e) print(ostring) print(traceback.print_exc()) if caller: caller.msg(ostring) return return obj
class MudderyWorldObject(TYPECLASS("OBJECT")): typeclass_key = "WORLD_OBJECT" typeclass_name = _("World Object", "typeclasses") model_name = "world_objects"
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() 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) # 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 try: # Get record. obj_model_name = TYPECLASS("OBJECT").model_name unit_record = WorldData.get_table_data(obj_model_name, key=self.unit_key) unit_record = unit_record[0] except Exception as e: logger.log_errmsg("Can not find %s's price unit %s." % (self.goods_key, self.unit_key)) return self.unit_name = unit_record.name # load goods try: obj_record = WorldData.get_table_data(obj_model_name, key=self.goods_key) obj_record = obj_record[0] goods_models = TYPECLASS_SET.get_class_modeles( obj_record.typeclass) goods_data = WorldData.get_tables_data(goods_models, key=self.goods_key) except Exception as e: logger.log_errmsg("Can not find goods %s." % self.goods_key) return self.name = goods_data["name"] self.desc = goods_data["desc"] self.icon = goods_data.get("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.goods_key, self.number): caller.msg( {"alert": _("Sorry, you can not take more %s.") % self.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.goods_key, "number": self.number}] caller.receive_objects(obj_list)
def build_unique_objects(objects_data, type_name, caller=None): """ Build all objects in a model. Args: model_name: (string) The name of the data model. caller: (command caller) If provide, running messages will send to the caller. """ # new objects new_obj_keys = set(record.key for record in objects_data) # current objects current_objs = utils.search_obj_unique_type(type_name) # remove objects count_remove = 0 count_update = 0 count_create = 0 current_obj_keys = set() for obj in current_objs: obj_key = obj.get_data_key() if obj_key in current_obj_keys: # This object is duplcated. ostring = "Deleting %s" % obj_key print(ostring) if caller: caller.msg(ostring) # If default home will be removed, set default home to the Limbo. if obj.dbref == settings.DEFAULT_HOME: settings.DEFAULT_HOME = "#2" obj.delete() count_remove += 1 continue if not obj_key in new_obj_keys: # This object should be removed ostring = "Deleting %s" % obj_key print(ostring) if caller: caller.msg(ostring) # If default home will be removed, set default home to the Limbo. if obj.dbref == settings.DEFAULT_HOME: settings.DEFAULT_HOME = "#2" obj.delete() count_remove += 1 continue try: # set data obj.load_data() # put obj to its default location obj.reset_location() except Exception as 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 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 = WorldData.get_table_data(object_model_name, key=record.key) object_record = object_record[0] typeclass_path = TYPECLASS_SET.get_module( object_record.typeclass) obj = create.create_object(typeclass_path, object_record.name) count_create += 1 except Exception as e: ostring = "Can not create obj %s: %s" % (record.key, e) print(ostring) print(traceback.print_exc()) if caller: caller.msg(ostring) continue try: obj.set_data_key(record.key) obj.after_creation() utils.set_obj_unique_type(obj, type_name) except Exception as e: ostring = "Can not set data info to obj %s: %s" % (record.key, e) print(ostring) print(traceback.print_exc()) if caller: caller.msg(ostring) continue ostring = "Removed %d object(s). Created %d object(s). Updated %d object(s). Total %d objects.\n"\ % (count_remove, count_create, count_update, len(objects_data)) print(ostring) if caller: caller.msg(ostring)
def return_objectives(self): """ Get the information of all objectives. Set desc to an objective can hide the details of the objective. """ output = [] for ordinal, objective in self.objectives.items(): desc = objective["desc"] if desc: # If an objective has desc, use its desc. output.append({"ordinal": ordinal, "desc": objective["desc"]}) else: # Or make a desc by other data. obj_num = objective["number"] accomplished = self.db.accomplished.get(ordinal, 0) if objective["type"] == defines.OBJECTIVE_TALK: # talking target = _("Talk to") name = DIALOGUE_HANDLER.get_npc_name(objective["object"]) output.append({ "ordinal": ordinal, "target": target, "object": name, "accomplished": accomplished, "total": obj_num, }) elif objective["type"] == defines.OBJECTIVE_OBJECT: # getting target = _("Get") # Get the name of the objective object. object_key = objective["object"] model_name = TYPECLASS("OBJECT").model_name # Get record. try: record = WorldData.get_table_data(model_name, key=object_key) record = record[0] name = record.name except Exception as e: logger.log_err("Can not find the quest object: %s" % object_key) continue output.append({ "ordinal": ordinal, "target": target, "object": name, "accomplished": accomplished, "total": obj_num, }) elif self.objectives[ordinal][ "type"] == defines.OBJECTIVE_KILL: # getting target = _("Kill") # Get the name of the objective character. object_key = self.objectives[ordinal]["object"] model_name = TYPECLASS("OBJECT").model_name # Get record. try: record = WorldData.get_table_data(model_name, key=object_key) record = record[0] name = record.name except Exception as e: logger.log_err("Can not find the quest object: %s" % object_key) continue output.append({ "ordinal": ordinal, "target": target, "object": name, "accomplished": accomplished, "total": obj_num, }) return output