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.") else: book.read(G.player)
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 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 _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 = util.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 {util.a(attacker.name)}") if not (is_player or defender.alive): G.just_died = True
def drop(item): item_name = item item = G.player.inventory.find(item_name) if item is None: say.insayne(f"You don't have {util.a(item_name)} to drop.") else: say.insayne(f"You drop the {item_name} on the ground.") _move_item(G.player.inventory, G.player.current_room.items, item)
def activate(self): # TODO: Add global invocation counter. # TODO: Make events global and event polling constant so that this # ability can win the game. _say.insayne("You touch the ground. You sit and breathe, are still.") _say.insayne("The stillness soothes your mind and steels your will.", add_newline=False) self.owner.insanity.modify(-20)
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.modify(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 use(item, verb): item_name = item item = G.player.inventory.find(item_name) if item is None: say.insayne(f"You don't have {util.a(item_name)}.") return try: item.consume(G.player) except AttributeError: say.insayne(f"You can't {verb} the {item_name}.")
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 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: # TODO: Replace enqueue_text with text events. say.insayne(f"You proceed {the_direction.display_description}.") G.player.current_room.on_exit() G.player.current_room = next_room enter_room(G.player.current_room)
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 {util.a(corpse.name)} molders here.") say.insayne(f'Exits are {", ".join(G.player.current_room.display_exits)}.')
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("") else: self.alive = False self._death_throes(self) self.current_room.characters.remove(self) self.current_room.corpses.add(self)
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.modify(15) G.add_event(_SongInHeadEvent(), "pre")
def die(self, cause=None): cause = cause or self.health.last_cause self.alive = False is_player = self is G.player if is_player: G.player.current_room.on_exit() G.cause_of_death = cause say.insayne("You die.") say.insayne("...") self._death_throes(self) if is_player: return self.current_room.characters.remove(self) self.current_room.corpses.add(self)
def _get_random_start(): # TODO: Condition this on how the last death actually occurred. death_text = _G.cause_of_death or random.choice([ "being impaled", "slowly suffocating as a glabrous tentacle horror looks on", ]) for text in [ f"You recall your death by {death_text}. The memory fades away.", "You know only that you have been here for interminable years, " "that you have died innumerable times, and that someone once told " "you there was a way out. You were told this an eon ago, or maybe " "a day, but the stubborn hope of escape glisters in your mind.", ]: say.insayne(text)
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, "current_value"): lines.append(f"{stat.name:10}: {stat.current_value}/{stat.value}") 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 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.modify(-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 = util.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.modify(-dice.roll("2d2")) else: name = util.capitalized(consumer.name) say.insayne( f"{name} puffs mellowly on a {self.name}, looking extremely fly." )
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 inventory(): say.insayne("You possess the following:") if not G.player.inventory: say.insayne("Nothing.", add_newline=False) return # TODO: This is repeated in inspect(). Collapse these into a single function. for item in G.player.inventory: say.insayne(item.description, add_newline=False)
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 loot(item, corpse): item_name = item corpse_name = corpse character = _get_present_actor(corpse_name) if character is None: say.insayne(f"There is no {corpse_name} here.") return # TODO: Really need some abstraction around combat turns to avoid this # duplication. if character.alive: message = "You cannot loot the living!" # TODO: What if none of the character present chooses to attack? if G.player.current_room.npcs: message += " All enemies attack as your clumsy pickpocketing attempt fails." say.insayne(message) for character in G.player.current_room.npcs: if G.just_died: return assert character.ai is not None action = character.ai.choose_action(G.player.current_room) if action.attack is not None: _resolve_attack(character, action.attack) return else: if item_name in {"all", "everything"}: items = {item.name: item for item in character.inventory} else: items = {item_name: character.inventory.find(item_name)} for name, item in items.items(): if item is None: say.insayne(f"There is no {name} on the corpse.") else: say.insayne(f"You liberate {item.name} from the corpse.") _move_item(character.inventory, G.player.inventory, item)
def attack(actor): """Attacks another character in the same room.""" actor_name = actor # Variable names are constrained by adventurelib. # TODO: Consider turn order--some kind of agility stat? # TODO: Other actions should be considered "combat" actions. Implement some # notion of turns. # TODO: Other combat actions--spells, items, fleeing, etc. # TODO: Affinity/factions so monsters can choose whom to strike. defender = _get_present_actor(actor_name) if defender is None: say.insayne(f"There is no {actor_name} here to attack.") return if not defender.alive: say.insayne( f"In a blind fury, you hack uselessly at the {defender.name}'s corpse." ) G.player.insanity.modify(10) return # TODO: Move this to player AI. Use defender as a "hint." # TODO: Migrating to player AI will avoid the special-casing of Room.npcs. # TODO: Migrating to player AI can also allow for a more nuanced # menu when attacking. _resolve_attack(G.player, ai.Attack(target=defender, method=None)) for character in G.player.current_room.npcs: if G.just_died: return assert character.ai is not None action = character.ai.choose_action(G.player.current_room, impulse="attack") if action.attack is not None: _resolve_attack(character, action.attack) else: say.insayne( f"{util.capitalized(character.name)} makes no hostile motion.")
def execute(self): say.insayne( "Tittering and in a halted voice, the librarian utters these words:" ) say.insayne("\"The flesh is active, yet it's only soothed") say.insayne("when still. Through constant paradox, flesh moves.", add_newline=False) say.insayne("I hold stillness's secret! It is mine!", add_newline=False) say.insayne("Find it upon my body when I die.\"", add_newline=False) say.insayne( "\"Now watch,\" cries the rag-clad wretch, \"as the paradox " "is resolved.\"") npc.die() self._will_execute = False
def _discard_aliases(self, item): super()._discard_aliases(item) item.holder = None if self is _G.player.inventory: say.insayne(f"You lose possession of {item.name}.")
def _add_aliases(self, item): super()._add_aliases(item) logging.debug(f'_add_aliases called with {item}') item.holder = self if self is _G.player.inventory: say.insayne(f"You acquire {item.name}.")
def consume(self, consumer): # TODO: Customize text based on whether consumer is player. say.insayne(f"You eat the {self.name}. What is wrong with you?") consumer.insanity.modify(10) consumer.inventory.remove(self)
def smoker_death_throes(smoker): say.insayne( 'The smoker glances placidly around the environs. "Until the next ' 'time around, I guess." With a final nod, he breathes a perfect ' 'wreath of smoke, which dissipates solemnly.')
def fish_man_death_throes(fish_man): say.insayne( f"{util.capitalized(fish_man.name)} flops breathlessly upon the " "ground, blood commingling with piscine slobber. Half-formed gills " "flutter helplessly, urgently, then fall slack.")
def add_ability(self, ability): # TODO: "You" vs. name problem as always. say.insayne(f"You gain the ability {ability.NAME}.") # TODO: Bidirectional reference problem as always. ability.owner = self self._abilities[ability.NAME] = ability