added some tools to server, removed deprecated

code and adjusted entity, game, and event files to
work with items being mandatory for entities.
This commit is contained in:
2026-01-30 18:14:05 +01:00
parent 13488e1c2d
commit 5fef91e110
5 changed files with 196 additions and 78 deletions

View File

@@ -6,7 +6,7 @@ from utils.item import Item
import uuid import uuid
class Entity(Serializable): class Entity(Serializable):
def __init__(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item=None): def __init__(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item):
self.id = str(uuid.uuid4()) self.id = str(uuid.uuid4())
self.name = name self.name = name
self.strength = 20 self.strength = 20

View File

@@ -1,7 +1,7 @@
# Game imports # Game imports
from utils.serializable import Serializable from utils.serializable import Serializable
from entities.entity import Entity from entities.entity import Entity
from turn import Turn, TurnAction from events.turn import Turn, TurnAction
# Native imports # Native imports
import json import json
@@ -35,7 +35,7 @@ class Event(Serializable):
current_turn = self.get_current_turn() current_turn = self.get_current_turn()
current_turn.add_action(action_type=action, entity_id=entity_id, description=description) current_turn.add_action(action_type=action, entity_id=entity_id, description=description)
if current_turn.is_finished(): if current_turn.is_finished(len(self.entities)):
self.add_turn() self.add_turn()
return True return True

238
server.py
View File

@@ -1,6 +1,7 @@
# Game imports # Game imports
from events.turn import TurnAction
from utils.dice import Dice from utils.dice import Dice
from utils.game import Game from utils.game import Game, Stat
from utils.serializable import Serializable from utils.serializable import Serializable
from events.event import Event from events.event import Event
@@ -18,7 +19,7 @@ SAVE_PATH = "save_"
# Global Parameters # Global Parameters
mcp = FastMCP("wyvern-castle") mcp = FastMCP("wyvern-castle")
game: Game = None game = Game()
# Logging config # Logging config
logging.basicConfig( logging.basicConfig(
@@ -74,49 +75,91 @@ async def save_game(slot:int):
"error": str(e) "error": str(e)
} }
@DeprecationWarning
def append_to_history(event: Dict[str, Any]):
"""Append a game event to the history file."""
history = []
if os.path.exists(HISTORY_FILE):
with open(HISTORY_FILE, "r", encoding="utf-8") as f:
try:
history = json.load(f)
except json.JSONDecodeError:
history = []
history.append(event)
with open(HISTORY_FILE, "w", encoding="utf-8") as f:
json.dump(history, f, ensure_ascii=False, indent=2)
@DeprecationWarning
def read_history() -> list:
"""Read the game history from the file."""
if os.path.exists(HISTORY_FILE):
with open(HISTORY_FILE, "r", encoding="utf-8") as f:
try:
return json.load(f)
except json.JSONDecodeError:
return []
return []
# EVENTS TOOLS # EVENTS TOOLS
@mcp.tool() @mcp.tool()
async def start_event(location:str, initial_description:str, entity_list:list[str]): async def start_event(location:str, initial_description:str, entity_list:list[str]):
""" Start a new event in the game.
Args:
location: Location of the event
initial_description: Initial description of the event
entity_list: List of entity IDs involved in the event
"""
new_event = Event(location=location, initial_description=initial_description, entities=entity_list) new_event = Event(location=location, initial_description=initial_description, entities=entity_list)
game.add_event(new_event) game.add_event(new_event)
@mcp.tool() @mcp.tool()
async def perform_action(): async def perform_attack(src_entity_id:str, target_entity_id:str, attack_type:Stat):
pass """Perform an attack during an event. This add an attack action to the current event.
Args:
src_entity_id: The ID of the entity performing the attack
target_entity_id: The ID of the entity being attacked
attack_type: The type of attack being performed using the class Stat, can be Stat.STRENGTH, Stat.INTELLIGENCE, Stat.DEXTERITY which are 0, 1 and 2 respectively
"""
logging.info(f"Entity {src_entity_id} is performing an attack on {target_entity_id} using {attack_type}")
game.deal_damage(src=src_entity_id, target=target_entity_id, roll=Dice.roll(20), stat=attack_type, description=f"Entity {src_entity_id} attacks {target_entity_id} with a {attack_type} based attack.")
return {
"success": True,
"msg": f"Attack performed by {src_entity_id} on {target_entity_id} using {attack_type}"
}
@mcp.tool()
async def perform_test_action(entity_id:str, stat:Stat, difficulty:int, roll:int):
"""Perform a test action during an event. This add a test action to the current event.
This can be opening a door (DEXTERITY), solving a puzzle (INTELLIGENCE), resisting a charm (WISDOM) etc.
Args:
entity_id: The ID of the entity performing the test
stat: The stat being tested (using the Stat enum)
difficulty: The difficulty of the test
"""
logging.info(f"Entity {entity_id} is performing a test action on stat {stat} with difficulty {difficulty}")
entity = game.get_entity(entity_id)
stat_value = 0
match(stat):
case Stat.STRENGTH:
stat_value = entity.get_strength()
case Stat.INTELLIGENCE:
stat_value = entity.get_intelligence()
case Stat.DEXTERITY:
stat_value = entity.get_dexterity()
case Stat.WISDOM:
stat_value = entity.get_wisdom()
case Stat.CHARISMA:
stat_value = entity.get_charisma()
total = roll + stat_value
success = total >= difficulty
description = f"Entity {entity_id} performs a {stat.name} test (roll: {roll} + stat: {stat_value} = total: {total}) against difficulty {difficulty}. "
if success:
description += "The test is successful."
else:
description += "The test fails."
game.get_current_event().perform_action(TurnAction.BASIC, entity_id, description=description)
return {
"success": success,
"total": total,
"msg": description
}
@mcp.tool()
async def perform_stat_modification(entity_id:str, stat:Stat, value:int):
"""Modify a stat of an entity during an event. This add a stat modification action to the current event.
This can be due to a spell, a potion, a curse etc.
Args:
entity_id: The ID of the entity whose stat is being modified
stat: The stat being modified (using the Stat enum)
value: The value to modify the stat by (can be positive or negative)
"""
logging.info(f"Entity {entity_id} is having its stat {stat} modified by {value}")
game.modifying_stat(src=entity_id, value=value, stat=stat, description=f"Entity {entity_id} has its {stat.name} modified by {value}.")
return {
"success": True,
"msg": f"Stat {stat.name} of entity {entity_id} modified by {value}"
}
# ITEM TOOLS # ITEM TOOLS
@mcp.tool()
async def create_item():
pass
@mcp.tool()
async def add_item_to_entity():
pass
# OTHER UTILS # OTHER UTILS
@mcp.tool() @mcp.tool()
@@ -146,12 +189,21 @@ async def throw_a_dice(n_faces: int) -> Any:
@mcp.tool() @mcp.tool()
async def get_entity_status(): async def get_entity_status():
pass """
Get the status of all entities in the game.
"""
logging.info("Getting status of all entities")
players = [player.serialize() for player in game.active_players]
npcs = [npc.serialize() for npc in game.active_npcs]
return {
"players": players,
"npcs": npcs
}
@mcp.tool() @mcp.tool()
async def create_player(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item_id:str): async def create_player(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item_id:str):
"""Create a new player. Need all the stats to function properly. Throw a d20 for every stats you don't have, """Create a new player. Need all the stats to function properly. Throw a d20 for every stats you don't have,
and a d6 for hp, armor and speed. and a d50 for hp, armor and speed.
Args: Args:
name: Name of the player name: Name of the player
@@ -165,15 +217,24 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence:
speed: Speed of the player speed: Speed of the player
item: Item carried by the player item: Item carried by the player
""" """
logging.info(f"Creating NPC named {name}") logging.info(f"Creating player named {name}")
try: try:
player_id = game.create_player(name=name, strength=strength, dexterity=dexterity, intelligence=intelligence, wisdom=wisdom, charisma=charisma, hp=hp, armor=armor, speed=speed) player_id = game.create_player(name=name,
strength=strength,
dexterity=dexterity,
intelligence=intelligence,
wisdom=wisdom,
charisma=charisma,
hp=50 + hp,
armor=50 + armor,
speed= 50 + speed,
equipped_item=game.get_item(item_id)) # Check if item exists
game.add_item_to_entity(item_id=item_id, entity_id=player_id) game.add_item_to_entity(item_id=item_id, entity_id=player_id)
player_dict = game.get_player(player_id=player_id).serialize() player_dict = game.get_player(player_id=player_id).serialize()
logging.info(f"Creation of NPC successful") logging.info(f"Creation of player successful")
return { return {
"success": True, "success": True,
"npc_properties": player_dict "player_properties": player_dict
} }
except ReferenceError as e: except ReferenceError as e:
logging.info(f"ReferenceError: " + str(e)) logging.info(f"ReferenceError: " + str(e))
@@ -185,7 +246,7 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence:
@mcp.tool() @mcp.tool()
async def create_npc(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item_id:str): async def create_npc(name: str, strength: int, dexterity: int, intelligence: int, wisdom: int, charisma: int, hp: int, armor: int, speed: int, item_id:str):
"""Create a new NPC. Need all the stats to function properly. Throw a d20 for every stats you don't have, """Create a new NPC. Need all the stats to function properly. Throw a d20 for every stats you don't have,
and a d6 for hp, armor and speed. and a d100 for hp, armor and speed.
Args: Args:
name: Name of the NPC name: Name of the NPC
@@ -201,7 +262,8 @@ async def create_npc(name: str, strength: int, dexterity: int, intelligence: int
""" """
logging.info(f"Creating NPC named {name}") logging.info(f"Creating NPC named {name}")
try: try:
npc_id = game.create_npc(name=name, strength=strength, dexterity=dexterity, intelligence=intelligence, wisdom=wisdom, charisma=charisma, hp=hp, armor=armor, speed=speed) item = game.get_item(item_id) # Check if item exists
npc_id = game.create_npc(name=name, strength=strength, dexterity=dexterity, intelligence=intelligence, wisdom=wisdom, charisma=charisma, hp=50 + hp, armor=50 + armor, speed=50 + speed, equipped_item=item)
game.add_item_to_entity(item_id=item_id, entity_id=npc_id) game.add_item_to_entity(item_id=item_id, entity_id=npc_id)
npc_dict = game.get_npc(npc_id=npc_id).serialize() npc_dict = game.get_npc(npc_id=npc_id).serialize()
logging.info(f"Creation of NPC successful") logging.info(f"Creation of NPC successful")
@@ -223,7 +285,7 @@ async def create_item(name: str, description: str, stat_modifier: dict[str,int])
Args: Args:
name: Name of the item name: Name of the item
description: Description of the item description: Description of the item
bonus: Bonus of the item ex: strength+1,hp+5 bonus: Bonus or malus of the item ex: {"strength":+1,"hp":-5}
""" """
logging.info(f"Creating item, name={name} ; description={description}") logging.info(f"Creating item, name={name} ; description={description}")
item_id = game.create_item(name, description, stat_modifier) item_id = game.create_item(name, description, stat_modifier)
@@ -234,15 +296,82 @@ async def create_item(name: str, description: str, stat_modifier: dict[str,int])
} }
@mcp.tool() @mcp.tool()
async def add_item_to_player(player_name: str, item_name: str) -> Dict[str, Any]: async def equip_item_to_entity(entity_id: str, item_id: str) -> Dict[str, Any]:
"""Add an item to a player's inventory. """Equip an item to an entity (player or NPC).
Args: Args:
player_name: The name of the player to add the item to entity_id: The id of the entity to equip the item to
item_name: The name of the item to add item_id: The id of the item to equip
""" """
logging.info(f"Adding item {item_name} to player {player_name}") logging.info(f"Equipping item {item_id} to entity {entity_id}")
return {"status": "Item added"} game.add_item_to_entity(item_id=item_id, entity_id=entity_id)
return {"status": "Item equipped"}
@mcp.tool()
async def get_entity_info(entity_id: str) -> Dict[str, Any]:
"""Get the full information of an entity (player or NPC).
Args:
entity_id: The id of the entity to get information from
"""
logging.info(f"Getting info for entity {entity_id}")
entity = game.get_entity(entity_id)
return {
"entity_properties": entity.serialize()
}
@mcp.tool()
async def get_item_info(item_id: str) -> Dict[str, Any]:
"""Get the full information of an item.
Args:
item_id: The id of the item to get information from
"""
logging.info(f"Getting info for item {item_id}")
item = game.get_item(item_id)
return {
"item_properties": item.serialize()
}
@mcp.tool()
async def get_game_state() -> str:
"""Get the current game state as a serialized string."""
logging.info("Getting current game state")
return game.serialize()
@mcp.tool()
async def get_current_event() -> Dict[str, Any]:
"""Get the current event in the game."""
logging.info("Getting current event")
event = game.get_current_event()
if event:
return {
"success": True,
"event_properties": event.serialize()
}
else:
return {
"success": False,
"error": "No current event"
}
@mcp.tool()
async def add_event(location:str, initial_description:str, entity_list:list[str]) -> str:
"""Add a new event to the game.
Args:
location: Location of the event
initial_description: Initial description of the event
entity_list: List of entity IDs involved in the event
"""
logging.info("Adding new event to the game")
new_event = Event(location=location, initial_description=initial_description, entities=entity_list)
game.add_event(new_event)
return "Event added successfully."
@mcp.tool() @mcp.tool()
async def save_game_state() -> str: async def save_game_state() -> str:
@@ -254,18 +383,7 @@ async def save_game_state() -> str:
return "Game state saved to game_state.json" return "Game state saved to game_state.json"
# Example MCP tool to add an event to history
@mcp.tool()
async def add_event_to_history(event: Dict[str, Any]) -> str:
"""Add a game event to the history resource."""
append_to_history(event)
return "Event added to history."
# Example MCP tool to read history
@mcp.tool()
async def get_game_history() -> list:
"""Get the full game history."""
return read_history()
@mcp.tool() @mcp.tool()
async def purge_game_history_and_state() -> str: async def purge_game_history_and_state() -> str:

View File

@@ -1,6 +1,6 @@
# Game imports # Game imports
from serializable import Serializable from utils.serializable import Serializable
from dice import Dice from utils.dice import Dice
from events.event import Event from events.event import Event
from entities.player import Player from entities.player import Player
from entities.npc import NPC from entities.npc import NPC
@@ -11,13 +11,13 @@ from events.turn import TurnAction
from enum import IntEnum from enum import IntEnum
class Stat(IntEnum): class Stat(IntEnum):
STRENGTH = 0, STRENGTH = 0
INTELLIGENCE = 1, INTELLIGENCE = 1
DEXTERITY = 2, DEXTERITY = 2
WISDOM = 3, WISDOM = 3
CHARISMA = 4, CHARISMA = 4
HP = 5, HP = 5
ARMOR = 6, ARMOR = 6
SPEED = 7 SPEED = 7
class Game(Serializable): class Game(Serializable):
@@ -42,7 +42,7 @@ class Game(Serializable):
return player return player
raise ReferenceError(f"The player #{player_id} doesn't exist!") raise ReferenceError(f"The player #{player_id} doesn't exist!")
def create_player(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item=None): def create_player(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item):
new_player = Player(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item) new_player = Player(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item)
self.active_players.append(new_player) self.active_players.append(new_player)
return new_player.id return new_player.id
@@ -53,9 +53,9 @@ class Game(Serializable):
return npc return npc
raise ReferenceError(f"The npc #{npc_id} doesn't exist!") raise ReferenceError(f"The npc #{npc_id} doesn't exist!")
def create_npc(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item=None): def create_npc(self, name:str, strength:int, dexterity:int, intelligence:int, wisdom:int, charisma:int, hp:int, armor:int, speed:int, equipped_item:Item):
new_npc = NPC(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item) new_npc = NPC(name, strength, dexterity, intelligence, wisdom, charisma, hp, armor, speed, equipped_item)
self.active_players.append(new_npc) self.active_npcs.append(new_npc)
return new_npc.id return new_npc.id
def get_item(self, item_id:str): def get_item(self, item_id:str):
@@ -162,12 +162,12 @@ class Game(Serializable):
elif stat == Stat.DEXTERITY: # Using daggers, bows or throws elif stat == Stat.DEXTERITY: # Using daggers, bows or throws
dmg_amount += (roll * 5 / target_entity.get_armor()) * src_entity.get_dexterity() dmg_amount += (roll * 5 / target_entity.get_armor()) * src_entity.get_dexterity()
target_entity.deal_damage(dmg_amount) target_entity.deal_damage(int(dmg_amount))
additional_info = f"; {target_entity.name}({target_entity.get_id()}) took {dmg_amount} damage, {target_entity.get_hp()}hp remaining!" additional_info = f"; {target_entity.name}({target_entity.get_id()}) took {dmg_amount} damage, {target_entity.get_hp()}hp remaining!"
if target_entity.get_hp() <= 0: if target_entity.get_hp() <= 0:
self.kill_entity(target_entity) self.kill_entity(target_entity.get_id())
additional_info = f"; {target_entity.name}({target_entity.get_id()}) took {dmg_amount} damage, {target_entity.name} died!" additional_info = f"; {target_entity.name}({target_entity.get_id()}) took {dmg_amount} damage, {target_entity.name} died!"
turn_finished = self.get_current_event().perform_action(TurnAction.DAMAGE, src, description=description+additional_info) turn_finished = self.get_current_event().perform_action(TurnAction.DAMAGE, src, description=description+additional_info)

View File

@@ -1,5 +1,5 @@
# Game imports # Game imports
from serializable import Serializable from utils.serializable import Serializable
# Native imports # Native imports
import uuid import uuid