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

238
server.py
View File

@@ -1,6 +1,7 @@
# Game imports
from events.turn import TurnAction
from utils.dice import Dice
from utils.game import Game
from utils.game import Game, Stat
from utils.serializable import Serializable
from events.event import Event
@@ -18,7 +19,7 @@ SAVE_PATH = "save_"
# Global Parameters
mcp = FastMCP("wyvern-castle")
game: Game = None
game = Game()
# Logging config
logging.basicConfig(
@@ -74,49 +75,91 @@ async def save_game(slot:int):
"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
@mcp.tool()
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)
game.add_event(new_event)
@mcp.tool()
async def perform_action():
pass
async def perform_attack(src_entity_id:str, target_entity_id:str, attack_type:Stat):
"""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
@mcp.tool()
async def create_item():
pass
@mcp.tool()
async def add_item_to_entity():
pass
# OTHER UTILS
@mcp.tool()
@@ -146,12 +189,21 @@ async def throw_a_dice(n_faces: int) -> Any:
@mcp.tool()
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()
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,
and a d6 for hp, armor and speed.
and a d50 for hp, armor and speed.
Args:
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
item: Item carried by the player
"""
logging.info(f"Creating NPC named {name}")
logging.info(f"Creating player named {name}")
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)
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 {
"success": True,
"npc_properties": player_dict
"player_properties": player_dict
}
except ReferenceError as e:
logging.info(f"ReferenceError: " + str(e))
@@ -185,7 +246,7 @@ async def create_player(name: str, strength: int, dexterity: int, intelligence:
@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):
"""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:
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}")
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)
npc_dict = game.get_npc(npc_id=npc_id).serialize()
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:
name: Name 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}")
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()
async def add_item_to_player(player_name: str, item_name: str) -> Dict[str, Any]:
"""Add an item to a player's inventory.
async def equip_item_to_entity(entity_id: str, item_id: str) -> Dict[str, Any]:
"""Equip an item to an entity (player or NPC).
Args:
player_name: The name of the player to add the item to
item_name: The name of the item to add
entity_id: The id of the entity to equip the item to
item_id: The id of the item to equip
"""
logging.info(f"Adding item {item_name} to player {player_name}")
return {"status": "Item added"}
logging.info(f"Equipping item {item_id} to entity {entity_id}")
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()
async def save_game_state() -> str:
@@ -254,18 +383,7 @@ async def save_game_state() -> str:
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()
async def purge_game_history_and_state() -> str: