From 43a1aefb49b447fca20f51c991d056c9ceab9765 Mon Sep 17 00:00:00 2001 From: Howard Abrams Date: Sun, 24 Aug 2025 22:34:11 -0700 Subject: [PATCH] Add a Scoring system to character As character do things, they can now get a "score" for everything I think could be interesting/challenging. --- commands/default_cmdsets.py | 2 ++ commands/scoring.py | 49 +++++++++++++++++++++++++++++++++++++ typeclasses/alchemy.py | 3 ++- typeclasses/characters.py | 9 +++++++ typeclasses/drinkables.py | 6 ++++- typeclasses/fishing.py | 2 ++ typeclasses/objects.py | 16 ++++++++++++ typeclasses/pets.py | 3 +++ typeclasses/readables.py | 3 +++ typeclasses/sailing.py | 4 ++- typeclasses/sittables.py | 2 ++ typeclasses/things.py | 3 +++ utils/scoring.py | 24 ++++++++++++++++++ 13 files changed, 123 insertions(+), 3 deletions(-) create mode 100755 commands/scoring.py create mode 100755 utils/scoring.py diff --git a/commands/default_cmdsets.py b/commands/default_cmdsets.py index 022d0a5..0db60cb 100644 --- a/commands/default_cmdsets.py +++ b/commands/default_cmdsets.py @@ -24,6 +24,7 @@ from commands.everyone import (CmdTake, CmdThink, CmdSay, CmdWhisper, CmdRead, CmdEat, CmdDrink, CmdUse, CmdPush, CmdPull, CmdOpen, CmdClose, CmdFeed) +from commands.scoring import CmdScore from commands.hint import CmdHint from commands.misc import CmdLight from commands.wizards import (CmdGM, CmdSpell, CmdGMTrigger, @@ -70,6 +71,7 @@ class CharacterCmdSet(default_cmds.CharacterCmdSet): self.add(CmdMakeCocktail) self.add(CmdGift) self.add(CmdFeed) + self.add(CmdScore) class AccountCmdSet(default_cmds.AccountCmdSet): diff --git a/commands/scoring.py b/commands/scoring.py new file mode 100755 index 0000000..eb8ede6 --- /dev/null +++ b/commands/scoring.py @@ -0,0 +1,49 @@ +#!/usr/bin/env python + +from evennia import CmdSet + +from commands.command import Command +from utils.scoring import Scores + + +class CmdScore(Command): + """ + Display your score. + + Usage: + + score + + The more things you do in this game, the better your scorecard. + """ + key = "score" + aliases = ["scorecard"] + + def func(self): + """ + Implements the score command. + """ + def checked(tick): + return " |g✔|n " if score & tick.value else " |r-|n " + + score = self.caller.attributes.get('score', 0) + total = len([1 for tick in list(Scores) if score & tick.value]) + + msg = "Your scorecard is:|/" + "|/".join([ + checked(Scores.jump) + "Jumped in a puddle", + checked(Scores.moss_sit) + "Sat on some moss", + checked(Scores.throw_stick) + "Thrown a stick", + checked(Scores.catch_fish) + "Caught a fish", + checked(Scores.feed_bhb) + "Fed Big Hairy Beast", + checked(Scores.eat_candy) + "Eaten a candy", + checked(Scores.get_cocktail) + "Got a cocktail", + checked(Scores.finish_cocktail) + "Finished a cocktail", + checked(Scores.make_tea) + "Made a pot of tea", + checked(Scores.read_a_book) + "Read a book from Dabbler's library", + checked(Scores.blow_horn) + "Called on the Mist Horn", + checked(Scores.get_pipe) + "Received a smoking pipe", + checked(Scores.get_blue_medal) + "Found the Blue Medal", + checked(Scores.make_potion) + "Made a potion", + ]) + f"|/Total: {total}" + + self.caller.msg(msg) diff --git a/typeclasses/alchemy.py b/typeclasses/alchemy.py index 401aedd..a477c0e 100755 --- a/typeclasses/alchemy.py +++ b/typeclasses/alchemy.py @@ -12,7 +12,7 @@ from commands.command import Command from typeclasses.objects import Object from typeclasses.scripts import Script from typeclasses.drinkables import Container - +from utils.scoring import Scores class CmdEmpty(Command): """ @@ -241,6 +241,7 @@ class Cauldron(Object): seq, brew_func = self.can_create_laughter() if seq: + maker.score(Scores.make_potion) maker.announce_action(good) for idx, msg in enumerate(seq): delay(idx * 3 + 2, maker.announce_action, msg) diff --git a/typeclasses/characters.py b/typeclasses/characters.py index f09adbc..005b67f 100644 --- a/typeclasses/characters.py +++ b/typeclasses/characters.py @@ -105,6 +105,14 @@ class Character(Object, GenderCharacter, ContribRPCharacter): if obj.is_typeclass(typeclass): obj.delete() + def score(self, tick): + """ + Convenience method for keeping track of activities. + """ + score = self.attributes.get('score', 0) + score |= tick.value + self.attributes.add('score', score) + def new_account_setup(self): """ New accounts should connect the tutorial. @@ -137,6 +145,7 @@ class Character(Object, GenderCharacter, ContribRPCharacter): # Reset the "running state" that character may have # experienced. This way, new guests get to _re-experience_ it: + self.db.score = 0 self.db.visited = False self.db.jumped_times = 0 self.db.tutorstate = 0 diff --git a/typeclasses/drinkables.py b/typeclasses/drinkables.py index b26e720..91b1696 100755 --- a/typeclasses/drinkables.py +++ b/typeclasses/drinkables.py @@ -5,6 +5,7 @@ from evennia.utils import logger from typeclasses.objects import Object from commands.consumables import CmdSetTeapot, CmdSetFillable +from utils.scoring import Scores from utils.word_list import routput, choices import re @@ -282,6 +283,7 @@ class Teapot(Object): self.db.tea_choice = tea_choice self.db.desc = f"A large, brown teapot full of {desc}." drinker.announce_action(f"$You() $conj(make) a teapot of {desc}.") + drinker.score(Scores.make_tea) def do_fill(self, drinker): teatype = self.db.tea_choice @@ -517,9 +519,10 @@ class Cocktail(Object): pre_msg = details.get("pre", "") drink.location.msg(routput(pre_msg + " The << bartender ^ barkeep ^ elf >> << passes ^ slides ^ gives ^ hands >> you a |y{0}|n.", drink.name)) + owner.score(Scores.get_cocktail) theroom = drink.location.location - char = owner.db._sdesc or owner.get_display_name(theroom) + char = owner.sdesc.get() or owner.get_display_name(theroom) msg = routput(pre_msg + " The << bartender ^ barkeep ^ elf >> << passes ^ slides ^ gives ^ hands >> a {0} to {1}.", drink.db.cocktail_type, char) theroom.msg_contents(msg, exclude=owner) @@ -537,6 +540,7 @@ class Cocktail(Object): drinker.msg(choices(DRINK_COCKTAIL_MSGS, self.key, cocktail_type, flavor)) elif amount > 0: drinker.msg(choices(self.db.effects, self.key, cocktail_type)) + drinker.score(Scores.finish_cocktail) else: self.key = f"{self.db.cocktail_type} glass" self.aliases.add('glass') diff --git a/typeclasses/fishing.py b/typeclasses/fishing.py index 2918805..0ba759e 100755 --- a/typeclasses/fishing.py +++ b/typeclasses/fishing.py @@ -15,6 +15,7 @@ from urllib.request import urlopen from typeclasses.objects import Object from typeclasses.characters import Character from typeclasses.npcs import CarriableNPC +from utils.scoring import Scores from utils.word_list import routput import random @@ -238,3 +239,4 @@ class FishingPole(Object): })[0] fish.location = fisher fisher.msg(f"You caught a fish!") + fisher.score(Scores.catch_fish) diff --git a/typeclasses/objects.py b/typeclasses/objects.py index 8631e72..9f0c081 100755 --- a/typeclasses/objects.py +++ b/typeclasses/objects.py @@ -19,6 +19,7 @@ from evennia.prototypes.spawner import spawn from evennia.utils import delay, logger from evennia.utils.search import search_object +from utils.scoring import Scores from utils.word_list import routput @@ -562,6 +563,21 @@ class Listener: c.adjust_coins(int(m.group(1))) return + m = match(r"score_all ([a-z_]+)", cmd) + if m: + tick = Scores[m.group(1)] + for c in self.characters_here(puppets=True): + c.score(tick) + return + + m = match(r"score ([a-z_]+) *?( to|=)? *([a-z_]+)", cmd) + if m: + tick = Scores[m.group(1)] + c = self.search(m.group(3)) + if c: + c.score(tick) + return + if self.is_typeclass("typeclasses.characters.Character"): self.execute_cmd(cmd) else: diff --git a/typeclasses/pets.py b/typeclasses/pets.py index 6b368e3..0ffcdf4 100755 --- a/typeclasses/pets.py +++ b/typeclasses/pets.py @@ -23,6 +23,7 @@ from evennia.utils.search import search_object from typeclasses.objects import Object from typeclasses.characters import Character from commands.pets import CmdPetSet +from utils.scoring import Scores from utils.word_list import squish, choices @@ -627,10 +628,12 @@ class BHB(Friendly): msg = f"{noun} {how_sniff} sniffs $your() outstretched arm holding a scone. It {how_eat} eats it, and {and_then}." self.adjust_character(feeder, 100) feeder.has('scone').delete() + feeder.score(Scores.feed_bhb) elif is_berry(item): msg = f"{noun} {how_sniff} sniffs $your() outstretched arm with a handful of berries. It {how_eat} eats them, and {and_then}." self.adjust_character(feeder, 40) feeder.has('berries').delete() + feeder.score(Scores.feed_bhb) else: msg = f"{noun} doesn't appear interested in anything you have." diff --git a/typeclasses/readables.py b/typeclasses/readables.py index 9b7311c..98ee26d 100755 --- a/typeclasses/readables.py +++ b/typeclasses/readables.py @@ -9,6 +9,7 @@ from evennia.prototypes.spawner import spawn from typeclasses.objects import Object from typeclasses.consumables import Producer +from utils.scoring import Scores from utils.user_info import location from utils.word_list import routput @@ -135,6 +136,7 @@ class WriteableBook(Book): self.db.post_write_msg or "$You() $conj(put) down the quill, and $conj(stop) writing.") + writer.score(Scores.sign_guest_book) class Journal(WriteableBook): """ @@ -315,6 +317,7 @@ class Bookshelf(Producer): book.location = reader reader.msg(f"You pick up {filler} book, |w{title}|n.") + reader.score(Scores.read_a_book) self.db.last_title = None self.db.last_desc = None diff --git a/typeclasses/sailing.py b/typeclasses/sailing.py index a54052a..231ed26 100755 --- a/typeclasses/sailing.py +++ b/typeclasses/sailing.py @@ -6,10 +6,11 @@ from re import match from evennia import CmdSet from evennia.utils import logger, delay, int2str, search +from commands.command import Command from typeclasses.puzzles import StoryCube from typeclasses.scripts import Script from typeclasses.objects import Object -from commands.command import Command +from utils.scoring import Scores PORT = "Lazy Dock" PORT_EXIT = "dock" @@ -70,6 +71,7 @@ class CallingHorn(Object): blower.announce_action("$You() $conj(blow) $pron(your) horn " "and $conj(create) a long, resonating " "sound echoing over the water.") + blower.score(Scores.blow_horn) script_results = search.scripts("sailing") if script_results: diff --git a/typeclasses/sittables.py b/typeclasses/sittables.py index 89b5a49..bc5b244 100755 --- a/typeclasses/sittables.py +++ b/typeclasses/sittables.py @@ -2,6 +2,7 @@ from typeclasses.objects import Object from commands.sittables import CmdSetSit +from utils.scoring import Scores from utils.word_list import routput @@ -50,6 +51,7 @@ class Sittable(Object): self.db.sitter = sitter sitter.db.is_sitting = self + sitter.score(Scores.moss_sit) sitter.announce_action(f"$You() $conj(sit) {adjective} {article} {self.key}.") sitter.location.other_sit(sitter) diff --git a/typeclasses/things.py b/typeclasses/things.py index 9b43606..4ff020b 100755 --- a/typeclasses/things.py +++ b/typeclasses/things.py @@ -23,6 +23,7 @@ from commands.misc import (CmdSetPuddle, from commands.consumables import CmdSetMakeConsumable from commands.wizards import CmdSetWand from utils.word_list import routput, choices, paragraph +from utils.scoring import Scores from typeclasses.consumables import Litterable from typeclasses.objects import Object from typeclasses.scripts import KnockScript @@ -339,6 +340,7 @@ class Stick(Object): self.cmdset.add_default(CmdSetStick) def do_throw(self, thrower): + thrower.score(Scores.throw_stick) beast = thrower.location.search('beast') if beast and beast.db.is_awake: beast.thrown_stick(thrower) @@ -369,6 +371,7 @@ class Puddle(Object): self.cmdset.add_default(CmdSetPuddle) def do_jump(self, player): + player.score(Scores.jump) player.db.jumped_times = (player.db.jumped_times or 0) + 1 if player.db.jumped_times == 1: player.msg("You jump in the puddle! " diff --git a/utils/scoring.py b/utils/scoring.py new file mode 100755 index 0000000..a11d1c5 --- /dev/null +++ b/utils/scoring.py @@ -0,0 +1,24 @@ +#!/usr/bin/env python + +from enum import Enum + + +class Scores(Enum): + """ + Keep track of the scores we can do. + """ + jump = 2**0 + throw_stick = 2**1 + catch_fish = 2**2 + feed_bhb = 2**3 + blow_horn = 2**4 + make_tea = 2**5 + read_a_book = 2**6 + eat_candy = 2**7 + get_pipe = 2**8 + get_blue_medal = 2**9 + get_cocktail = 2**10 + finish_cocktail = 2**11 + make_potion = 2**12 + sign_guest_book = 2**13 + moss_sit = 2**14