def _discard_aliases(self, item, message=None): super()._discard_aliases(item) item.holder = None if self is G.player.inventory: message = message or f"You lose possession of {item.name}." if message is not None: say.insayne(message)
def _resolve_attack(attacker, attack): # TODO: Add equipment, different damage dice, etc. # TODO: Respect attack.method. defender = attack.target is_player = attacker is G.player if is_player: subj, obj = ["you", defender.name] else: subj, obj = [attacker.name, "you"] subj = say.capitalized(subj) miss = "miss" if is_player else "misses" hit = "hit" if is_player else "hits" strength_mod = int((attacker.strength.value - 10) / 2) to_hit = strength_mod + dice.roll("1d20") if to_hit < (10 + (defender.stamina.value - 10) / 2): say.insayne(f"{subj} {miss}.") else: damage = dice.roll("1d8") + strength_mod # TODO: How to organize messages better? Death also creates text, so # there should be a way to make sure the messages are ordered. say.insayne(f"{subj} {hit} {obj} for {damage} damage!") # TODO: Attack should have associated text which is consulted here. defender.health.heal_or_harm( -1 * damage, cause=f"the fins of {say.a(attacker.name)}") if not (is_player or defender.alive): G.just_died = True
def ability(ability): ability_name = ability the_ability = G.player.abilities.get(ability_name) if the_ability is None: say.insayne("You know no such ability.") else: the_ability.activate()
def librarian_death_throes(librarian): say.insayne( "The librarian grins impossibly wide. A thin rivulet of blood " "appears between his teeth. His eyes roll back and, with a giggle, " "he falls backward onto the ground as though reclining on a divan." ) say.insayne("The edge of a hidebound book peeks from his rags.")
def random_action(): skip = [ "?", "help", "quit", "random", "suicide", "north", "south", "east", "west", None, ] filtered_commands = [ c for c in when.CommandHandler.COMMANDS if c[0].prefix not in skip ] random_command = random.choice(filtered_commands) command_pattern = random_command[0] entity_names = ([npc.name for npc in G.player.current_room.npcs] + [corpse.name for corpse in G.player.current_room.corpses] + [item.name for item in G.player.inventory] + [item.name for item in G.player.current_room.items] + list(G.player.current_room._exits)) args = [ random.choice(entity_names) for i in range(len(command_pattern.arguments)) ] final_command = f"{''.join(command_pattern.prefix)} {' '.join(args)}" say.insayne(f"random command: {final_command}", insanity=0) when.handle(final_command)
def cheat(code): # TODO: Make healing more general. matches = [] for pattern, function in [ (_HEAL_PATTERN, _heal_stat), (_STAT_PATTERN, _modify_stat), (_ABILITY_PATTERN, _cheat_ability), ]: match = pattern.search(code) if match is not None: matches.append((function, match)) break for function, match in matches: logging.debug("function is %s" % function) logging.debug("match.groups is %s" % match.groups()) try: function(*match.groups()) break except _CheatException: pass else: say.insayne("You attempt to pry open cosmic mysteries but fail. Your " "pitiful mind reels with the effort.") G.player.insanity.heal_or_harm(15)
def _add_aliases(self, item, message=None): super()._add_aliases(item) logging.debug("_add_aliases called with %s", item) item.holder = self if self is G.player.inventory: message = message or f"You acquire {item.name}." if message is not None: say.insayne(message)
def test_no_alteration_when_insanity_low(self, mock_stdout): original_text = "There is no death in all the world." say.insayne(original_text, insanity=0) augmented = mock_stdout.getvalue() # whimsylib.say.output freely introduces newlines. augmented = re.sub(r"\n", r" ", augmented) _, edits = _sifted_edits(original_text, augmented) edited_edits = re.sub(r"\s*", r"", edits) self.assertEqual("", edited_edits)
def go(direction): direction = direction.lower() next_room, the_direction = G.player.current_room.exit(direction) if next_room is None: say.insayne(f"It is not possible to proceed {direction}.") else: say.insayne(f"You proceed {the_direction.display_description}.") G.player.current_room.on_exit() enter_room(next_room)
def read(self, actor): if actor.has_read(self): # TODO: Condition pronoun/verb agreement on whether actor is # player or not. Make a utility function for this. say.insayne( "You have already tasted the wisdom distilled in this book.") else: actor.add_ability(ability.meditation()) actor.set_has_read(self)
def execute(self): # TODO: This kind of thing could be handled with a description generator. say.insayne( "You are in a dark tube. The walls and floor quiver at your touch, " "and you realize this is the intestine of a vast behemoth.") G.player.insanity.heal_or_harm(10) # TODO: change to self.room.description G.player.current_room.description = ( "The walls and floor of the intestine room shudder at your step.") self._will_execute = False
def test_sets_zalgochance_when_insanity_high(self, mock_stdout): # TODO: Refactor the zalgotext library to be more testable. original_text = "some text" say.insayne(original_text, insanity=100) augmented = mock_stdout.getvalue() # whimsylib.say.output freely introduces newlines. augmented = re.sub(r"\n", r" ", augmented) _, edits = _sifted_edits(original_text, augmented) edited_edits = re.sub(r"[A-Z]*", r"", edits) self.assertNotEqual("", edited_edits)
def use_computer(consumer): insanity = _G.player.insanity.value desc = extra_description.get_interval( insanity, extra_description.use_computer_descriptions ) say.insayne(desc) if insanity >= 10 and insanity < 20: _G.player.insanity.modify(2) elif insanity >= 25: _G.player.health.heal_or_harm(-10)
def test_output_reconstructable(self, mock_stdout): original_text = ( "When Gregor Samsa awoke one morning from unsettling dreams, " "he found himself changed in his bed into a monstrous vermin.") say.insayne(original_text, insanity=100) augmented = mock_stdout.getvalue() # whimsylib.say.output freely introduces newlines. augmented = re.sub(r"\n", r" ", augmented) reconstructed, _ = _sifted_edits(original_text, augmented) self.assertEqual(original_text, reconstructed)
def read(book): book_name = book _, book = _find_available_item(book_name) if book is None: say.insayne(f"There is no {book_name} here to read.") elif not isinstance(book, Book): say.insayne( f"You stare intently at the {book_name} but, alas, fail to read it." ) else: book.read(G.player)
def die(self, cause=None): if self is G.player: cause = cause or self.health.last_cause G.cause_of_death = cause say.insayne("You die.") say.insayne("...") raise tartarus.RaptureException("") self.alive = False self._death_throes(self) self.current_room.characters.remove(self) self.current_room.corpses.add(self)
def _look(): if G.player.current_room.description: say.insayne(G.player.current_room.description) # TODO: Bespoke descriptions for all items and characters. for item in G.player.current_room.items: say.insayne(item.idle_description) for character in G.player.current_room.npcs: say.insayne(character.idle_text) for corpse in G.player.current_room.corpses: say.insayne(f"The corpse of {say.a(corpse.name)} molders here.") say.insayne(f'Exits are {", ".join(G.player.current_room.display_exits)}.')
def drop(item): item_name = item item = G.player.inventory.find(item_name) if item is None: say.insayne(f"You don't have {say.a(item_name)} to drop.") else: _move_item( G.player.inventory, G.player.current_room.items, item, relinquishment_message=f"You drop the {item_name} on the ground.", )
def execute(self): self._counter += 1 if self.room is not G.player.current_room: return if self._counter % self._TURNS_TO_CHIME == 0: say.insayne( "Suddenly, the bells begin to chime in simultaneity, if not " "exactly unison. From the chaos can be discerned a discordant " "melody. It is a blasphemous all-bells rendition of Hanson's " "'MMMBop.' As the final incomprehensible peal subsides, you " "realize the song is stuck in your head, threatening the " "sinews of your very sanity.") G.player.insanity.heal_or_harm(15) G.add_event(_SongInHeadEvent(), "pre")
def use(item, actor, verb): item_name = item actor_name = actor # Inventory item case item = G.player.inventory.find(item_name) if item: if actor_name: actor = _get_present_actor(actor_name) if actor: try: item.consume(actor) except AttributeError: say.insayne( f"You can't {verb} the {item_name} on {actor_name}.") else: try: item.consume(G.player) except AttributeError: say.insayne(f"You can't {verb} the {item_name}.") return # NPC case item = G.player.current_room.npcs.find(item_name) if item: try: item.consume(G.player) except AttributeError: say.insayne(f"You can't {verb} the {item_name}.") return say.insayne(f"You don't have {say.a(item_name)}.")
def execute(self): # TODO: Should not be G.player--what if somebody else wants a smoke? if _G.player.inventory.find("cigarette") or _G.player.inventory.find( "stub" ): say.insayne( "The smoker clucks his tongue. \"You've already got a " 'smoke; why are you trying to bum one off me?"' ) else: cigarette = random.choice( [items.Cigarette, items.CigaretteStub] ).create() say.insayne( '"Here you go," says the smoker between puffs. "Have a ' "smoke with me. It's all there is to do here, man. Just " 'that and wait to die and live again."' ) _G.player.inventory.add(cigarette) lighter = npc.inventory.find("lighter") if lighter is not None: say.insayne('"Here, you\'ll need this, too."') npc.inventory.remove(lighter) _G.player.inventory.add(lighter) say.insayne('"No smoke without fire."')
def test_interpolates_voices_when_insanity_high(self, mock_stdout): # TODO: Refactor the zalgotext library to be more testable. original_text = "no, not a text, not a text at all" say.insayne(original_text, insanity=100) augmented = mock_stdout.getvalue() # whimsylib.say.output freely introduces newlines. augmented = re.sub(r"\n", r" ", augmented) _, edits = _sifted_edits(original_text, augmented) edited_edits = re.sub(r"[^A-Z]*", r"", edits) with open("/tmp/output.txt", "w") as outp: outp.write(mock_stdout.getvalue()) self.assertIsNotNone( re.search( r"^(HEEEHEHEHEHEHE|THEREISNOHOPE|DIDYOUHEARTHAT?" "|IPROMISEYOUKNOWLEDGE)+$", edited_edits, ))
def handle(cls, command): command = command.lower().strip() max_matches = -1 call_me = None func_me = None for template, function, kwargs in cls._COMMANDS: match = template.match(command) if match is not None: call_kwargs = match.groupdict() if len(call_kwargs) > max_matches: max_matches = len(call_kwargs) call_kwargs.update(kwargs) call_me = call_kwargs func_me = function if func_me is not None: func_me(**call_me) else: say.insayne(f'I don\'t understand "{command}."')
def use_office_copier(consumer): insanity = _G.player.insanity.value if insanity < 20: say.insayne("The copier seems to be broken, as usual.") elif insanity < 29: bodyparts = ["fingers", "tongues", "arms", "lips", "nipples"] say.insayne( "The copier moans lasciviously. You feel its hot breath on your neck. It envelops you, and penetrates you." ) say.insayne( "Abruptly, orgasmically, new " f"{random.choice(bodyparts)} erupt from your skin." ) _G.player.insanity.modify(3) else: say.insayne("The copier sighs.") say.insayne( "You hear a meaty thumping coming from the direction of the meeting room." )
def execute(self): say.insayne("The writhing mass speaks, many voices merging into one:") say.insayne( '"Hey champ! How are those quarterly targets coming along? Working hard or hardly working?"' ) say.insayne("The writhing mass cackles.") say.insayne( "\"You should go see Gary! Now there's a team player. Maybe do him a favor! Lord knows he's done enough for you...\"" ) insanity = _G.player.insanity.value if insanity == 29: _G.player.insanity.modify(1)
def stats(): lines = [] name_str = f"{G.player.name}" lines.append(name_str) # TODO: Unfuck this for zalgo. lines.append("".join("-" for _ in name_str)) for stat in G.player.all_stats(): if hasattr(stat, "maximum"): lines.append(f"{stat.name:10}: {stat.value}/{stat.maximum}") else: lines.append(f"{stat.name:10}: {stat.value}") lines.append("".join("-" for _ in name_str)) lines.append("- abilities -") for ability_name, ability in sorted(G.player.abilities.items()): if ability_name: lines.append(f"{ability_name}: {ability.DESCRIPTION}") for i, line in enumerate(lines): add_newline = i == 0 say.insayne(line, add_newline=add_newline)
def consume(self, consumer): if consumer is _G.player: say.insayne("You drink the coffee.") consumer.insanity.modify(-5) elif consumer.name == "gary": insanity = _G.player.insanity.value if insanity >= 30: # Trigger Office ending say.insayne( "With a horrific tearing sound and a wet pop, Gary vanishes from existence. " "In his place is a perfectly Gary-shaped hole in space.") say.insayne("YOU MADE IT!") # TODO: make "enter gary" action or just teleport. consumer.current_room.characters.remove(consumer) else: say.insayne( 'Gary loudly slurps the coffee. "Well thanks, pal!"') else: say.insayne("Nothing happens.") _G.player.inventory.remove(self)
def take(item): item_name = item location, item = _find_in_room(item_name) if location is None or item is None: if G.player.current_room.npcs.find(item_name): say.insayne("You cannot take sentient beings.") elif G.player.current_room.corpses.find(item_name): say.insayne("The corpse would be too burdensome to carry.") else: say.insayne(f"There is no {item_name} here to take.") elif not item.obtainable: say.insayne("You can't take the {item_name}.") else: _move_item(location, G.player.inventory, item)
def consume(self, consumer): if not consumer.inventory.find("lighter"): if consumer is _G.player: say.insayne(f"You have no way to light the {self.name}.") return consumer.inventory.remove(self) cigarette_butt = CigaretteButt.create() consumer.inventory.add(cigarette_butt) consumer.current_room.items.add(Smoke.create(consumer.current_room)) if consumer is _G.player: say.insayne( f"You take a furtive puff on the {self.name}. It tastes foul " "and acrid. You do not feel like you are wearing a leather " "jacket at all.") consumer.psyche.heal_or_harm(dice.roll("1d2")) # TODO: Make insanity a variable statistic? consumer.insanity.heal_or_harm(-dice.roll("1d2")) # TODO: Interesting problem with how this is implemented: # because text is not queued but printed directly, if this line # precedes anything else in this function and player dies, # weird stuff will ensue. consumer.health.heal_or_harm(-dice.roll("2d2"), cause="smoking half a cig") else: name = say.capitalized(consumer.name) say.insayne(f"{name} puffs furtively on a {self.name}.")
def consume(self, consumer): if not consumer.inventory.find("lighter"): if consumer is _G.player: say.insayne(f"You have no way to light the {self.name}.") return # TODO: Buff strength for a little bit. # TODO: Heal insanity, restore psyche. # TODO: I don't like this solution as it presumes the item is in the # consumer's inventory. Maybe that is a fine assumption. If not, # consider storing the inventory relationship as two-way. consumer.inventory.remove(self) cigarette_stub = CigaretteStub.create() consumer.inventory.add(cigarette_stub) consumer.current_room.items.add(Smoke.create(consumer.current_room)) # TODO: Customize text based on whether consumer is player. # TODO: Add location to actors so that the state of onlookers can # be properly assessed. aliases = random.sample(self.aliases, 2) if consumer is _G.player: say.insayne( f"You take a long, smooth drag on the {aliases[0]}. Time seems " "to mellow; all activity nearby slows. Onlookers watch as you " "draw measured, pensive little puffs from the delicious " f"{aliases[1]}. You look very cool.") consumer.health.heal_or_harm(-dice.roll("1d2"), cause="being cool") consumer.psyche.heal_or_harm(dice.roll("2d2")) # TODO: Make insanity a variable statistic? consumer.insanity.heal_or_harm(-dice.roll("2d2")) else: name = say.capitalized(consumer.name) say.insayne( f"{name} puffs mellowly on a {self.name}, looking extremely fly." )