def create_knack(self): """Attempts to create a new knack""" desc = self.rhs if not desc: raise self.error_class("You must provide a description.") try: stat, skill, name = ( self.lhslist[0], self.lhslist[1], ", ".join(self.lhslist[2:]), ) except IndexError: raise self.error_class("You must provide a stat and skill.") if not name: raise self.error_class("You must provide a name.") if self.caller.mods.get_knack_by_name(name): raise self.error_class("You already have a knack by that name.") stat, skill = stat.lower(), skill.lower() if stat not in Trait.get_valid_stat_names(): raise self.error_class("{} is not a valid stat.".format(stat)) if skill not in Trait.get_valid_skill_names(): raise self.error_class("{} is not a valid skill.".format(skill)) if any( [ knack for knack in self.caller.mods.knacks if knack.stat == stat and knack.skill == skill ] ): raise self.error_class( "You already have a knack for that skill and stat combination." ) self.caller.pay_xp(self.new_knack_cost) self.caller.mods.create_knack(name, stat, skill, desc) self.msg("You create a knack called '{}' for {}+{}.".format(name, stat, skill))
def get_stat_cost( self, # type: Retainer or Agent attr, ): """ Get the cost of a stat based on our current rating and the type of agent we are. """ from .npc_types import primary_stats from world.traits.models import Trait atype = self.agent.type stats = primary_stats.get(atype, []) base = get_stat_cost(self, attr) if attr not in stats: base *= 2 xpcost = base rescost = base if attr in Trait.get_valid_stat_names(Trait.MENTAL): restype = "economic" elif attr in Trait.get_valid_stat_names(Trait.SOCIAL): restype = "social" elif attr in Trait.get_valid_stat_names(Trait.PHYSICAL): restype = "military" else: # special stats restype = "military" return xpcost, rescost, restype
def cost_at_rank(skill, current_rating, new_rating): """Returns the total cost when given a current rating and the new rating.""" from world.traits.models import Trait cost = 0 if new_rating > current_rating: while current_rating < new_rating: current_rating += 1 if (skill in Trait.get_valid_skill_names(Trait.COMBAT) or skill in Trait.get_valid_ability_names()): mult = COMBAT_SKILL_COST_MULT else: mult = NON_COMBAT_SKILL_COST_MULT if current_rating >= 6 and skill in Trait.get_valid_skill_names(): base = LEGENDARY_COST mult //= 10 else: base = current_rating cost += base * mult return cost if new_rating < current_rating: while current_rating > new_rating: if (skill in Trait.get_valid_skill_names(Trait.COMBAT) or skill in Trait.get_valid_ability_names()): cost -= current_rating * COMBAT_SKILL_COST_MULT else: cost -= current_rating * NON_COMBAT_SKILL_COST_MULT current_rating -= 1 return cost return cost
def __getattr__(self, name): stat_names = Trait.get_valid_stat_names() if name in stat_names: return self.get_stat_value(name) other_names = Trait.get_valid_other_names() if name in other_names: return self.other.get(name, 0) raise AttributeError( f"{name} not found in {stat_names + other_names}.")
def skills(self, skills_dict: Dict[str, int]): self.wipe_all_skills() for skill, value in skills_dict.items(): trait = Trait.get_instance_by_name(skill) if not trait: names = ", ".join(Trait.get_valid_skill_names()) raise InvalidTrait( f"No trait found by '{skill}'. Valid: {names}") self._cache["skill"][skill] = self.character.trait_values.create( trait=trait, value=value)
def do_spoof_roll(self): args = self.lhs if self.rhs else self.args syntax_error = ( "Usage: <stat>/<value> [+ <skill>/<value>] at difficulty=<npc name>" ) # Split string at ' at ' args, diff_rating = self._extract_difficulty(args, syntax_error) # Split string at '+', if possible, and strip. stat_str, skill_str = self._extract_stat_skill_string( args, syntax_error) # Get Stat value stat, stat_value = self._get_values(stat_str) if stat and stat not in Trait.get_valid_stat_names(): raise self.error_class(f"{stat} is not a valid stat name.") if stat_value < 1 or stat_value > self.STAT_LIMIT: raise self.error_class( f"Stats must be between 1 and {self.STAT_LIMIT}.") # Get skill value, if applicable (None if not) skill = None skill_value = None if skill_str: skill, skill_value = self._get_values(skill_str) if skill and skill not in Trait.get_valid_skill_names(): raise self.error_class(f"{skill} is not a valid skill name.") if skill_value < 1 or skill_value > self.SKILL_LIMIT: raise self.error_class( f"Skills must be between 1 and {self.SKILL_LIMIT}.") # Will be None if not self.rhs, which is what we want. npc_name = self.rhs can_crit = "crit" in self.switches is_flub = "flub" in self.switches BaseCheckMaker.perform_check_for_character( self.caller, roll_class=SpoofRoll, stat=stat, stat_value=stat_value, skill=skill, skill_value=skill_value, rating=diff_rating, npc_name=npc_name, can_crit=can_crit, is_flub=is_flub, )
def get_stat_and_skill_from_args(self, stats_string): skill = None try: stat, skill = stats_string.split("+") stat = stat.strip().lower() skill = skill.strip().lower() except (TypeError, ValueError): stat = stats_string.strip().lower() if stat not in Trait.get_valid_stat_names(): raise self.error_class(f"{stat} is not a valid stat name.") if skill and skill not in Trait.get_valid_skill_names(): raise self.error_class(f"{skill} is not a valid skill name.") return stat, skill
def adjust_skill(self, field, value=1): if field not in Trait.get_valid_skill_names(): raise Exception( "Error in adjust_skill: %s not found as a valid skill." % field) current = self.get_skill_value(field) self.set_skill_value(field, current + value) self.character.db.trainer = None if field in Trait.get_valid_skill_names(Trait.CRAFTING): abilitylist = _parent_abilities_[field] for ability in abilitylist: if ability not in self.abilities: self.set_ability_value(ability, 1)
def get_partial_match(args, s_type): from world.traits.models import Trait # helper function for finding partial string match of stat/skills if s_type == "stat": word_list = Trait.get_valid_stat_names() elif s_type == "skill": word_list = Trait.get_valid_skill_names() else: return matches = [] for word in word_list: if word.startswith(args): matches.append(word) return matches
def clues_shared_modifier_seed(self): """Seed value for clue sharing costs""" from world.traits.models import Trait seed = 0 pc = self.char_ob for stat in Trait.get_valid_stat_names(Trait.SOCIAL): seed += pc.traits.get_stat_value(stat) # do not be nervous. I love you. <3 seed += sum([ pc.traits.get_skill_value(ob) for ob in Trait.get_valid_skill_names(Trait.SOCIAL) ]) seed += pc.traits.get_skill_value("investigation") * 3 return seed
def create_wound(self, severity=SERIOUS_WOUND): from world.traits.models import Trait trait = Trait.get_random_physical_stat() self.character.health_status.wounds.create(severity=severity, trait=trait) del self.character.health_status.cached_wounds
def get_ability_cost(caller, ability, adjust_value=None, check_teacher=True, unmodified=False): """Uses cost at rank and factors in teacher discounts if they are allowed.""" from world.traits.models import Trait current_rating = caller.traits.get_ability_value(ability) if not adjust_value and adjust_value != 0: adjust_value = 1 new_rating = current_rating + adjust_value cost = cost_at_rank(ability, current_rating, new_rating) crafting_abilities = Trait.get_valid_ability_names(Trait.CRAFTING) if ability in crafting_abilities: cost /= 2 if cost < 0: return cost if unmodified: return cost # abilities are more expensive the more we have in the same category if ability in crafting_abilities: for c_ability in crafting_abilities: cost += caller.traits.get_ability_value(c_ability) # check what discount would be if check_teacher: if caller.traits.check_training(ability, stype="ability"): cost = discounted_cost(caller, cost) return cost
def ability(accessing_obj, accessed_obj, *args, **kwargs): """ Check accessing_obj's rank in an ability to determine lock. Usage: ability(value) ability(ability_name, value) If only value is given, ability must be a property in accessed_obj that returns ability_name. """ if not args: return False if len(args) == 1: if args[0] == "all": return True name = accessed_obj.ability val = int(args[0]) else: name = args[0] val = int(args[1]) if name == "all": from world.traits.models import Trait ability_list = Trait.get_valid_ability_names(Trait.CRAFTING) else: ability_list = name.split(",") for ability_name in ability_list: ability_name = ability_name.lower().strip() try: pab = accessing_obj.traits.get_ability_value(ability_name) except AttributeError: return False if pab >= val: return True return False
def abilities(self, abilities_dict: Dict[str, int]): self.wipe_all_abilities() for ability, value in abilities_dict.items(): trait = Trait.get_instance_by_name(ability) self._cache["ability"][ ability] = self.character.trait_values.create(trait=trait, value=value)
def adjust_ability(self, field, value=1): if field not in Trait.get_valid_ability_names(): raise Exception( "Error in adjust_ability: %s not found as a valid ability." % field) current = self.get_ability_value(field) self.set_ability_value(field, current + value) self.character.db.trainer = None
def remove_last_skill_purchase_record(self, trait_name: str) -> int: trait = Trait.get_instance_by_name(trait_name) purchase = (self.character.trait_purchases.filter( trait=trait).order_by("cost").last()) if not purchase: raise ValueError("No purchase found") cost = purchase.cost purchase.delete() return cost
def adjust_stat(self, field, value=1): if field not in Trait.get_valid_stat_names(): raise Exception( "Error in adjust_stat: %s not found as a valid stat." % field) if value == 1 and self.cure_permanent_wound(field): return current = self.get_stat_value(field, raw=True) self.set_stat_value(field, current + value) self.character.db.trainer = None
def get_highest_crafting_skill(character): """Returns the highest crafting skill for character""" from world.traits.models import Trait skills = character.traits.skills return max( Trait.get_valid_skill_names(Trait.CRAFTING) + ["artwork"], key=lambda x: skills.get(x, 0), )
def mirror_physical_stats_and_skills(self, source_character): """ This is a way to destructively replace the physical stats and skills of our character with that of a source character. Eventually we'll replace this with some template system where characters can either use a template permanently or temporarily and modify it with their own values. """ for stat in Trait.get_valid_stat_names(Trait.PHYSICAL): val = source_character.traits.get_stat_value(stat) self.set_stat_value(stat, val) self.skills = dict(source_character.traits.skills)
def display_traits(self): caller = self.caller caller.msg("{wCurrent Teacher:{n %s" % caller.db.trainer) caller.msg("{wUnspent XP:{n %s" % caller.db.xp) caller.msg("{wLifetime Earned XP:{n %s" % caller.db.total_xp) all_stats = ", ".join(Trait.get_valid_stat_names()) caller.msg("\n{wStat names:{n") caller.msg(all_stats) caller.msg("\n{wSkill names:{n") caller.msg(", ".join(Trait.get_valid_skill_names())) caller.msg("\n{wDominion skill names:{n") caller.msg(", ".join(skill for skill in stats_and_skills.DOM_SKILLS)) caller.msg("\n{wAbility names:{n") crafting = Trait.get_valid_ability_names(Trait.CRAFTING) abilities = caller.traits.abilities abilities = set(abilities.keys()) | set(crafting) if caller.check_permstring("builder"): caller.msg(", ".join(Trait.get_valid_ability_names())) else: caller.msg(", ".join(ability for ability in abilities))
def set_roll(self, action): """Sets a stat and skill for action or assistant""" try: stat, skill = self.rhslist stat = stat.lower() skill = skill.lower() except (ValueError, TypeError, AttributeError): self.msg("Usage: @action/roll <action #>=<stat>,<skill>") return if ( stat not in Trait.get_valid_stat_names() or skill not in Trait.get_valid_skill_names() ): self.msg("You must provide a valid stat and skill.") return field_name = "stat_used" self.set_action_field(action, field_name, stat, verbose_name="stat") field_name = "skill_used" return self.set_action_field(action, field_name, skill, verbose_name="skill")
def set_trait_value(self, trait_type: str, name: str, value: int): """Deletes or sets up a trait value for a character, updating our cache""" # if our value is 0 or lower, delete the trait value if value <= 0 and name in self._cache[trait_type]: trait_value = self._cache[trait_type].pop(name) if trait_value.pk: trait_value.delete() else: # create/update our trait value to be the new value if name in self._cache[trait_type]: trait_value = self._cache[trait_type][name] else: trait = Trait.get_instance_by_name(name) if not trait: names = ", ".join(ob.name for ob in Trait.get_all_instances()) raise InvalidTrait( f"No trait found by '{name}'. Valid: {names}") trait_value, _ = self.character.trait_values.get_or_create( trait=trait) self._cache[trait_type][name] = trait_value trait_value.value = value trait_value.save()
def get_npc_stat_cap(atype, stat): if atype == SMALL_ANIMAL and stat in Trait.get_valid_stat_names(Trait.PHYSICAL): return 2 return 5
"mana": 1, "luck": 1, "willpower": 1, } npc_stats = { GUARD: guard_stats, THUG: guard_stats, SPY: spy_stats, ASSISTANT: assistant_stats, CHAMPION: guard_stats, ANIMAL: animal_stats, SMALL_ANIMAL: small_animal_stats, } primary_stats = { GUARD: Trait.get_valid_stat_names(Trait.PHYSICAL), THUG: Trait.get_valid_stat_names(Trait.PHYSICAL), SPY: Trait.get_valid_stat_names(Trait.SOCIAL), ASSISTANT: Trait.get_valid_stat_names(Trait.MENTAL), CHAMPION: Trait.get_valid_stat_names(Trait.PHYSICAL), ANIMAL: Trait.get_valid_stat_names(Trait.PHYSICAL), SMALL_ANIMAL: Trait.get_valid_stat_names(Trait.PHYSICAL), } guard_skills = dict([(key, 0) for key in Trait.get_valid_skill_names(Trait.COMBAT)]) guard_skills.update({"ride": 0, "leadership": 0, "war": 0}) spy_skills = dict([(key, 0) for key in Trait.get_valid_skill_names(Trait.SOCIAL)]) spy_skills.update({"streetwise": 0, "investigation": 0}) assistant_skills = dict( [(key, 0) for key in Trait.get_valid_skill_names(Trait.GENERAL)] )
def func(self): """Execute command.""" caller = self.caller switches = self.switches # try to handle possible caching errors caller.attributes._cache.pop("currently_training-None", None) caller.attributes._cache.pop("num_trained-None", None) caller.refresh_from_db() if not self.args: self.msg( "Currently training: %s" % ", ".join(str(ob) for ob in self.currently_training(caller))) self.msg("You can train %s targets." % self.max_trainees(caller)) return if not self.lhs or not self.rhs or not self.switches: caller.msg( "Usage: train/[stat or skill] <character to train>=<name of stat or skill to train>" ) return additional_cost = 0 if "retainer" in self.switches: player = caller.player.search(self.lhs) from world.dominion.models import Agent if len(self.rhslist) < 2: rhs = self.rhs else: rhs = self.rhslist[0] try: additional_cost = int(self.rhslist[1]) except ValueError: self.msg("Additional AP must be a number.") return try: if rhs.isdigit(): targ = player.retainers.get(id=rhs).dbobj else: targ = player.retainers.get(name__iexact=rhs).dbobj if not targ or not targ.pk: raise Agent.DoesNotExist except (Agent.DoesNotExist, AttributeError): self.msg("Could not find %s's retainer named %s." % (player, rhs)) return caller_msg = "You have trained %s." % targ targ_msg = "" else: targ = caller.search(self.lhs) if not targ: caller.msg("No one to train by the name of %s." % self.lhs) return if not targ.player: caller.msg( "Use the /retainer switch to train non-player-characters.") return if "stat" in switches: stat = self.rhs.lower() if not self.check_attribute_name(Trait.get_valid_stat_names(), "stat"): return if not self.check_attribute_value( caller.traits.get_stat_value(stat), targ.traits.get_stat_value(stat)): return elif "skill" in switches: skill = self.rhs.lower() if not self.check_attribute_name(Trait.get_valid_skill_names(), "skill"): return if not self.check_attribute_value( caller.traits.get_skill_value(skill), targ.traits.get_skill_value(skill), ): return elif "ability" in switches: ability = self.rhs.lower() if not self.check_attribute_name( Trait.get_valid_ability_names(), "ability"): return if not self.check_attribute_value( caller.traits.get_ability_value(ability), targ.traits.get_ability_value(ability), ): return else: caller.msg( "Usage: train/[stat or skill] <character>=<stat or skill name>" ) return caller_msg = ( "You have provided training to %s for them to increase their %s." % (targ.name, self.rhs)) targ_msg = "%s has provided you training, helping you increase your %s." % ( caller.name, self.rhs, ) if not targ.can_be_trained_by(caller): return if not self.pay_ap_cost(caller, additional_cost): return targ.post_training(caller, trainer_msg=caller_msg, targ_msg=targ_msg, ap_spent=additional_cost) return
def func(self): """ Allows the character to check their xp, and spend it if they use the /spend switch and meet the requirements. """ caller = self.caller dompc = None resource = None set_specialization = False spec_warning = False if self.cmdstring == "learn": self.switches.append("spend") if not self.args: # Just display our xp self.display_traits() return if "transfer" in self.switches: self.transfer_xp() return args = self.args.lower() # get cost already factors in if we have a trainer, so no need to check if args in Trait.get_valid_stat_names(): cost = stats_and_skills.get_stat_cost(caller, args) current = caller.traits.get_stat_value(args) if not caller.traits.check_stat_can_be_raised(args): caller.msg("%s is already at its maximum." % args) return stype = "stat" elif args in Trait.get_valid_skill_names(): current = caller.traits.get_skill_value(args) if current >= 6: caller.msg("%s is already at its maximum." % args) return if (current >= 5 and stats_and_skills.get_skill_cost_increase(caller) <= -1.0): caller.msg( "You cannot buy a legendary skill while you still have catchup xp remaining." ) return cost = stats_and_skills.get_skill_cost(caller, args) stype = "skill" elif args in stats_and_skills.DOM_SKILLS: try: dompc = caller.player.Dominion current = getattr(dompc, args) resource = stats_and_skills.get_dom_resource(args) if current >= 10: caller.msg("%s is already at its maximum." % args) return cost = stats_and_skills.get_dom_cost(caller, args) stype = "dom" except AttributeError: caller.msg("Dominion object not found.") return elif args in Trait.get_valid_ability_names(): # if we don't have it, determine if we can learn it current = caller.traits.get_ability_value(args) if not current: if args in Trait.get_valid_ability_names(Trait.CRAFTING): # check if we have valid skill: if args == "tailor" and "sewing" not in caller.traits.skills: caller.msg("You must have sewing to be a tailor.") return if (args == "weaponsmith" or args == "armorsmith" ) and "smithing" not in caller.traits.skills: caller.msg("You must have smithing to be a %s." % args) return if args == "apothecary" and "alchemy" not in caller.traits.skills: caller.msg( "You must have alchemy to be an apothecary.") return if (args == "leatherworker" and "tanning" not in caller.traits.skills): caller.msg( "You must have tanning to be a leatherworker.") return if (args == "carpenter" and "woodworking" not in caller.traits.skills): caller.msg( "You must have woodworking to be a carpenter.") return if args == "jeweler" and "smithing" not in caller.traits.skills: caller.msg("You must have smithing to be a jeweler.") return spec_warning = True elif not caller.check_permstring(args): caller.msg("You do not have permission to learn %s." % args) return else: spec_warning = False if current >= 6: caller.msg("%s is already at its maximum." % args) return if args in Trait.get_valid_ability_names(Trait.CRAFTING): spec_warning = True if current == 5: if any(key for key, value in caller.traits.abilities.items() if key in Trait.get_valid_ability_names(Trait.CRAFTING) and value >= 6): caller.msg( "You have already chosen a crafting specialization.") return else: set_specialization = True spec_warning = False stype = "ability" cost = stats_and_skills.get_ability_cost(caller, args) else: caller.msg("'%s' wasn't identified as a stat, ability, or skill." % self.args) return if "cost" in self.switches: caller.msg("Cost for %s: %s" % (self.args, cost)) return if "spend" in self.switches: # ap_cost = 5 * (current + 1) # if not self.player.pay_action_points(ap_cost): # self.msg("You do not have enough action points to spend xp on that.") # return if stype == "dom": if cost > getattr(dompc.assets, resource): msg = "Unable to buy influence in %s. The cost is %s, " % ( args, cost, ) msg += "and you have %s %s resources available." % ( getattr(dompc.assets, resource), resource, ) caller.msg(msg) return elif cost > caller.db.xp: caller.msg( "Unable to raise %s. The cost is %s, and you have %s xp." % (args, cost, caller.db.xp)) return if stype == "stat": caller.adjust_xp(-cost) caller.traits.adjust_stat(args) caller.msg("You have increased your %s to %s." % (args, current + 1)) return if stype == "skill": caller.adjust_xp(-cost) caller.traits.adjust_skill(args) skill_history = caller.db.skill_history or {} spent_list = skill_history.get(args, []) spent_list.append(cost) skill_history[args] = spent_list caller.db.skill_history = skill_history caller.msg("You have increased your %s to %s." % (args, current + 1)) if current + 1 == 6: # legendary rating inform_staff("%s has bought a rank 6 of %s." % (caller, args)) return if stype == "ability": if set_specialization: caller.msg("You have set your primary ability to be %s." % args) if spec_warning: caller.msg( "{wNote: The first crafting ability raised to 6 will be your specialization.{n" ) caller.adjust_xp(-cost) caller.traits.adjust_ability(args) ability_history = caller.db.ability_history or {} spent_list = ability_history.get(args, []) spent_list.append(cost) ability_history[args] = spent_list caller.db.ability_history = ability_history caller.msg("You have increased your %s to %s." % (args, current + 1)) return if stype == "dom": # charge them influence setattr(dompc.assets, resource, getattr(dompc.assets, resource) - cost) caller.traits.adjust_dom(args) caller.msg( "You have increased your %s influence for a cost of %s %s resources." % (args, resource, cost)) caller.refresh_from_db() return return # invalid or no switch + arguments caller.msg("Usage: xp/spend <stat, ability or skill>")
def cure_permanent_wound(self, trait_name: str) -> bool: """Returns True if we deleted a permanent wound, False otherwise""" trait = Trait.get_instance_by_name(trait_name) return self.character.health_status.heal_permanent_wound_for_trait( trait)
def record_skill_purchase(self, trait_name: str, cost): trait = Trait.get_instance_by_name(trait_name) self.character.trait_purchases.create(trait=trait, cost=cost)
def get_value_by_trait(self, trait: Trait) -> int: name = trait.name.lower() trait_type = trait.get_trait_type_display() return self.adjust_by_wounds(self._cache[trait_type][name].value, name)
def get_attr_from_args(self, agent, category="stat"): """ Helper method that returns the attr that the player is buying or displays a failure message and returns None. """ if not self.check_categories(category): return rhs = self.rhs if category == "level" and not rhs: rhs = agent.type_str if not rhs: if category == "ability": self.msg( "Ability must be one of the following: %s" % ", ".join(agent.buyable_abilities) ) return self.msg("You must provide the name of what you want to purchase.") return attr = rhs.lower() if category == "stat": stat_names = Trait.get_valid_stat_names() if attr not in stat_names: self.msg( "When buying a stat, it must be one of the following: %s" % ", ".join(stat_names) ) return return attr if category == "skill": skill_names = Trait.get_valid_skill_names() if attr not in skill_names: self.msg( "When buying a skill, it must be one of the following: %s" % ", ".join(skill_names) ) return return attr if category == "ability": if attr not in agent.buyable_abilities: self.msg( "Ability must be one of the following: %s" % ", ".join(agent.buyable_abilities) ) return return attr if category == "level": if attr not in self.retainer_types: self.msg( "The type of level to buy must be one of the following: %s" % ", ".join(self.retainer_types) ) return return "%s_level" % attr if category == "armor": return "armor" if category == "weapon": try: cats = ("weapon_damage", "difficulty_mod") attr = self.rhslist[0] if attr not in cats: self.msg("Must specify one of the following: %s" % ", ".join(cats)) return except IndexError: self.msg("Must specify a weapon field, and the weapon category.") return return attr