Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V0.34.0 #91

Merged
merged 5 commits into from
Sep 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion llm_config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ BACKEND: "kobold_cpp" # valid options: "openai", "llama_cpp", "kobold_cpp". if u
MEMORY_SIZE: 512
UNLIMITED_REACTS: False
DIALOGUE_TEMPLATE: '{{"response":"may be both dialogue and action.", "sentiment":"sentiment based on response", "give":"if any physical item of {character2}s is given as part of the dialogue. Or nothing."}}'
ACTION_LIST: ['move, say, attack, wear, remove, wield, take, eat, drink, emote']
ACTION_LIST: ['move, say, attack, wear, remove, wield, take, eat, drink, emote, search, hide, unhide, pick_lock']
ACTION_TEMPLATE: '{{"goal": reason for action, "thoughts":thoughts about performing action, 25 words "action":chosen action, "target":character, item or exit or description, "text": if anything is said during the action}}'
ITEM_TEMPLATE: '{{"name":"", "type":"", "short_descr":"", "level":int, "value":int}}'
CREATURE_TEMPLATE: '{{"name":"", "body":"", "mass":int(kg), "hp":int, "type":"Npc or Mob", "level":int, "aggressive":bool, "unarmed_attack":One of [FISTS, CLAWS, BITE, TAIL, HOOVES, HORN, TUSKS, BEAK, TALON], "short_descr":""}}'
Expand Down
14 changes: 12 additions & 2 deletions stories/anything/story.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,13 @@
from tale import parse_utils
from tale.driver import Driver
from tale.json_story import JsonStory
from tale.magic import MagicType
from tale.skills.magic import MagicType
from tale.main import run_from_cmdline
from tale.player import Player, PlayerConnection
from tale.charbuilder import PlayerNaming
from tale.skills.skills import SkillType
from tale.story import *
from tale.weapon_type import WeaponType
from tale.skills.weapon_type import WeaponType

class Story(JsonStory):

Expand Down Expand Up @@ -55,6 +56,15 @@ def create_account_dialog(self, playerconnection: PlayerConnection, playernaming
else:
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED, value=random.randint(20, 40))
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED, value=random.randint(20, 40))
stealth = yield "input", ("Are you sneaky? (yes/no)", lang.yesno)
if stealth:
playerconnection.player.stats.skills[SkillType.HIDE] = random.randint(30, 50)
playerconnection.player.stats.skills[SkillType.PICK_LOCK] = random.randint(30, 50)
else:
playerconnection.player.stats.skills[SkillType.HIDE] = random.randint(10, 30)
playerconnection.player.stats.skills[SkillType.PICK_LOCK] = random.randint(10, 30)
playerconnection.player.stats.skills[SkillType.SEARCH] = random.randint(20, 40)

return True

def welcome(self, player: Player) -> str:
Expand Down
37 changes: 37 additions & 0 deletions stories/chat_room/story.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import pathlib
import sys
from typing import Optional, Generator

import tale
from tale import parse_utils
from tale.base import Location
from tale.driver import Driver
from tale.json_story import JsonStory
from tale.llm.llm_ext import DynamicStory
from tale.main import run_from_cmdline
from tale.player import Player, PlayerConnection
from tale.charbuilder import PlayerNaming
from tale.story import *
from tale.skills.weapon_type import WeaponType
from tale.zone import Zone

class Story(JsonStory):

driver = None

def __init__(self) -> None:
super(Story, self).__init__('', parse_utils.load_story_config(parse_utils.load_json('story_config.json')))

def init(self, driver: Driver) -> None:

super(Story, self).init(driver)


if __name__ == "__main__":
# story is invoked as a script, start it in the Tale Driver.
gamedir = pathlib.Path(__file__).parent
if gamedir.is_dir() or gamedir.is_file():
cmdline_args = sys.argv[1:]
cmdline_args.insert(0, "--game")
cmdline_args.insert(1, str(gamedir))
run_from_cmdline(cmdline_args)
2 changes: 1 addition & 1 deletion stories/combat_sandbox/story.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from tale.player import Player, PlayerConnection
from tale.charbuilder import PlayerNaming
from tale.story import *
from tale.weapon_type import WeaponType
from tale.skills.weapon_type import WeaponType
from tale.zone import Zone

class Story(JsonStory):
Expand Down
14 changes: 12 additions & 2 deletions stories/dungeon/story.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,13 @@
from tale.dungeon.dungeon_generator import ItemPopulator, Layout, LayoutGenerator, MobPopulator
from tale.items.basic import Money
from tale.json_story import JsonStory
from tale.magic import MagicType
from tale.skills.magic import MagicType
from tale.main import run_from_cmdline
from tale.npc_defs import RoamingMob
from tale.player import Player, PlayerConnection
from tale.skills.skills import SkillType
from tale.story import *
from tale.weapon_type import WeaponType
from tale.skills.weapon_type import WeaponType
from tale.zone import Zone

class Story(JsonStory):
Expand Down Expand Up @@ -66,6 +67,15 @@ def create_account_dialog(self, playerconnection: PlayerConnection, playernaming
else:
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED, value=random.randint(20, 40))
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED, value=random.randint(20, 40))
stealth = yield "input", ("Are you sneaky? (yes/no)", lang.yesno)
if stealth:
playerconnection.player.stats.skills[SkillType.HIDE] = random.randint(30, 50)
playerconnection.player.stats.skills[SkillType.PICK_LOCK] = random.randint(30, 50)
else:
playerconnection.player.stats.skills[SkillType.HIDE] = random.randint(10, 30)
playerconnection.player.stats.skills[SkillType.PICK_LOCK] = random.randint(10, 30)
playerconnection.player.stats.skills[SkillType.SEARCH] = random.randint(20, 40)

return True

def welcome(self, player: Player) -> str:
Expand Down
179 changes: 179 additions & 0 deletions stories/dungeon_test/story.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,179 @@
import pathlib
import random
import sys
from typing import Generator

from tale import parse_utils
from tale import lang
from tale.base import Door, Exit, Location
from tale.charbuilder import PlayerNaming
from tale.driver import Driver
from tale.dungeon.dungeon_generator import ItemPopulator, Layout, LayoutGenerator, MobPopulator
from tale.items.basic import Money
from tale.json_story import JsonStory
from tale.main import run_from_cmdline
from tale.player import Player, PlayerConnection
from tale.story import *
from tale.skills.weapon_type import WeaponType
from tale.zone import Zone

class Story(JsonStory):

driver = None # type: Driver

def __init__(self, path = '', layout_generator = LayoutGenerator(), mob_populator = MobPopulator(), item_populator = ItemPopulator(), config: StoryConfig = None) -> None:
super(Story, self).__init__(path, config or parse_utils.load_story_config(parse_utils.load_json('story_config.json')))
self.layout_generator = layout_generator
self.mob_populator = mob_populator
self.item_populator = item_populator
self.max_depth = 5
self.depth = 0


def init(self, driver: Driver) -> None:
super(Story, self).init(driver)

def init_player(self, player: Player) -> None:
"""
Called by the game driver when it has created the player object (after successful login).
You can set the hint texts on the player object, or change the state object, etc.
"""
player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED_RANGED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED_RANGED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.UNARMED, value=random.randint(20, 30))
pass

def create_account_dialog(self, playerconnection: PlayerConnection, playernaming: PlayerNaming) -> Generator:
"""
Override to add extra dialog options to the character creation process.
Because there's no actual player yet, you receive PlayerConnection and PlayerNaming arguments.
Write stuff to the user via playerconnection.output(...)
Ask questions using the yield "input", "question?" mechanism.
Return True to declare all is well, and False to abort the player creation process.
"""
ranged = yield "input", ("Do you prefer ranged over close combat? (yes/no)", lang.yesno)
if ranged:
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED_RANGED, value=random.randint(20, 40))
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED_RANGED, value=random.randint(20, 40))
else:
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED, value=random.randint(20, 40))
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED, value=random.randint(20, 40))
return True

def welcome(self, player: Player) -> str:
"""welcome text when player enters a new game"""
player.tell("<bright>Hello, %s! Welcome to %s.</>" % (player.title, self.config.name), end=True)
player.tell("\n")
player.tell(self.config.context)
player.tell("\n")
return ""

def welcome_savegame(self, player: Player) -> str:
"""welcome text when player enters the game after loading a saved game"""
player.tell("<bright>Hello %s, welcome back to %s.</>" % (player.title, self.config.name), end=True)
player.tell("\n")
player.tell(self.driver.resources["messages/welcome.txt"].text)
player.tell("\n")
return ""

def goodbye(self, player: Player) -> None:
"""goodbye text when player quits the game"""
player.tell("Goodbye, %s. Please come back again soon." % player.title)
player.tell("\n")

def add_zone(self, zone: Zone) -> bool:
if not super(Story, self).add_zone(zone):
return False
if zone.locations != {}:
return True
first_zone = len(self._zones.values()) == 0
zone.size_z = 1
layout = self.layout_generator.generate()

rooms = self._prepare_locations(layout=layout, first_zone=first_zone)

self._describe_rooms(zone=zone, layout=layout, rooms=rooms)

self._connect_locations(layout=layout)

mob_spawners = self.mob_populator.populate(zone=zone, layout=layout, story=self)
for mob_spawner in mob_spawners:
self.world.add_mob_spawner(mob_spawner)

item_spawners = self.item_populator.populate(zone=zone, story=self)
for item_spawner in item_spawners:
self.world.add_item_spawner(item_spawner)

if not first_zone:
self.layout_generator.spawn_gold(zone=zone)

return True

def _describe_rooms(self, zone: Zone, layout: Layout, rooms: list):
described_rooms = []
sliced_rooms = []
for num in range(0, len(rooms), 10):
sliced_rooms.extend(rooms[num:num+10])
for i in range(3):
described_rooms_slice = self.driver.llm_util.generate_dungeon_locations(zone_info=zone.get_info(), locations=sliced_rooms, depth = self.depth, max_depth=self.max_depth) # type LocationDescriptionResponse
if described_rooms_slice.valid:
described_rooms.extend(described_rooms_slice.location_descriptions)
sliced_rooms = []
break
if len(rooms) != len(described_rooms):
print(f'Rooms list not same length: {len(rooms)} vs {len(described_rooms)}')
for room in described_rooms:
i = 1
if zone.get_location(room.name):
# ensure unique names
room.name = f'{room.name}({i})'
i += 1
location = Location(name=room.name, descr=room.description)
location.world_location = list(layout.cells.values())[room.index].coord
zone.add_location(location=location)
self.add_location(zone=zone.name, location=location)
return described_rooms


def _prepare_locations(self, layout: Layout, first_zone: bool = False) -> list:
index = 0
rooms = []
for cell in list(layout.cells.values()):
if cell.is_dungeon_entrance:
rooms.append(f'{{"index": {index}, "name": "Entrance to dungeon"}}')
if cell.is_entrance:
rooms.append(f'{{"index": {index}, "name": "Room with pathway leading up to this level."}}')
elif cell.is_exit:
rooms.append(f'{{"index": {index}, "name": "Room with pathway leading down"}}')
elif cell.is_room:
rooms.append(f'{{"index": {index}, "name": "Room"}}')
else:
rooms.append(f'{{"index": {index}, "name": "Hallway", "description": "A hallway"}}')
index += 1
return rooms

def _connect_locations(self, layout: Layout) -> None:
connections = layout.connections
for connection in connections:
cell_location = self.world._grid.get(connection.coord.as_tuple(), None) # type: Location
parent_location = self.world._grid.get(connection.other.as_tuple(), None) # type: Location
if cell_location.exits.get(parent_location.name, None):
continue
elif parent_location.exits.get(cell_location.name, None):
continue
if connection.door:
Door.connect(cell_location, parent_location.name, '', None, parent_location, cell_location.name, '', None, opened=False, locked=connection.locked, key_code=connection.key_code)
else:
Exit.connect(cell_location, parent_location.name, '', None, parent_location, cell_location.name, '', None)


if __name__ == "__main__":
# story is invoked as a script, start it in the Tale Driver.
gamedir = pathlib.Path(__file__).parent
if gamedir.is_dir() or gamedir.is_file():
cmdline_args = sys.argv[1:]
cmdline_args.insert(0, "--game")
cmdline_args.insert(1, str(gamedir))
run_from_cmdline(cmdline_args)
85 changes: 85 additions & 0 deletions stories/land_story/story.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
import pathlib
import random
import sys
from typing import Generator

import tale
from tale import lang
from tale import parse_utils
from tale.driver import Driver
from tale.json_story import JsonStory
from tale.llm.llm_ext import DynamicStory
from tale.main import run_from_cmdline
from tale.player import Player, PlayerConnection
from tale.charbuilder import PlayerNaming
from tale.story import *
from tale.skills.weapon_type import WeaponType
from tale.zone import Zone

class Story(JsonStory):

def __init__(self) -> None:
super(Story, self).__init__('', parse_utils.load_story_config(parse_utils.load_json('story_config.json')))

def init(self, driver: Driver) -> None:
super(Story, self).init(driver)

def init_player(self, player: Player) -> None:
"""
Called by the game driver when it has created the player object (after successful login).
You can set the hint texts on the player object, or change the state object, etc.
"""
player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED_RANGED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED_RANGED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED, value=random.randint(10, 30))
player.stats.set_weapon_skill(weapon_type=WeaponType.UNARMED, value=random.randint(20, 30))
pass

def create_account_dialog(self, playerconnection: PlayerConnection, playernaming: PlayerNaming) -> Generator:
"""
Override to add extra dialog options to the character creation process.
Because there's no actual player yet, you receive PlayerConnection and PlayerNaming arguments.
Write stuff to the user via playerconnection.output(...)
Ask questions using the yield "input", "question?" mechanism.
Return True to declare all is well, and False to abort the player creation process.
"""
ranged = yield "input", ("Do you prefer ranged over close combat? (yes/no)", lang.yesno)
if ranged:
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED_RANGED, value=random.randint(20, 40))
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED_RANGED, value=random.randint(20, 40))
else:
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.ONE_HANDED, value=random.randint(20, 40))
playerconnection.player.stats.set_weapon_skill(weapon_type=WeaponType.TWO_HANDED, value=random.randint(20, 40))
return True

def welcome(self, player: Player) -> str:
"""welcome text when player enters a new game"""
player.tell("<bright>Hello, %s! Welcome to %s.</>" % (player.title, self.config.name), end=True)
player.tell("\n")
player.tell(self.driver.resources["messages/welcome.txt"].text)
player.tell("\n")
return ""

def welcome_savegame(self, player: Player) -> str:
"""welcome text when player enters the game after loading a saved game"""
player.tell("<bright>Hello %s, welcome back to %s.</>" % (player.title, self.config.name), end=True)
player.tell("\n")
player.tell(self.driver.resources["messages/welcome.txt"].text)
player.tell("\n")
return ""

def goodbye(self, player: Player) -> None:
"""goodbye text when player quits the game"""
player.tell("Goodbye, %s. Please come back again soon." % player.title)
player.tell("\n")


if __name__ == "__main__":
# story is invoked as a script, start it in the Tale Driver.
gamedir = pathlib.Path(__file__).parent
if gamedir.is_dir() or gamedir.is_file():
cmdline_args = sys.argv[1:]
cmdline_args.insert(0, "--game")
cmdline_args.insert(1, str(gamedir))
run_from_cmdline(cmdline_args)
2 changes: 1 addition & 1 deletion stories/prancingllama/npcs/npcs.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
from tale import lang
from typing import Optional

from tale.weapon_type import WeaponType
from tale.skills.weapon_type import WeaponType

class InnKeeper(LivingNpc):

Expand Down
Loading
Loading