From 43956fa81836f8948eb781156ccd6a0c885f63e4 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Mon, 7 Apr 2025 22:45:45 -0700 Subject: [PATCH] Create a friendly pet --- typeclasses/pets.py | 241 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 240 insertions(+), 1 deletion(-) diff --git a/typeclasses/pets.py b/typeclasses/pets.py index a20e02d..b497da2 100755 --- a/typeclasses/pets.py +++ b/typeclasses/pets.py @@ -1,4 +1,6 @@ #!/usr/bin/env python +# Emacs environement +# (setq python-shell-interpreter "/Users/howard/src/moss-n-puddles/.venv/bin/ipython") """ Pets @@ -194,5 +196,242 @@ class Fire(Pet): # def at_post_move(self, source_location, move_type, **kwargs): - self.update_hunger(feeder=feeder, amount=300) +# ---------------------------------------------------------------------- +# Friendly + +class Reaction(Enum): + SCARED = 1 + CONCERNED = 100 + INTERESTED = 300 + FRIENDLY = 850 + ECSTATIC = 1000 + + +class Friendly(Pet): + """ + This pet keeps track of the characters in the game. + It has different reactions based on the characters in the room. + """ + + def at_object_creation(self): + """ + Called when object is first created. + """ + super().at_object_creation() + + # The higher this value the more "spammy" a pet is in making + # comments in the room: + self.db.active_amount = 3 + + # If set to 'ignores', the pet is more concerned about the + # 'friendliest' character in Room, otherwise, it is more + # scared at the stranger: + self.db.new_character_reaction = "ignores" + + @property + def friendly_var(self): + """ + Return variable name on character types use to gauge the + reaction of this pet towards that character. + """ + key = self.key.replace(" ", "_") + return f"{key}_friendly_level" + + def friendly_level(self, character): + """ + Return reaction level of this pet towards a character. + """ + varname = self.friendly_var + return character.attributes.get(varname) or 0 + + def friendly_reaction(self, character=None): + """ + Return reaction enum of this pet towards a character. + If character not given, then looks at all characters in the room + """ + if character: + level = self.friendly_level(character) + if level < Reaction.SCARED.value: + return Reaction.SCARED + if level < Reaction.CONCERNED.value: + return Reaction.CONCERNED + if level < Reaction.INTERESTED.value: + return Reaction.INTERESTED + if level < Reaction.FRIENDLY.value: + return Reaction.FRIENDLY + return Reaction.ECSTATIC + else: + if self.db.new_character_reaction == "ignores": + return self.highest_friendly_reaction() + return self.lowest_friendly_reaction() + + @property + def local_characters(self): + """ + Return a list of all Characters in the room with the Pet. + """ + return [c for c in self.location.contents + if c.is_typeclass("typeclasses.characters.Character")] + + def lowest_friendly_reaction(self): + """ + Return reaction of this pet to the least friendliest + character(s) in the area (room) that this pet resides. + Returns a tuple, Reaction and a list of characters. + """ + # State is a tuple of the level and the character: + level = Reaction.ECSTATIC + characters = [] + + for c in self.local_characters: + this_level = self.friendly_reaction(c) + if this_level.value < level.value: + level = this_level + characters = [c] + if this_level.value == level.value: + characters += [c] + return (level, characters) + + def highest_friendly_reaction(self): + """ + Return reaction of this pet to the friendliest + character(s) in the area (room) that this pet resides. + Returns a tuple, Reaction and a list of characters. + """ + # State is a tuple of the level and the character: + level = Reaction.SCARED + characters = [] + + for c in self.local_characters: + this_level = self.friendly_reaction(c) + if this_level.value > level.value: + level = this_level + characters = [c] + if this_level.value == level.value: + characters += [c] + return (level, characters) + + def adjust_all(self, amount): + """ + Adjusts reaction level to all characters that have + interacted with this pet, whether they are near it or not. + This is essentially a loneliness measure, for out-of-sight, + out-of-mind. + """ + for c in Character.objects.get_objs_with_attr(self.friendly_var): + self.adjust_character(c, amount) + + def adjust_all_locally(self, amount): + """ + Adjusts reaction level to all characters in the room + with this pet. Hanging out with the pet should be helpful. + """ + for c in self.local_characters: + self.adjust_character(c, amount) + + def adjust_character(self, character, amount): + """ + Adjusts the reaction to 'character' by an 'amount. + Note that this should never go below zero. + """ + new_val = self.friendly_level(character) + amount + if new_val < 0: + new_val = 0 + character.attributes.add(self.friendly_var, new_val) + + def return_appearance(self, looker, **kwargs): + """ + Called by the 'look' command. This formats the description + of this object based on 'reaction', and the character's + _friendly_ level. + + Args: + looker (Object): Object doing the looking. + """ + level = self.friendly_reaction(looker) + # looking at the friendly pets makes them nervous... just a little: + self.adjust_character(looker, -1) + + if level == Reaction.SCARED: + return self.db.desc + " " + choices(self.db.scared_msg or "It seems scared of you.") + elif level == Reaction.CONCERNED: + return self.db.desc + " " + choices(self.db.concerned_msg or "It seems concerned you are here.") + elif level == Reaction.INTERESTED: + return self.db.desc + " " + choices(self.db.interested_msg or "It seems interested in you.") + elif level == Reaction.FRIENDLY: + return self.db.desc + " " + choices(self.db.friendly_msg or "It seems happy to see you.") + else: + # If we have an ecstatic message, use it otherwise, grab the friendly: + return self.db.desc + " " + choices(self.db.ecstatic_msg or self.db.friendly_msg or + "It seems ecstatic to see you.") + + def update_state(self, *args, **kwargs): + """ + Hrm. + """ + super().update_state(*args, **kwargs) + self.adjust_all(self.db.loneliness_amount or -1) + self.adjust_all_locally(self.db.shyness_amount or 5) + + # How spammy do we want the pet to be? + if random.randint(0, 100) < self.db.active_amount: + self.do_action() + else: + print("Nope") + + def do_action(self): + # Do something based on the highest friendly level is the same area! + (level, chars) = self.friendly_reaction() + focus = random.choice(chars) + + if level == Reaction.SCARED: + msg = choices(self.db.scared_actions) + elif level == Reaction.CONCERNED: + msg = choices(self.db.concerned_actions) + elif level == Reaction.INTERESTED: + msg = choices(self.db.interested_actions) + elif level == Reaction.FRIENDLY: + msg = choices(self.db.friendly_actions) + else: + # If we have an ecstatic message, use it otherwise, grab the friendly: + msg = choices(self.db.ecstatic_actions or self.db.friendly_actions) + + focus.msg( + sub("", "You", sub("", "you", msg)) + ) + self.location.msg_contents(sub("<[Yy]ou>", focus.name.title(), msg), + exclude=focus) + + +class Imp(Friendly): + def action(self): + # Do something based on the highest friendly level is the same area! + # Need a ticker .. + # (state, chars) = self.highest_friendly_level() + pass + + def update_state(self, *args, **kwargs): + pass + + +class BHB(Friendly): + "The Big Hairy Beast object." + def action(self): + # Do something based on the highest friendly level is the same area! + # Need a ticker .. + # (state, chars) = self.highest_friendly_level() + pass + # - < 10 : Attempts to hide + # - < 20 : Steers clear but doesn’t hide + # - < 50 : Wags it tail/butt + # - > 50 : Follows you + # level = self.friendly_level(looker) + # if level == 0: + # return f"You see a massive shadow lurking " + # if level < 10: + # return f"{self.db.desc} {self.hunger_appearance()}" + + def given(self, giver, thing): + # if obj.is_typeclass("typeclasses.things.Wood"): + return True