Compare commits
43 Commits
master
...
32af625d14
| Author | SHA1 | Date | |
|---|---|---|---|
| 32af625d14 | |||
| 8784fbaa88 | |||
| a8767b34e2 | |||
| d9bc46adc1 | |||
| 45cfa25911 | |||
| 7c0e257540 | |||
| 6d3fb63355 | |||
| dd92cf5991 | |||
| 94c4ca9e95 | |||
| 19b537d8b0 | |||
| 58f0c1b8f6 | |||
| 29b4853c84 | |||
| fdd48034e4 | |||
| a38906b445 | |||
| 4ced1b04df | |||
| 76f67c4a22 | |||
| 185be7fee0 | |||
| f3ac0c8647 | |||
| 03ab783eeb | |||
| 30c3b800e6 | |||
| d789b5df65 | |||
| e6e7cdb7b7 | |||
| 98bb6ab589 | |||
| 1b21465dc4 | |||
| 77d913fe50 | |||
| 4d26c43d1d | |||
| 51f6041ee4 | |||
| 19808dd44c | |||
| 61a42d3a77 | |||
| 0a7156504f | |||
| 8312cfe13f | |||
| 16171dc34a | |||
| 52b199ff10 | |||
| 8675f9bf75 | |||
| a0635499a7 | |||
| 2419dbeb34 | |||
| 196346165f | |||
| 20cb279793 | |||
| 4353d112f4 | |||
| 9c6eb770e5 | |||
| aaa69316c2 | |||
| bda363de76 | |||
| e198d9ac8a |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -35,3 +35,4 @@ Thumbs.db
|
|||||||
logs/
|
logs/
|
||||||
app/logs/
|
app/logs/
|
||||||
*.log
|
*.log
|
||||||
|
CLAUDE.md
|
||||||
|
|||||||
@@ -164,8 +164,22 @@ def register_blueprints(app: Flask) -> None:
|
|||||||
app.register_blueprint(npcs_bp)
|
app.register_blueprint(npcs_bp)
|
||||||
logger.info("NPCs API blueprint registered")
|
logger.info("NPCs API blueprint registered")
|
||||||
|
|
||||||
|
# Import and register Chat API blueprint
|
||||||
|
from app.api.chat import chat_bp
|
||||||
|
app.register_blueprint(chat_bp)
|
||||||
|
logger.info("Chat API blueprint registered")
|
||||||
|
|
||||||
|
# Import and register Combat API blueprint
|
||||||
|
from app.api.combat import combat_bp
|
||||||
|
app.register_blueprint(combat_bp)
|
||||||
|
logger.info("Combat API blueprint registered")
|
||||||
|
|
||||||
|
# Import and register Inventory API blueprint
|
||||||
|
from app.api.inventory import inventory_bp
|
||||||
|
app.register_blueprint(inventory_bp)
|
||||||
|
logger.info("Inventory API blueprint registered")
|
||||||
|
|
||||||
# TODO: Register additional blueprints as they are created
|
# TODO: Register additional blueprints as they are created
|
||||||
# from app.api import combat, marketplace, shop
|
# from app.api import marketplace, shop
|
||||||
# app.register_blueprint(combat.bp, url_prefix='/api/v1/combat')
|
|
||||||
# app.register_blueprint(marketplace.bp, url_prefix='/api/v1/marketplace')
|
# app.register_blueprint(marketplace.bp, url_prefix='/api/v1/marketplace')
|
||||||
# app.register_blueprint(shop.bp, url_prefix='/api/v1/shop')
|
# app.register_blueprint(shop.bp, url_prefix='/api/v1/shop')
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ from flask import Blueprint, request, make_response, render_template, redirect,
|
|||||||
from appwrite.exception import AppwriteException
|
from appwrite.exception import AppwriteException
|
||||||
|
|
||||||
from app.services.appwrite_service import AppwriteService
|
from app.services.appwrite_service import AppwriteService
|
||||||
|
from app.services.session_cache_service import SessionCacheService
|
||||||
from app.utils.response import (
|
from app.utils.response import (
|
||||||
success_response,
|
success_response,
|
||||||
created_response,
|
created_response,
|
||||||
@@ -305,7 +306,11 @@ def api_logout():
|
|||||||
if not token:
|
if not token:
|
||||||
return unauthorized_response(message="No active session")
|
return unauthorized_response(message="No active session")
|
||||||
|
|
||||||
# Logout user
|
# Invalidate session cache before Appwrite logout
|
||||||
|
cache = SessionCacheService()
|
||||||
|
cache.invalidate_token(token)
|
||||||
|
|
||||||
|
# Logout user from Appwrite
|
||||||
appwrite = AppwriteService()
|
appwrite = AppwriteService()
|
||||||
appwrite.logout_user(session_id=token)
|
appwrite.logout_user(session_id=token)
|
||||||
|
|
||||||
@@ -340,6 +345,36 @@ def api_logout():
|
|||||||
return error_response(message="An unexpected error occurred", code="INTERNAL_ERROR")
|
return error_response(message="An unexpected error occurred", code="INTERNAL_ERROR")
|
||||||
|
|
||||||
|
|
||||||
|
@auth_bp.route('/api/v1/auth/me', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def api_get_current_user():
|
||||||
|
"""
|
||||||
|
Get the currently authenticated user's data.
|
||||||
|
|
||||||
|
This endpoint is lightweight and uses cached session data when available,
|
||||||
|
making it suitable for frequent use (e.g., checking user tier, verifying
|
||||||
|
session is still valid).
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: User data
|
||||||
|
401: Not authenticated
|
||||||
|
"""
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
return unauthorized_response(message="Not authenticated")
|
||||||
|
|
||||||
|
return success_response(
|
||||||
|
result={
|
||||||
|
"id": user.id,
|
||||||
|
"email": user.email,
|
||||||
|
"name": user.name,
|
||||||
|
"email_verified": user.email_verified,
|
||||||
|
"tier": user.tier
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
@auth_bp.route('/api/v1/auth/verify-email', methods=['GET'])
|
@auth_bp.route('/api/v1/auth/verify-email', methods=['GET'])
|
||||||
def api_verify_email():
|
def api_verify_email():
|
||||||
"""
|
"""
|
||||||
@@ -480,6 +515,10 @@ def api_reset_password():
|
|||||||
appwrite = AppwriteService()
|
appwrite = AppwriteService()
|
||||||
appwrite.confirm_password_reset(user_id=user_id, secret=secret, password=password)
|
appwrite.confirm_password_reset(user_id=user_id, secret=secret, password=password)
|
||||||
|
|
||||||
|
# Invalidate all cached sessions for this user (security: password changed)
|
||||||
|
cache = SessionCacheService()
|
||||||
|
cache.invalidate_user(user_id)
|
||||||
|
|
||||||
logger.info("Password reset successfully", user_id=user_id)
|
logger.info("Password reset successfully", user_id=user_id)
|
||||||
|
|
||||||
return success_response(
|
return success_response(
|
||||||
|
|||||||
320
api/app/api/chat.py
Normal file
320
api/app/api/chat.py
Normal file
@@ -0,0 +1,320 @@
|
|||||||
|
"""
|
||||||
|
Chat API Endpoints.
|
||||||
|
|
||||||
|
Provides REST API for accessing player-NPC conversation history,
|
||||||
|
searching messages, and managing chat data.
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, request
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
|
||||||
|
from app.utils.auth import require_auth, get_current_user
|
||||||
|
from app.services.chat_message_service import (
|
||||||
|
get_chat_message_service,
|
||||||
|
ChatMessageNotFound,
|
||||||
|
ChatMessagePermissionDenied
|
||||||
|
)
|
||||||
|
from app.services.npc_loader import get_npc_loader
|
||||||
|
from app.models.chat_message import MessageContext
|
||||||
|
from app.utils.response import (
|
||||||
|
success_response,
|
||||||
|
error_response,
|
||||||
|
not_found_response,
|
||||||
|
validation_error_response
|
||||||
|
)
|
||||||
|
from app.utils.logging import get_logger
|
||||||
|
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
chat_bp = Blueprint('chat', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
@chat_bp.route('/api/v1/characters/<character_id>/chats', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def get_conversations_summary(character_id: str):
|
||||||
|
"""
|
||||||
|
Get summary of all NPC conversations for a character.
|
||||||
|
|
||||||
|
Returns list of NPCs the character has talked to, with message counts,
|
||||||
|
last message timestamp, and preview of most recent exchange.
|
||||||
|
|
||||||
|
Path params:
|
||||||
|
character_id: Character ID
|
||||||
|
|
||||||
|
Query params:
|
||||||
|
None
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with list of conversation summaries
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Get conversation summaries
|
||||||
|
chat_service = get_chat_message_service()
|
||||||
|
summaries = chat_service.get_all_conversations_summary(character_id, user.id)
|
||||||
|
|
||||||
|
# Convert to dict for JSON response
|
||||||
|
conversations = [summary.to_dict() for summary in summaries]
|
||||||
|
|
||||||
|
logger.info("Retrieved conversation summaries",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
count=len(conversations))
|
||||||
|
|
||||||
|
return success_response({"conversations": conversations})
|
||||||
|
|
||||||
|
except ChatMessagePermissionDenied as e:
|
||||||
|
logger.warning("Permission denied getting conversations",
|
||||||
|
user_id=user.id if 'user' in locals() else None,
|
||||||
|
character_id=character_id)
|
||||||
|
return error_response(str(e), 403)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to get conversations summary",
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(f"Failed to retrieve conversations: {str(e)}", 500)
|
||||||
|
|
||||||
|
|
||||||
|
@chat_bp.route('/api/v1/characters/<character_id>/chats/<npc_id>', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def get_conversation_history(character_id: str, npc_id: str):
|
||||||
|
"""
|
||||||
|
Get full conversation history between character and specific NPC.
|
||||||
|
|
||||||
|
Returns paginated list of messages ordered by timestamp DESC (most recent first).
|
||||||
|
|
||||||
|
Path params:
|
||||||
|
character_id: Character ID
|
||||||
|
npc_id: NPC ID
|
||||||
|
|
||||||
|
Query params:
|
||||||
|
limit: Maximum messages to return (default 50, max 100)
|
||||||
|
offset: Number of messages to skip (default 0)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with messages and pagination info
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Get query parameters
|
||||||
|
limit = request.args.get('limit', 50, type=int)
|
||||||
|
offset = request.args.get('offset', 0, type=int)
|
||||||
|
|
||||||
|
# Validate parameters
|
||||||
|
if limit < 1:
|
||||||
|
return validation_error_response("limit must be at least 1")
|
||||||
|
if offset < 0:
|
||||||
|
return validation_error_response("offset must be at least 0")
|
||||||
|
|
||||||
|
# Get conversation history
|
||||||
|
chat_service = get_chat_message_service()
|
||||||
|
messages = chat_service.get_conversation_history(
|
||||||
|
character_id=character_id,
|
||||||
|
user_id=user.id,
|
||||||
|
npc_id=npc_id,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get NPC name if available
|
||||||
|
npc_loader = get_npc_loader()
|
||||||
|
npc = npc_loader.load_npc(npc_id)
|
||||||
|
npc_name = npc.name if npc else npc_id.replace('_', ' ').title()
|
||||||
|
|
||||||
|
# Convert messages to dict
|
||||||
|
message_dicts = [msg.to_dict() for msg in messages]
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"npc_id": npc_id,
|
||||||
|
"npc_name": npc_name,
|
||||||
|
"total_messages": len(messages), # Count in this batch
|
||||||
|
"messages": message_dicts,
|
||||||
|
"pagination": {
|
||||||
|
"limit": limit,
|
||||||
|
"offset": offset,
|
||||||
|
"has_more": len(messages) == limit # If we got a full batch, there might be more
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Retrieved conversation history",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
npc_id=npc_id,
|
||||||
|
count=len(messages))
|
||||||
|
|
||||||
|
return success_response(result)
|
||||||
|
|
||||||
|
except ChatMessagePermissionDenied as e:
|
||||||
|
logger.warning("Permission denied getting conversation",
|
||||||
|
user_id=user.id if 'user' in locals() else None,
|
||||||
|
character_id=character_id,
|
||||||
|
npc_id=npc_id)
|
||||||
|
return error_response(str(e), 403)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to get conversation history",
|
||||||
|
character_id=character_id,
|
||||||
|
npc_id=npc_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(f"Failed to retrieve conversation: {str(e)}", 500)
|
||||||
|
|
||||||
|
|
||||||
|
@chat_bp.route('/api/v1/characters/<character_id>/chats/search', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def search_messages(character_id: str):
|
||||||
|
"""
|
||||||
|
Search messages by text with optional filters.
|
||||||
|
|
||||||
|
Query params:
|
||||||
|
q (required): Search text to find in player_message and npc_response
|
||||||
|
npc_id (optional): Filter by specific NPC
|
||||||
|
context (optional): Filter by message context (dialogue, quest_offered, shop, etc.)
|
||||||
|
date_from (optional): Start date ISO format (e.g., 2025-11-25T00:00:00Z)
|
||||||
|
date_to (optional): End date ISO format
|
||||||
|
limit (optional): Maximum messages to return (default 50, max 100)
|
||||||
|
offset (optional): Number of messages to skip (default 0)
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with matching messages
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Get query parameters
|
||||||
|
search_text = request.args.get('q')
|
||||||
|
npc_id = request.args.get('npc_id')
|
||||||
|
context_str = request.args.get('context')
|
||||||
|
date_from = request.args.get('date_from')
|
||||||
|
date_to = request.args.get('date_to')
|
||||||
|
limit = request.args.get('limit', 50, type=int)
|
||||||
|
offset = request.args.get('offset', 0, type=int)
|
||||||
|
|
||||||
|
# Validate required parameters
|
||||||
|
if not search_text:
|
||||||
|
return validation_error_response("q (search text) is required")
|
||||||
|
|
||||||
|
# Validate optional parameters
|
||||||
|
if limit < 1:
|
||||||
|
return validation_error_response("limit must be at least 1")
|
||||||
|
if offset < 0:
|
||||||
|
return validation_error_response("offset must be at least 0")
|
||||||
|
|
||||||
|
# Parse context enum if provided
|
||||||
|
context = None
|
||||||
|
if context_str:
|
||||||
|
try:
|
||||||
|
context = MessageContext(context_str)
|
||||||
|
except ValueError:
|
||||||
|
valid_contexts = [c.value for c in MessageContext]
|
||||||
|
return validation_error_response(
|
||||||
|
f"Invalid context. Must be one of: {', '.join(valid_contexts)}"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Search messages
|
||||||
|
chat_service = get_chat_message_service()
|
||||||
|
messages = chat_service.search_messages(
|
||||||
|
character_id=character_id,
|
||||||
|
user_id=user.id,
|
||||||
|
search_text=search_text,
|
||||||
|
npc_id=npc_id,
|
||||||
|
context=context,
|
||||||
|
date_from=date_from,
|
||||||
|
date_to=date_to,
|
||||||
|
limit=limit,
|
||||||
|
offset=offset
|
||||||
|
)
|
||||||
|
|
||||||
|
# Convert messages to dict
|
||||||
|
message_dicts = [msg.to_dict() for msg in messages]
|
||||||
|
|
||||||
|
result = {
|
||||||
|
"search_text": search_text,
|
||||||
|
"filters": {
|
||||||
|
"npc_id": npc_id,
|
||||||
|
"context": context_str,
|
||||||
|
"date_from": date_from,
|
||||||
|
"date_to": date_to
|
||||||
|
},
|
||||||
|
"total_results": len(messages),
|
||||||
|
"messages": message_dicts,
|
||||||
|
"pagination": {
|
||||||
|
"limit": limit,
|
||||||
|
"offset": offset,
|
||||||
|
"has_more": len(messages) == limit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.info("Search completed",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
search_text=search_text,
|
||||||
|
results=len(messages))
|
||||||
|
|
||||||
|
return success_response(result)
|
||||||
|
|
||||||
|
except ChatMessagePermissionDenied as e:
|
||||||
|
logger.warning("Permission denied searching messages",
|
||||||
|
user_id=user.id if 'user' in locals() else None,
|
||||||
|
character_id=character_id)
|
||||||
|
return error_response(str(e), 403)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to search messages",
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(f"Search failed: {str(e)}", 500)
|
||||||
|
|
||||||
|
|
||||||
|
@chat_bp.route('/api/v1/characters/<character_id>/chats/<message_id>', methods=['DELETE'])
|
||||||
|
@require_auth
|
||||||
|
def delete_message(character_id: str, message_id: str):
|
||||||
|
"""
|
||||||
|
Soft delete a message (sets is_deleted=True).
|
||||||
|
|
||||||
|
Used for privacy/moderation. Message remains in database but filtered from queries.
|
||||||
|
|
||||||
|
Path params:
|
||||||
|
character_id: Character ID (for ownership validation)
|
||||||
|
message_id: Message ID to delete
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
JSON response with success confirmation
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Soft delete message
|
||||||
|
chat_service = get_chat_message_service()
|
||||||
|
success = chat_service.soft_delete_message(
|
||||||
|
message_id=message_id,
|
||||||
|
character_id=character_id,
|
||||||
|
user_id=user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Message deleted",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
message_id=message_id)
|
||||||
|
|
||||||
|
return success_response({
|
||||||
|
"message_id": message_id,
|
||||||
|
"deleted": success
|
||||||
|
})
|
||||||
|
|
||||||
|
except ChatMessageNotFound as e:
|
||||||
|
logger.warning("Message not found for deletion",
|
||||||
|
message_id=message_id,
|
||||||
|
character_id=character_id)
|
||||||
|
return not_found_response(str(e))
|
||||||
|
except ChatMessagePermissionDenied as e:
|
||||||
|
logger.warning("Permission denied deleting message",
|
||||||
|
user_id=user.id if 'user' in locals() else None,
|
||||||
|
character_id=character_id,
|
||||||
|
message_id=message_id)
|
||||||
|
return error_response(str(e), 403)
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to delete message",
|
||||||
|
message_id=message_id,
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(f"Delete failed: {str(e)}", 500)
|
||||||
1093
api/app/api/combat.py
Normal file
1093
api/app/api/combat.py
Normal file
File diff suppressed because it is too large
Load Diff
639
api/app/api/inventory.py
Normal file
639
api/app/api/inventory.py
Normal file
@@ -0,0 +1,639 @@
|
|||||||
|
"""
|
||||||
|
Inventory API Blueprint
|
||||||
|
|
||||||
|
Endpoints for managing character inventory and equipment.
|
||||||
|
All endpoints require authentication and enforce ownership validation.
|
||||||
|
|
||||||
|
Endpoints:
|
||||||
|
- GET /api/v1/characters/<id>/inventory - Get character inventory and equipped items
|
||||||
|
- POST /api/v1/characters/<id>/inventory/equip - Equip an item
|
||||||
|
- POST /api/v1/characters/<id>/inventory/unequip - Unequip an item
|
||||||
|
- POST /api/v1/characters/<id>/inventory/use - Use a consumable item
|
||||||
|
- DELETE /api/v1/characters/<id>/inventory/<item_id> - Drop an item
|
||||||
|
"""
|
||||||
|
|
||||||
|
from flask import Blueprint, request
|
||||||
|
|
||||||
|
from app.services.inventory_service import (
|
||||||
|
get_inventory_service,
|
||||||
|
ItemNotFoundError,
|
||||||
|
CannotEquipError,
|
||||||
|
InvalidSlotError,
|
||||||
|
CannotUseItemError,
|
||||||
|
InventoryFullError,
|
||||||
|
VALID_SLOTS,
|
||||||
|
MAX_INVENTORY_SIZE,
|
||||||
|
)
|
||||||
|
from app.services.character_service import (
|
||||||
|
get_character_service,
|
||||||
|
CharacterNotFound,
|
||||||
|
)
|
||||||
|
from app.utils.response import (
|
||||||
|
success_response,
|
||||||
|
error_response,
|
||||||
|
not_found_response,
|
||||||
|
validation_error_response,
|
||||||
|
)
|
||||||
|
from app.utils.auth import require_auth, get_current_user
|
||||||
|
from app.utils.logging import get_logger
|
||||||
|
|
||||||
|
|
||||||
|
# Initialize logger
|
||||||
|
logger = get_logger(__file__)
|
||||||
|
|
||||||
|
# Create blueprint
|
||||||
|
inventory_bp = Blueprint('inventory', __name__)
|
||||||
|
|
||||||
|
|
||||||
|
# =============================================================================
|
||||||
|
# API Endpoints
|
||||||
|
# =============================================================================
|
||||||
|
|
||||||
|
@inventory_bp.route('/api/v1/characters/<character_id>/inventory', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def get_inventory(character_id: str):
|
||||||
|
"""
|
||||||
|
Get character inventory and equipped items.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character_id: Character ID
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Inventory and equipment data
|
||||||
|
401: Not authenticated
|
||||||
|
404: Character not found or not owned by user
|
||||||
|
500: Internal server error
|
||||||
|
|
||||||
|
Example Response:
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"inventory": [
|
||||||
|
{
|
||||||
|
"item_id": "gen_abc123",
|
||||||
|
"name": "Flaming Dagger",
|
||||||
|
"item_type": "weapon",
|
||||||
|
"rarity": "rare",
|
||||||
|
...
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"equipped": {
|
||||||
|
"weapon": {...},
|
||||||
|
"helmet": null,
|
||||||
|
...
|
||||||
|
},
|
||||||
|
"inventory_count": 5,
|
||||||
|
"max_inventory": 100
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
logger.info("Getting inventory",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id)
|
||||||
|
|
||||||
|
# Get character (validates ownership)
|
||||||
|
char_service = get_character_service()
|
||||||
|
character = char_service.get_character(character_id, user.id)
|
||||||
|
|
||||||
|
# Get inventory service
|
||||||
|
inventory_service = get_inventory_service()
|
||||||
|
|
||||||
|
# Get inventory items
|
||||||
|
inventory_items = inventory_service.get_inventory(character)
|
||||||
|
|
||||||
|
# Get equipped items
|
||||||
|
equipped_items = inventory_service.get_equipped_items(character)
|
||||||
|
|
||||||
|
# Build equipped dict with all slots (None for empty slots)
|
||||||
|
equipped_response = {}
|
||||||
|
for slot in VALID_SLOTS:
|
||||||
|
item = equipped_items.get(slot)
|
||||||
|
equipped_response[slot] = item.to_dict() if item else None
|
||||||
|
|
||||||
|
logger.info("Inventory retrieved successfully",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_count=len(inventory_items))
|
||||||
|
|
||||||
|
return success_response(
|
||||||
|
result={
|
||||||
|
"inventory": [item.to_dict() for item in inventory_items],
|
||||||
|
"equipped": equipped_response,
|
||||||
|
"inventory_count": len(inventory_items),
|
||||||
|
"max_inventory": MAX_INVENTORY_SIZE,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except CharacterNotFound as e:
|
||||||
|
logger.warning("Character not found",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to get inventory",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="INVENTORY_GET_ERROR",
|
||||||
|
message="Failed to retrieve inventory",
|
||||||
|
status=500
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inventory_bp.route('/api/v1/characters/<character_id>/inventory/equip', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def equip_item(character_id: str):
|
||||||
|
"""
|
||||||
|
Equip an item from inventory to a specified slot.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character_id: Character ID
|
||||||
|
|
||||||
|
Request Body:
|
||||||
|
{
|
||||||
|
"item_id": "gen_abc123",
|
||||||
|
"slot": "weapon"
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Item equipped successfully
|
||||||
|
400: Cannot equip item (wrong type, level requirement, etc.)
|
||||||
|
401: Not authenticated
|
||||||
|
404: Character or item not found
|
||||||
|
422: Validation error (invalid slot)
|
||||||
|
500: Internal server error
|
||||||
|
|
||||||
|
Example Response:
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"message": "Equipped Flaming Dagger to weapon slot",
|
||||||
|
"equipped": {...},
|
||||||
|
"unequipped_item": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Get request data
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return validation_error_response(
|
||||||
|
message="Request body is required",
|
||||||
|
details={"error": "Request body is required"}
|
||||||
|
)
|
||||||
|
|
||||||
|
item_id = data.get('item_id', '').strip()
|
||||||
|
slot = data.get('slot', '').strip().lower()
|
||||||
|
|
||||||
|
# Validate required fields
|
||||||
|
validation_errors = {}
|
||||||
|
|
||||||
|
if not item_id:
|
||||||
|
validation_errors['item_id'] = "item_id is required"
|
||||||
|
|
||||||
|
if not slot:
|
||||||
|
validation_errors['slot'] = "slot is required"
|
||||||
|
|
||||||
|
if validation_errors:
|
||||||
|
return validation_error_response(
|
||||||
|
message="Validation failed",
|
||||||
|
details=validation_errors
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Equipping item",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id,
|
||||||
|
slot=slot)
|
||||||
|
|
||||||
|
# Get character (validates ownership)
|
||||||
|
char_service = get_character_service()
|
||||||
|
character = char_service.get_character(character_id, user.id)
|
||||||
|
|
||||||
|
# Get inventory service
|
||||||
|
inventory_service = get_inventory_service()
|
||||||
|
|
||||||
|
# Equip item
|
||||||
|
previous_item = inventory_service.equip_item(
|
||||||
|
character, item_id, slot, user.id
|
||||||
|
)
|
||||||
|
|
||||||
|
# Get item name for message
|
||||||
|
equipped_item = character.equipped.get(slot)
|
||||||
|
item_name = equipped_item.get_display_name() if equipped_item else item_id
|
||||||
|
|
||||||
|
# Build equipped response
|
||||||
|
equipped_response = {}
|
||||||
|
for s in VALID_SLOTS:
|
||||||
|
item = character.equipped.get(s)
|
||||||
|
equipped_response[s] = item.to_dict() if item else None
|
||||||
|
|
||||||
|
logger.info("Item equipped successfully",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id,
|
||||||
|
slot=slot,
|
||||||
|
previous_item=previous_item.item_id if previous_item else None)
|
||||||
|
|
||||||
|
return success_response(
|
||||||
|
result={
|
||||||
|
"message": f"Equipped {item_name} to {slot} slot",
|
||||||
|
"equipped": equipped_response,
|
||||||
|
"unequipped_item": previous_item.to_dict() if previous_item else None,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except CharacterNotFound as e:
|
||||||
|
logger.warning("Character not found for equip",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except ItemNotFoundError as e:
|
||||||
|
logger.warning("Item not found for equip",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id if 'item_id' in locals() else 'unknown',
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except InvalidSlotError as e:
|
||||||
|
logger.warning("Invalid slot for equip",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
slot=slot if 'slot' in locals() else 'unknown',
|
||||||
|
error=str(e))
|
||||||
|
return validation_error_response(
|
||||||
|
message=str(e),
|
||||||
|
details={"slot": str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
except CannotEquipError as e:
|
||||||
|
logger.warning("Cannot equip item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id if 'item_id' in locals() else 'unknown',
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="CANNOT_EQUIP",
|
||||||
|
message=str(e),
|
||||||
|
status=400
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to equip item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="EQUIP_ERROR",
|
||||||
|
message="Failed to equip item",
|
||||||
|
status=500
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inventory_bp.route('/api/v1/characters/<character_id>/inventory/unequip', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def unequip_item(character_id: str):
|
||||||
|
"""
|
||||||
|
Unequip an item from a specified slot (returns to inventory).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character_id: Character ID
|
||||||
|
|
||||||
|
Request Body:
|
||||||
|
{
|
||||||
|
"slot": "weapon"
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Item unequipped successfully (or slot was empty)
|
||||||
|
400: Inventory full, cannot unequip
|
||||||
|
401: Not authenticated
|
||||||
|
404: Character not found
|
||||||
|
422: Validation error (invalid slot)
|
||||||
|
500: Internal server error
|
||||||
|
|
||||||
|
Example Response:
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"message": "Unequipped Flaming Dagger from weapon slot",
|
||||||
|
"unequipped_item": {...},
|
||||||
|
"equipped": {...}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Get request data
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return validation_error_response(
|
||||||
|
message="Request body is required",
|
||||||
|
details={"error": "Request body is required"}
|
||||||
|
)
|
||||||
|
|
||||||
|
slot = data.get('slot', '').strip().lower()
|
||||||
|
|
||||||
|
if not slot:
|
||||||
|
return validation_error_response(
|
||||||
|
message="Validation failed",
|
||||||
|
details={"slot": "slot is required"}
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Unequipping item",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
slot=slot)
|
||||||
|
|
||||||
|
# Get character (validates ownership)
|
||||||
|
char_service = get_character_service()
|
||||||
|
character = char_service.get_character(character_id, user.id)
|
||||||
|
|
||||||
|
# Get inventory service
|
||||||
|
inventory_service = get_inventory_service()
|
||||||
|
|
||||||
|
# Unequip item
|
||||||
|
unequipped_item = inventory_service.unequip_item(character, slot, user.id)
|
||||||
|
|
||||||
|
# Build equipped response
|
||||||
|
equipped_response = {}
|
||||||
|
for s in VALID_SLOTS:
|
||||||
|
item = character.equipped.get(s)
|
||||||
|
equipped_response[s] = item.to_dict() if item else None
|
||||||
|
|
||||||
|
# Build message
|
||||||
|
if unequipped_item:
|
||||||
|
message = f"Unequipped {unequipped_item.get_display_name()} from {slot} slot"
|
||||||
|
else:
|
||||||
|
message = f"Slot '{slot}' was already empty"
|
||||||
|
|
||||||
|
logger.info("Item unequipped",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
slot=slot,
|
||||||
|
unequipped_item=unequipped_item.item_id if unequipped_item else None)
|
||||||
|
|
||||||
|
return success_response(
|
||||||
|
result={
|
||||||
|
"message": message,
|
||||||
|
"unequipped_item": unequipped_item.to_dict() if unequipped_item else None,
|
||||||
|
"equipped": equipped_response,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except CharacterNotFound as e:
|
||||||
|
logger.warning("Character not found for unequip",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except InvalidSlotError as e:
|
||||||
|
logger.warning("Invalid slot for unequip",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
slot=slot if 'slot' in locals() else 'unknown',
|
||||||
|
error=str(e))
|
||||||
|
return validation_error_response(
|
||||||
|
message=str(e),
|
||||||
|
details={"slot": str(e)}
|
||||||
|
)
|
||||||
|
|
||||||
|
except InventoryFullError as e:
|
||||||
|
logger.warning("Inventory full, cannot unequip",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="INVENTORY_FULL",
|
||||||
|
message=str(e),
|
||||||
|
status=400
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to unequip item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="UNEQUIP_ERROR",
|
||||||
|
message="Failed to unequip item",
|
||||||
|
status=500
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inventory_bp.route('/api/v1/characters/<character_id>/inventory/use', methods=['POST'])
|
||||||
|
@require_auth
|
||||||
|
def use_item(character_id: str):
|
||||||
|
"""
|
||||||
|
Use a consumable item from inventory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character_id: Character ID
|
||||||
|
|
||||||
|
Request Body:
|
||||||
|
{
|
||||||
|
"item_id": "health_potion_small"
|
||||||
|
}
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Item used successfully
|
||||||
|
400: Cannot use item (not consumable)
|
||||||
|
401: Not authenticated
|
||||||
|
404: Character or item not found
|
||||||
|
500: Internal server error
|
||||||
|
|
||||||
|
Example Response:
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"item_used": "Small Health Potion",
|
||||||
|
"effects_applied": [
|
||||||
|
{
|
||||||
|
"effect_name": "Healing",
|
||||||
|
"effect_type": "hot",
|
||||||
|
"value": 25,
|
||||||
|
"message": "Restored 25 HP"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"hp_restored": 25,
|
||||||
|
"mp_restored": 0,
|
||||||
|
"message": "Used Small Health Potion: Restored 25 HP"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
# Get request data
|
||||||
|
data = request.get_json()
|
||||||
|
|
||||||
|
if not data:
|
||||||
|
return validation_error_response(
|
||||||
|
message="Request body is required",
|
||||||
|
details={"error": "Request body is required"}
|
||||||
|
)
|
||||||
|
|
||||||
|
item_id = data.get('item_id', '').strip()
|
||||||
|
|
||||||
|
if not item_id:
|
||||||
|
return validation_error_response(
|
||||||
|
message="Validation failed",
|
||||||
|
details={"item_id": "item_id is required"}
|
||||||
|
)
|
||||||
|
|
||||||
|
logger.info("Using item",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id)
|
||||||
|
|
||||||
|
# Get character (validates ownership)
|
||||||
|
char_service = get_character_service()
|
||||||
|
character = char_service.get_character(character_id, user.id)
|
||||||
|
|
||||||
|
# Get inventory service
|
||||||
|
inventory_service = get_inventory_service()
|
||||||
|
|
||||||
|
# Use consumable
|
||||||
|
result = inventory_service.use_consumable(character, item_id, user.id)
|
||||||
|
|
||||||
|
logger.info("Item used successfully",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id,
|
||||||
|
hp_restored=result.hp_restored,
|
||||||
|
mp_restored=result.mp_restored)
|
||||||
|
|
||||||
|
return success_response(result=result.to_dict())
|
||||||
|
|
||||||
|
except CharacterNotFound as e:
|
||||||
|
logger.warning("Character not found for use item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except ItemNotFoundError as e:
|
||||||
|
logger.warning("Item not found for use",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id if 'item_id' in locals() else 'unknown',
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except CannotUseItemError as e:
|
||||||
|
logger.warning("Cannot use item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id if 'item_id' in locals() else 'unknown',
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="CANNOT_USE_ITEM",
|
||||||
|
message=str(e),
|
||||||
|
status=400
|
||||||
|
)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to use item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="USE_ITEM_ERROR",
|
||||||
|
message="Failed to use item",
|
||||||
|
status=500
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@inventory_bp.route('/api/v1/characters/<character_id>/inventory/<item_id>', methods=['DELETE'])
|
||||||
|
@require_auth
|
||||||
|
def drop_item(character_id: str, item_id: str):
|
||||||
|
"""
|
||||||
|
Drop (remove) an item from inventory.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
character_id: Character ID
|
||||||
|
item_id: Item ID to drop
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Item dropped successfully
|
||||||
|
401: Not authenticated
|
||||||
|
404: Character or item not found
|
||||||
|
500: Internal server error
|
||||||
|
|
||||||
|
Example Response:
|
||||||
|
{
|
||||||
|
"result": {
|
||||||
|
"message": "Dropped Rusty Sword",
|
||||||
|
"dropped_item": {...},
|
||||||
|
"inventory_count": 4
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
user = get_current_user()
|
||||||
|
|
||||||
|
logger.info("Dropping item",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id)
|
||||||
|
|
||||||
|
# Get character (validates ownership)
|
||||||
|
char_service = get_character_service()
|
||||||
|
character = char_service.get_character(character_id, user.id)
|
||||||
|
|
||||||
|
# Get inventory service
|
||||||
|
inventory_service = get_inventory_service()
|
||||||
|
|
||||||
|
# Drop item
|
||||||
|
dropped_item = inventory_service.drop_item(character, item_id, user.id)
|
||||||
|
|
||||||
|
logger.info("Item dropped successfully",
|
||||||
|
user_id=user.id,
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id,
|
||||||
|
item_name=dropped_item.get_display_name())
|
||||||
|
|
||||||
|
return success_response(
|
||||||
|
result={
|
||||||
|
"message": f"Dropped {dropped_item.get_display_name()}",
|
||||||
|
"dropped_item": dropped_item.to_dict(),
|
||||||
|
"inventory_count": len(character.inventory),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
except CharacterNotFound as e:
|
||||||
|
logger.warning("Character not found for drop",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except ItemNotFoundError as e:
|
||||||
|
logger.warning("Item not found for drop",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response(message=str(e))
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to drop item",
|
||||||
|
user_id=user.id if 'user' in locals() else 'unknown',
|
||||||
|
character_id=character_id,
|
||||||
|
item_id=item_id,
|
||||||
|
error=str(e))
|
||||||
|
return error_response(
|
||||||
|
code="DROP_ITEM_ERROR",
|
||||||
|
message="Failed to drop item",
|
||||||
|
status=500
|
||||||
|
)
|
||||||
@@ -281,6 +281,7 @@ def get_npcs_at_location(location_id: str):
|
|||||||
"role": npc.role,
|
"role": npc.role,
|
||||||
"appearance": npc.appearance.brief,
|
"appearance": npc.appearance.brief,
|
||||||
"tags": npc.tags,
|
"tags": npc.tags,
|
||||||
|
"image_url": npc.image_url,
|
||||||
})
|
})
|
||||||
|
|
||||||
return success_response({
|
return success_response({
|
||||||
|
|||||||
@@ -132,23 +132,44 @@ def list_sessions():
|
|||||||
user = get_current_user()
|
user = get_current_user()
|
||||||
user_id = user.id
|
user_id = user.id
|
||||||
session_service = get_session_service()
|
session_service = get_session_service()
|
||||||
|
character_service = get_character_service()
|
||||||
|
|
||||||
# Get user's active sessions
|
# Get user's active sessions
|
||||||
sessions = session_service.get_user_sessions(user_id, active_only=True)
|
sessions = session_service.get_user_sessions(user_id, active_only=True)
|
||||||
|
|
||||||
|
# Build character name lookup for efficiency
|
||||||
|
character_ids = [s.solo_character_id for s in sessions if s.solo_character_id]
|
||||||
|
character_names = {}
|
||||||
|
for char_id in character_ids:
|
||||||
|
try:
|
||||||
|
char = character_service.get_character(char_id, user_id)
|
||||||
|
if char:
|
||||||
|
character_names[char_id] = char.name
|
||||||
|
except Exception:
|
||||||
|
pass # Character may have been deleted
|
||||||
|
|
||||||
# Build response with basic session info
|
# Build response with basic session info
|
||||||
sessions_list = []
|
sessions_list = []
|
||||||
for session in sessions:
|
for session in sessions:
|
||||||
|
# Get combat round if in combat
|
||||||
|
combat_round = None
|
||||||
|
if session.is_in_combat() and session.combat_encounter:
|
||||||
|
combat_round = session.combat_encounter.round_number
|
||||||
|
|
||||||
sessions_list.append({
|
sessions_list.append({
|
||||||
'session_id': session.session_id,
|
'session_id': session.session_id,
|
||||||
'character_id': session.solo_character_id,
|
'character_id': session.solo_character_id,
|
||||||
|
'character_name': character_names.get(session.solo_character_id),
|
||||||
'turn_number': session.turn_number,
|
'turn_number': session.turn_number,
|
||||||
'status': session.status.value,
|
'status': session.status.value,
|
||||||
'created_at': session.created_at,
|
'created_at': session.created_at,
|
||||||
'last_activity': session.last_activity,
|
'last_activity': session.last_activity,
|
||||||
|
'in_combat': session.is_in_combat(),
|
||||||
'game_state': {
|
'game_state': {
|
||||||
'current_location': session.game_state.current_location,
|
'current_location': session.game_state.current_location,
|
||||||
'location_type': session.game_state.location_type.value
|
'location_type': session.game_state.location_type.value,
|
||||||
|
'in_combat': session.is_in_combat(),
|
||||||
|
'combat_round': combat_round
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -235,7 +256,7 @@ def create_session():
|
|||||||
return error_response(
|
return error_response(
|
||||||
status=409,
|
status=409,
|
||||||
code="SESSION_LIMIT_EXCEEDED",
|
code="SESSION_LIMIT_EXCEEDED",
|
||||||
message="Maximum active sessions limit reached (5). Please end an existing session first."
|
message=str(e)
|
||||||
)
|
)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
@@ -485,10 +506,12 @@ def get_session_state(session_id: str):
|
|||||||
"character_id": session.get_character_id(),
|
"character_id": session.get_character_id(),
|
||||||
"turn_number": session.turn_number,
|
"turn_number": session.turn_number,
|
||||||
"status": session.status.value,
|
"status": session.status.value,
|
||||||
|
"in_combat": session.is_in_combat(),
|
||||||
"game_state": {
|
"game_state": {
|
||||||
"current_location": session.game_state.current_location,
|
"current_location": session.game_state.current_location,
|
||||||
"location_type": session.game_state.location_type.value,
|
"location_type": session.game_state.location_type.value,
|
||||||
"active_quests": session.game_state.active_quests
|
"active_quests": session.game_state.active_quests,
|
||||||
|
"in_combat": session.is_in_combat()
|
||||||
},
|
},
|
||||||
"available_actions": available_actions
|
"available_actions": available_actions
|
||||||
})
|
})
|
||||||
@@ -602,3 +625,111 @@ def get_history(session_id: str):
|
|||||||
code="HISTORY_ERROR",
|
code="HISTORY_ERROR",
|
||||||
message="Failed to get conversation history"
|
message="Failed to get conversation history"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@sessions_bp.route('/api/v1/sessions/<session_id>', methods=['DELETE'])
|
||||||
|
@require_auth
|
||||||
|
def delete_session(session_id: str):
|
||||||
|
"""
|
||||||
|
Permanently delete a game session.
|
||||||
|
|
||||||
|
This removes the session from the database entirely. The session cannot be
|
||||||
|
recovered after deletion. Use this to free up session slots for users who
|
||||||
|
have reached their tier limit.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Session deleted successfully
|
||||||
|
401: Not authenticated
|
||||||
|
404: Session not found or not owned by user
|
||||||
|
500: Internal server error
|
||||||
|
"""
|
||||||
|
logger.info("Deleting session", session_id=session_id)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get current user
|
||||||
|
user = get_current_user()
|
||||||
|
user_id = user.id
|
||||||
|
|
||||||
|
# Delete session (validates ownership internally)
|
||||||
|
session_service = get_session_service()
|
||||||
|
session_service.delete_session(session_id, user_id)
|
||||||
|
|
||||||
|
logger.info("Session deleted successfully",
|
||||||
|
session_id=session_id,
|
||||||
|
user_id=user_id)
|
||||||
|
|
||||||
|
return success_response({
|
||||||
|
"message": "Session deleted successfully",
|
||||||
|
"session_id": session_id
|
||||||
|
})
|
||||||
|
|
||||||
|
except SessionNotFound as e:
|
||||||
|
logger.warning("Session not found for deletion",
|
||||||
|
session_id=session_id,
|
||||||
|
error=str(e))
|
||||||
|
return not_found_response("Session not found")
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to delete session",
|
||||||
|
session_id=session_id,
|
||||||
|
error=str(e),
|
||||||
|
exc_info=True)
|
||||||
|
return error_response(
|
||||||
|
status=500,
|
||||||
|
code="SESSION_DELETE_ERROR",
|
||||||
|
message="Failed to delete session"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@sessions_bp.route('/api/v1/usage', methods=['GET'])
|
||||||
|
@require_auth
|
||||||
|
def get_usage():
|
||||||
|
"""
|
||||||
|
Get user's daily usage information.
|
||||||
|
|
||||||
|
Returns the current daily turn usage, limit, remaining turns,
|
||||||
|
and reset time. Limits are based on user's subscription tier.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
200: Usage information
|
||||||
|
{
|
||||||
|
"user_id": "user_123",
|
||||||
|
"user_tier": "free",
|
||||||
|
"current_usage": 15,
|
||||||
|
"daily_limit": 50,
|
||||||
|
"remaining": 35,
|
||||||
|
"reset_time": "2025-11-27T00:00:00+00:00",
|
||||||
|
"is_limited": false,
|
||||||
|
"is_unlimited": false
|
||||||
|
}
|
||||||
|
401: Not authenticated
|
||||||
|
500: Internal server error
|
||||||
|
"""
|
||||||
|
logger.info("Getting usage info")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Get current user and tier
|
||||||
|
user = get_current_user()
|
||||||
|
user_id = user.id
|
||||||
|
user_tier = get_user_tier_from_user(user)
|
||||||
|
|
||||||
|
# Get usage info from rate limiter
|
||||||
|
rate_limiter = RateLimiterService()
|
||||||
|
usage_info = rate_limiter.get_usage_info(user_id, user_tier)
|
||||||
|
|
||||||
|
logger.debug("Usage info retrieved",
|
||||||
|
user_id=user_id,
|
||||||
|
current_usage=usage_info.get('current_usage'),
|
||||||
|
remaining=usage_info.get('remaining'))
|
||||||
|
|
||||||
|
return success_response(usage_info)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error("Failed to get usage info",
|
||||||
|
error=str(e),
|
||||||
|
exc_info=True)
|
||||||
|
return error_response(
|
||||||
|
status=500,
|
||||||
|
code="USAGE_ERROR",
|
||||||
|
message="Failed to get usage information"
|
||||||
|
)
|
||||||
|
|||||||
@@ -76,6 +76,7 @@ class RateLimitTier:
|
|||||||
ai_calls_per_day: int
|
ai_calls_per_day: int
|
||||||
custom_actions_per_day: int # -1 for unlimited
|
custom_actions_per_day: int # -1 for unlimited
|
||||||
custom_action_char_limit: int
|
custom_action_char_limit: int
|
||||||
|
max_sessions: int = 1 # Maximum active game sessions allowed
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -86,6 +87,14 @@ class RateLimitingConfig:
|
|||||||
tiers: Dict[str, RateLimitTier] = field(default_factory=dict)
|
tiers: Dict[str, RateLimitTier] = field(default_factory=dict)
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class SessionCacheConfig:
|
||||||
|
"""Session cache configuration for reducing Appwrite API calls."""
|
||||||
|
enabled: bool = True
|
||||||
|
ttl_seconds: int = 300 # 5 minutes
|
||||||
|
redis_db: int = 2 # Separate from RQ (db 0) and rate limiting (db 1)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class AuthConfig:
|
class AuthConfig:
|
||||||
"""Authentication configuration."""
|
"""Authentication configuration."""
|
||||||
@@ -104,6 +113,7 @@ class AuthConfig:
|
|||||||
name_min_length: int
|
name_min_length: int
|
||||||
name_max_length: int
|
name_max_length: int
|
||||||
email_max_length: int
|
email_max_length: int
|
||||||
|
session_cache: SessionCacheConfig = field(default_factory=SessionCacheConfig)
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
@@ -229,7 +239,11 @@ class Config:
|
|||||||
tiers=rate_limit_tiers
|
tiers=rate_limit_tiers
|
||||||
)
|
)
|
||||||
|
|
||||||
auth_config = AuthConfig(**config_data['auth'])
|
# Parse auth config with nested session_cache
|
||||||
|
auth_data = config_data['auth'].copy()
|
||||||
|
session_cache_data = auth_data.pop('session_cache', {})
|
||||||
|
session_cache_config = SessionCacheConfig(**session_cache_data) if session_cache_data else SessionCacheConfig()
|
||||||
|
auth_config = AuthConfig(**auth_data, session_cache=session_cache_config)
|
||||||
session_config = SessionConfig(**config_data['session'])
|
session_config = SessionConfig(**config_data['session'])
|
||||||
marketplace_config = MarketplaceConfig(**config_data['marketplace'])
|
marketplace_config = MarketplaceConfig(**config_data['marketplace'])
|
||||||
cors_config = CORSConfig(**config_data['cors'])
|
cors_config = CORSConfig(**config_data['cors'])
|
||||||
|
|||||||
34
api/app/data/abilities/absolute_zero.yaml
Normal file
34
api/app/data/abilities/absolute_zero.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Absolute Zero - Arcanist Cryomancy ultimate
|
||||||
|
# Ultimate freeze all enemies
|
||||||
|
|
||||||
|
ability_id: "absolute_zero"
|
||||||
|
name: "Absolute Zero"
|
||||||
|
description: "Lower the temperature to absolute zero, freezing all enemies solid and dealing massive ice damage"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 90
|
||||||
|
damage_type: "ice"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 70
|
||||||
|
cooldown: 6
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "absolute_freeze"
|
||||||
|
name: "Absolute Zero"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 2
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "absolute_zero"
|
||||||
|
- effect_id: "shattered"
|
||||||
|
name: "Shattered"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 2
|
||||||
|
power: 20
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "absolute_zero"
|
||||||
16
api/app/data/abilities/aimed_shot.yaml
Normal file
16
api/app/data/abilities/aimed_shot.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Aimed Shot - Wildstrider Marksmanship ability
|
||||||
|
# High accuracy ranged attack
|
||||||
|
|
||||||
|
ability_id: "aimed_shot"
|
||||||
|
name: "Aimed Shot"
|
||||||
|
description: "Take careful aim and fire a precise shot at your target"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 18
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 8
|
||||||
|
cooldown: 1
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/arcane_brilliance.yaml
Normal file
25
api/app/data/abilities/arcane_brilliance.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Arcane Brilliance - Lorekeeper Arcane Weaving ability
|
||||||
|
# Intelligence buff
|
||||||
|
|
||||||
|
ability_id: "arcane_brilliance"
|
||||||
|
name: "Arcane Brilliance"
|
||||||
|
description: "Grant an ally increased intelligence and magical power"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.4
|
||||||
|
mana_cost: 10
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "arcane_brilliance_buff"
|
||||||
|
name: "Arcane Brilliance"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 5
|
||||||
|
power: 10
|
||||||
|
stat_affected: "intelligence"
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "arcane_brilliance"
|
||||||
25
api/app/data/abilities/arcane_weakness.yaml
Normal file
25
api/app/data/abilities/arcane_weakness.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Arcane Weakness - Lorekeeper Arcane Weaving ability
|
||||||
|
# Stat debuff on enemy
|
||||||
|
|
||||||
|
ability_id: "arcane_weakness"
|
||||||
|
name: "Arcane Weakness"
|
||||||
|
description: "Expose the weaknesses in your enemy's defenses, reducing their resistances"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 25
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "weakened_defenses"
|
||||||
|
name: "Weakened"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 4
|
||||||
|
power: 25
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "arcane_weakness"
|
||||||
25
api/app/data/abilities/army_of_the_dead.yaml
Normal file
25
api/app/data/abilities/army_of_the_dead.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Army of the Dead - Necromancer Raise Dead ultimate
|
||||||
|
# Summon undead army
|
||||||
|
|
||||||
|
ability_id: "army_of_the_dead"
|
||||||
|
name: "Army of the Dead"
|
||||||
|
description: "Raise an entire army of undead to overwhelm your enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 80
|
||||||
|
damage_type: "shadow"
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 70
|
||||||
|
cooldown: 8
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "undead_army"
|
||||||
|
name: "Army of the Dead"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 5
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "army_of_the_dead"
|
||||||
25
api/app/data/abilities/bestial_wrath.yaml
Normal file
25
api/app/data/abilities/bestial_wrath.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Bestial Wrath - Wildstrider Beast Companion ability
|
||||||
|
# Pet damage buff
|
||||||
|
|
||||||
|
ability_id: "bestial_wrath"
|
||||||
|
name: "Bestial Wrath"
|
||||||
|
description: "Enrage your companion, increasing their damage for 3 turns"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.4
|
||||||
|
mana_cost: 25
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "enraged_companion"
|
||||||
|
name: "Enraged Companion"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 3
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "bestial_wrath"
|
||||||
16
api/app/data/abilities/blessed_sacrifice.yaml
Normal file
16
api/app/data/abilities/blessed_sacrifice.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Blessed Sacrifice - Oathkeeper Redemption ability
|
||||||
|
# Transfer ally wounds to self
|
||||||
|
|
||||||
|
ability_id: "blessed_sacrifice"
|
||||||
|
name: "Blessed Sacrifice"
|
||||||
|
description: "Take an ally's wounds upon yourself, healing them while damaging yourself"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 50
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 25
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/blizzard.yaml
Normal file
25
api/app/data/abilities/blizzard.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Blizzard - Arcanist Cryomancy ability
|
||||||
|
# AoE ice damage with slow
|
||||||
|
|
||||||
|
ability_id: "blizzard"
|
||||||
|
name: "Blizzard"
|
||||||
|
description: "Summon a devastating blizzard that damages and slows all enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 40
|
||||||
|
damage_type: "ice"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 32
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "frostbitten"
|
||||||
|
name: "Frostbitten"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 3
|
||||||
|
power: 30
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "blizzard"
|
||||||
16
api/app/data/abilities/cleanse.yaml
Normal file
16
api/app/data/abilities/cleanse.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Cleanse - Oathkeeper Redemption ability
|
||||||
|
# Remove all debuffs
|
||||||
|
|
||||||
|
ability_id: "cleanse"
|
||||||
|
name: "Cleanse"
|
||||||
|
description: "Purify an ally, removing all negative effects"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.3
|
||||||
|
mana_cost: 18
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/cleave.yaml
Normal file
16
api/app/data/abilities/cleave.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Cleave - Vanguard Weapon Master ability
|
||||||
|
# AoE attack hitting all enemies
|
||||||
|
|
||||||
|
ability_id: "cleave"
|
||||||
|
name: "Cleave"
|
||||||
|
description: "Swing your weapon in a wide arc, hitting all enemies"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 20
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "strength"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 15
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/confuse.yaml
Normal file
25
api/app/data/abilities/confuse.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Confuse - Lorekeeper Illusionist ability
|
||||||
|
# Random target attacks
|
||||||
|
|
||||||
|
ability_id: "confuse"
|
||||||
|
name: "Confuse"
|
||||||
|
description: "Confuse your enemy, causing them to attack random targets"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 12
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "confused"
|
||||||
|
name: "Confused"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 2
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "confuse"
|
||||||
25
api/app/data/abilities/consecrated_ground.yaml
Normal file
25
api/app/data/abilities/consecrated_ground.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Consecrated Ground - Oathkeeper Aegis of Light ability
|
||||||
|
# Ground buff with damage reduction zone
|
||||||
|
|
||||||
|
ability_id: "consecrated_ground"
|
||||||
|
name: "Consecrated Ground"
|
||||||
|
description: "Consecrate the ground, creating a zone that reduces damage taken by all allies standing within"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.4
|
||||||
|
mana_cost: 30
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "consecrated_protection"
|
||||||
|
name: "Consecrated"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 3
|
||||||
|
power: 25
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "consecrated_ground"
|
||||||
25
api/app/data/abilities/consecration.yaml
Normal file
25
api/app/data/abilities/consecration.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Consecration - Luminary Radiant Judgment ability
|
||||||
|
# Ground AoE holy damage
|
||||||
|
|
||||||
|
ability_id: "consecration"
|
||||||
|
name: "Consecration"
|
||||||
|
description: "Consecrate the ground beneath your feet, dealing holy damage to all nearby enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 40
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 28
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "consecrated_ground"
|
||||||
|
name: "Consecrated"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 3
|
||||||
|
power: 10
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "consecration"
|
||||||
16
api/app/data/abilities/coordinated_attack.yaml
Normal file
16
api/app/data/abilities/coordinated_attack.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Coordinated Attack - Wildstrider Beast Companion ability
|
||||||
|
# Attack with pet
|
||||||
|
|
||||||
|
ability_id: "coordinated_attack"
|
||||||
|
name: "Coordinated Attack"
|
||||||
|
description: "Attack in perfect coordination with your companion for bonus damage"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 30
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 18
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/corpse_explosion.yaml
Normal file
16
api/app/data/abilities/corpse_explosion.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Corpse Explosion - Necromancer Raise Dead ability
|
||||||
|
# Detonate corpse/minion AoE
|
||||||
|
|
||||||
|
ability_id: "corpse_explosion"
|
||||||
|
name: "Corpse Explosion"
|
||||||
|
description: "Detonate a corpse or minion, dealing AoE shadow damage to all nearby enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 45
|
||||||
|
damage_type: "shadow"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 28
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/coup_de_grace.yaml
Normal file
16
api/app/data/abilities/coup_de_grace.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Coup de Grace - Assassin Blade Specialist ability
|
||||||
|
# Execute low HP targets
|
||||||
|
|
||||||
|
ability_id: "coup_de_grace"
|
||||||
|
name: "Coup de Grace"
|
||||||
|
description: "Deliver the killing blow. Instantly kills targets below 25% HP, otherwise deals massive damage"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 70
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 40
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/curse_of_agony.yaml
Normal file
25
api/app/data/abilities/curse_of_agony.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Curse of Agony - Necromancer Dark Affliction ability
|
||||||
|
# Heavy shadow DoT
|
||||||
|
|
||||||
|
ability_id: "curse_of_agony"
|
||||||
|
name: "Curse of Agony"
|
||||||
|
description: "Curse your target with unbearable agony, dealing increasing shadow damage over 5 turns"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 10
|
||||||
|
damage_type: "shadow"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 28
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "agony"
|
||||||
|
name: "Curse of Agony"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 5
|
||||||
|
power: 12
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "curse_of_agony"
|
||||||
25
api/app/data/abilities/death_mark.yaml
Normal file
25
api/app/data/abilities/death_mark.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Death Mark - Assassin Shadow Dancer ability
|
||||||
|
# Mark target for bonus damage
|
||||||
|
|
||||||
|
ability_id: "death_mark"
|
||||||
|
name: "Death Mark"
|
||||||
|
description: "Mark your target for death. Your next attack deals 200% damage"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.0
|
||||||
|
mana_cost: 30
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "marked_for_death"
|
||||||
|
name: "Marked for Death"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 2
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "death_mark"
|
||||||
25
api/app/data/abilities/death_pact.yaml
Normal file
25
api/app/data/abilities/death_pact.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Death Pact - Necromancer Raise Dead ability
|
||||||
|
# Sacrifice minion for HP/mana
|
||||||
|
|
||||||
|
ability_id: "death_pact"
|
||||||
|
name: "Death Pact"
|
||||||
|
description: "Sacrifice one of your minions to restore your health and mana"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 50
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 0
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "death_pact_heal"
|
||||||
|
name: "Death Pact"
|
||||||
|
effect_type: "hot"
|
||||||
|
duration: 1
|
||||||
|
power: 40
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "death_pact"
|
||||||
25
api/app/data/abilities/divine_aegis.yaml
Normal file
25
api/app/data/abilities/divine_aegis.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Divine Aegis - Oathkeeper Aegis of Light ability
|
||||||
|
# Massive party shield
|
||||||
|
|
||||||
|
ability_id: "divine_aegis"
|
||||||
|
name: "Divine Aegis"
|
||||||
|
description: "Invoke divine protection to create a powerful shield around all allies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 60
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 45
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "divine_aegis_shield"
|
||||||
|
name: "Divine Aegis"
|
||||||
|
effect_type: "shield"
|
||||||
|
duration: 3
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "divine_aegis"
|
||||||
34
api/app/data/abilities/divine_blessing.yaml
Normal file
34
api/app/data/abilities/divine_blessing.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Divine Blessing - Oathkeeper Redemption ability
|
||||||
|
# Stat buff + HoT
|
||||||
|
|
||||||
|
ability_id: "divine_blessing"
|
||||||
|
name: "Divine Blessing"
|
||||||
|
description: "Bless an ally with divine power, increasing their stats and healing over time"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 35
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "blessed"
|
||||||
|
name: "Divine Blessing"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 4
|
||||||
|
power: 15
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "divine_blessing"
|
||||||
|
- effect_id: "blessed_healing"
|
||||||
|
name: "Blessed Healing"
|
||||||
|
effect_type: "hot"
|
||||||
|
duration: 4
|
||||||
|
power: 10
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "divine_blessing"
|
||||||
16
api/app/data/abilities/divine_intervention.yaml
Normal file
16
api/app/data/abilities/divine_intervention.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Divine Intervention - Luminary Divine Protection ability
|
||||||
|
# Full heal + cleanse
|
||||||
|
|
||||||
|
ability_id: "divine_intervention"
|
||||||
|
name: "Divine Intervention"
|
||||||
|
description: "Call upon divine power to fully heal and cleanse an ally of all negative effects"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 80
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 45
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/divine_storm.yaml
Normal file
25
api/app/data/abilities/divine_storm.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Divine Storm - Luminary Radiant Judgment ultimate
|
||||||
|
# Ultimate AoE holy + stun all
|
||||||
|
|
||||||
|
ability_id: "divine_storm"
|
||||||
|
name: "Divine Storm"
|
||||||
|
description: "Unleash the full fury of the divine, dealing massive holy damage to all enemies and stunning them"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 95
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 60
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "divine_judgment"
|
||||||
|
name: "Divine Judgment"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 1
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "divine_storm"
|
||||||
25
api/app/data/abilities/drain_life.yaml
Normal file
25
api/app/data/abilities/drain_life.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Drain Life - Necromancer Dark Affliction ability
|
||||||
|
# Shadow damage + self-heal
|
||||||
|
|
||||||
|
ability_id: "drain_life"
|
||||||
|
name: "Drain Life"
|
||||||
|
description: "Drain the life force from your enemy, dealing shadow damage and healing yourself"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 18
|
||||||
|
damage_type: "shadow"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 12
|
||||||
|
cooldown: 1
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "life_drain"
|
||||||
|
name: "Life Drained"
|
||||||
|
effect_type: "hot"
|
||||||
|
duration: 1
|
||||||
|
power: 9
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "drain_life"
|
||||||
34
api/app/data/abilities/epidemic.yaml
Normal file
34
api/app/data/abilities/epidemic.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Epidemic - Necromancer Dark Affliction ultimate
|
||||||
|
# Ultimate multi-DoT all enemies
|
||||||
|
|
||||||
|
ability_id: "epidemic"
|
||||||
|
name: "Epidemic"
|
||||||
|
description: "Unleash a devastating epidemic that afflicts all enemies with multiple diseases"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 60
|
||||||
|
damage_type: "shadow"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 60
|
||||||
|
cooldown: 6
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "epidemic_plague"
|
||||||
|
name: "Epidemic"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 5
|
||||||
|
power: 20
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "epidemic"
|
||||||
|
- effect_id: "weakened"
|
||||||
|
name: "Weakened"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 5
|
||||||
|
power: 25
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "epidemic"
|
||||||
16
api/app/data/abilities/execute.yaml
Normal file
16
api/app/data/abilities/execute.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Execute - Vanguard Weapon Master ability
|
||||||
|
# Bonus damage to low HP targets
|
||||||
|
|
||||||
|
ability_id: "execute"
|
||||||
|
name: "Execute"
|
||||||
|
description: "Finish off weakened enemies. Deals bonus damage to targets below 30% HP"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 60
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "strength"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 40
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/explosive_shot.yaml
Normal file
25
api/app/data/abilities/explosive_shot.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Explosive Shot - Wildstrider Marksmanship ability
|
||||||
|
# Impact AoE damage
|
||||||
|
|
||||||
|
ability_id: "explosive_shot"
|
||||||
|
name: "Explosive Shot"
|
||||||
|
description: "Fire an explosive arrow that detonates on impact, dealing AoE damage"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 55
|
||||||
|
damage_type: "fire"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 38
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "burning_shrapnel"
|
||||||
|
name: "Burning Shrapnel"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 2
|
||||||
|
power: 8
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "explosive_shot"
|
||||||
25
api/app/data/abilities/firestorm.yaml
Normal file
25
api/app/data/abilities/firestorm.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Firestorm - Arcanist Pyromancy ability
|
||||||
|
# Massive AoE fire damage
|
||||||
|
|
||||||
|
ability_id: "firestorm"
|
||||||
|
name: "Firestorm"
|
||||||
|
description: "Call down a storm of fire from the heavens, devastating all enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 55
|
||||||
|
damage_type: "fire"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 45
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "scorched"
|
||||||
|
name: "Scorched"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 2
|
||||||
|
power: 12
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 3
|
||||||
|
source: "firestorm"
|
||||||
16
api/app/data/abilities/flame_burst.yaml
Normal file
16
api/app/data/abilities/flame_burst.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Flame Burst - Arcanist Pyromancy ability
|
||||||
|
# AoE fire burst centered on caster
|
||||||
|
|
||||||
|
ability_id: "flame_burst"
|
||||||
|
name: "Flame Burst"
|
||||||
|
description: "Release a burst of flames around you, scorching all nearby enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 25
|
||||||
|
damage_type: "fire"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 18
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/frozen_orb.yaml
Normal file
25
api/app/data/abilities/frozen_orb.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Frozen Orb - Arcanist Cryomancy ability
|
||||||
|
# AoE freeze with damage
|
||||||
|
|
||||||
|
ability_id: "frozen_orb"
|
||||||
|
name: "Frozen Orb"
|
||||||
|
description: "Launch a swirling orb of frost that freezes enemies in its path"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 28
|
||||||
|
damage_type: "ice"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 20
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "frozen"
|
||||||
|
name: "Frozen"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 1
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "frozen_orb"
|
||||||
25
api/app/data/abilities/glacial_spike.yaml
Normal file
25
api/app/data/abilities/glacial_spike.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Glacial Spike - Arcanist Cryomancy ability
|
||||||
|
# Heavy single target with freeze
|
||||||
|
|
||||||
|
ability_id: "glacial_spike"
|
||||||
|
name: "Glacial Spike"
|
||||||
|
description: "Impale your target with a massive spike of ice, dealing heavy damage and freezing them"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 60
|
||||||
|
damage_type: "ice"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 40
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "deep_freeze"
|
||||||
|
name: "Deep Freeze"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 2
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "glacial_spike"
|
||||||
25
api/app/data/abilities/guardian_angel.yaml
Normal file
25
api/app/data/abilities/guardian_angel.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Guardian Angel - Luminary Divine Protection ability
|
||||||
|
# Death prevention buff
|
||||||
|
|
||||||
|
ability_id: "guardian_angel"
|
||||||
|
name: "Guardian Angel"
|
||||||
|
description: "Bless an ally with divine protection that prevents death once"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.4
|
||||||
|
mana_cost: 35
|
||||||
|
cooldown: 6
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "guardian_angel_buff"
|
||||||
|
name: "Guardian Angel"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 5
|
||||||
|
power: 1
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "guardian_angel"
|
||||||
25
api/app/data/abilities/hammer_of_justice.yaml
Normal file
25
api/app/data/abilities/hammer_of_justice.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Hammer of Justice - Luminary Radiant Judgment ability
|
||||||
|
# Holy damage + stun
|
||||||
|
|
||||||
|
ability_id: "hammer_of_justice"
|
||||||
|
name: "Hammer of Justice"
|
||||||
|
description: "Smash your enemy with a divine hammer, dealing holy damage and stunning them"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 55
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 38
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "justice_stun"
|
||||||
|
name: "Judged"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 2
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "hammer_of_justice"
|
||||||
25
api/app/data/abilities/haste.yaml
Normal file
25
api/app/data/abilities/haste.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Haste - Lorekeeper Arcane Weaving ability
|
||||||
|
# Grant extra action
|
||||||
|
|
||||||
|
ability_id: "haste"
|
||||||
|
name: "Haste"
|
||||||
|
description: "Speed up time around an ally, granting them an extra action"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.4
|
||||||
|
mana_cost: 20
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "hasted"
|
||||||
|
name: "Hasted"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 2
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "haste"
|
||||||
@@ -7,7 +7,7 @@ description: "Channel divine energy to restore an ally's health"
|
|||||||
ability_type: "spell"
|
ability_type: "spell"
|
||||||
base_power: 25
|
base_power: 25
|
||||||
damage_type: "holy"
|
damage_type: "holy"
|
||||||
scaling_stat: "intelligence"
|
scaling_stat: "wisdom"
|
||||||
scaling_factor: 0.5
|
scaling_factor: 0.5
|
||||||
mana_cost: 10
|
mana_cost: 10
|
||||||
cooldown: 0
|
cooldown: 0
|
||||||
|
|||||||
25
api/app/data/abilities/holy_fire.yaml
Normal file
25
api/app/data/abilities/holy_fire.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Holy Fire - Luminary Radiant Judgment ability
|
||||||
|
# Holy DoT with reduced healing
|
||||||
|
|
||||||
|
ability_id: "holy_fire"
|
||||||
|
name: "Holy Fire"
|
||||||
|
description: "Engulf your enemy in holy flames that burn over time and reduce their healing"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 25
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 18
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "holy_burning"
|
||||||
|
name: "Holy Fire"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 3
|
||||||
|
power: 8
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "holy_fire"
|
||||||
25
api/app/data/abilities/holy_shield.yaml
Normal file
25
api/app/data/abilities/holy_shield.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Holy Shield - Luminary Divine Protection ability
|
||||||
|
# Grant damage absorb shield
|
||||||
|
|
||||||
|
ability_id: "holy_shield"
|
||||||
|
name: "Holy Shield"
|
||||||
|
description: "Grant an ally a protective barrier of holy light that absorbs damage"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 30
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 15
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "holy_shield_barrier"
|
||||||
|
name: "Holy Shield"
|
||||||
|
effect_type: "shield"
|
||||||
|
duration: 3
|
||||||
|
power: 30
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "holy_shield"
|
||||||
25
api/app/data/abilities/ice_shard.yaml
Normal file
25
api/app/data/abilities/ice_shard.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Ice Shard - Arcanist Cryomancy ability
|
||||||
|
# Single target ice damage with slow
|
||||||
|
|
||||||
|
ability_id: "ice_shard"
|
||||||
|
name: "Ice Shard"
|
||||||
|
description: "Hurl a shard of ice at your enemy, dealing frost damage and slowing them"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 20
|
||||||
|
damage_type: "ice"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 10
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "chilled"
|
||||||
|
name: "Chilled"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 2
|
||||||
|
power: 20
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 3
|
||||||
|
source: "ice_shard"
|
||||||
25
api/app/data/abilities/inferno.yaml
Normal file
25
api/app/data/abilities/inferno.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Inferno - Arcanist Pyromancy ability
|
||||||
|
# AoE fire DoT
|
||||||
|
|
||||||
|
ability_id: "inferno"
|
||||||
|
name: "Inferno"
|
||||||
|
description: "Summon a raging inferno that burns all enemies for 3 turns"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 35
|
||||||
|
damage_type: "fire"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 30
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "inferno_burn"
|
||||||
|
name: "Inferno Flames"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 3
|
||||||
|
power: 10
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 3
|
||||||
|
source: "inferno"
|
||||||
34
api/app/data/abilities/last_stand.yaml
Normal file
34
api/app/data/abilities/last_stand.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Last Stand - Oathkeeper Aegis of Light ultimate
|
||||||
|
# Invulnerable + taunt all
|
||||||
|
|
||||||
|
ability_id: "last_stand"
|
||||||
|
name: "Last Stand"
|
||||||
|
description: "Make your final stand, becoming invulnerable and forcing all enemies to attack you"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "constitution"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 55
|
||||||
|
cooldown: 8
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "invulnerable"
|
||||||
|
name: "Invulnerable"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 3
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "last_stand"
|
||||||
|
- effect_id: "ultimate_taunt"
|
||||||
|
name: "Challenged"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 3
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "last_stand"
|
||||||
25
api/app/data/abilities/lay_on_hands.yaml
Normal file
25
api/app/data/abilities/lay_on_hands.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Lay on Hands - Oathkeeper Redemption ability
|
||||||
|
# Touch heal
|
||||||
|
|
||||||
|
ability_id: "lay_on_hands"
|
||||||
|
name: "Lay on Hands"
|
||||||
|
description: "Place your hands upon an ally to heal their wounds"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 25
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 12
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "gentle_healing"
|
||||||
|
name: "Soothed"
|
||||||
|
effect_type: "hot"
|
||||||
|
duration: 2
|
||||||
|
power: 5
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "lay_on_hands"
|
||||||
25
api/app/data/abilities/mass_confusion.yaml
Normal file
25
api/app/data/abilities/mass_confusion.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Mass Confusion - Lorekeeper Illusionist ability
|
||||||
|
# AoE confusion
|
||||||
|
|
||||||
|
ability_id: "mass_confusion"
|
||||||
|
name: "Mass Confusion"
|
||||||
|
description: "Unleash a wave of illusions that confuses all enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 35
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "mass_confused"
|
||||||
|
name: "Bewildered"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 3
|
||||||
|
power: 40
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "mass_confusion"
|
||||||
25
api/app/data/abilities/mass_domination.yaml
Normal file
25
api/app/data/abilities/mass_domination.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Mass Domination - Lorekeeper Illusionist ultimate
|
||||||
|
# Mind control all enemies
|
||||||
|
|
||||||
|
ability_id: "mass_domination"
|
||||||
|
name: "Mass Domination"
|
||||||
|
description: "Dominate the minds of all enemies, forcing them to attack each other"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 75
|
||||||
|
cooldown: 8
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "dominated"
|
||||||
|
name: "Dominated"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 3
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "mass_domination"
|
||||||
25
api/app/data/abilities/mass_enhancement.yaml
Normal file
25
api/app/data/abilities/mass_enhancement.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Mass Enhancement - Lorekeeper Arcane Weaving ability
|
||||||
|
# AoE stat buff
|
||||||
|
|
||||||
|
ability_id: "mass_enhancement"
|
||||||
|
name: "Mass Enhancement"
|
||||||
|
description: "Enhance all allies with arcane power, increasing all their stats"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 32
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "enhanced"
|
||||||
|
name: "Enhanced"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 4
|
||||||
|
power: 15
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "mass_enhancement"
|
||||||
25
api/app/data/abilities/mass_heal.yaml
Normal file
25
api/app/data/abilities/mass_heal.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Mass Heal - Luminary Divine Protection ability
|
||||||
|
# AoE healing
|
||||||
|
|
||||||
|
ability_id: "mass_heal"
|
||||||
|
name: "Mass Heal"
|
||||||
|
description: "Channel divine energy to heal all allies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 35
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 30
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "mass_regen"
|
||||||
|
name: "Divine Healing"
|
||||||
|
effect_type: "hot"
|
||||||
|
duration: 2
|
||||||
|
power: 8
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "mass_heal"
|
||||||
25
api/app/data/abilities/mesmerize.yaml
Normal file
25
api/app/data/abilities/mesmerize.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Mesmerize - Lorekeeper Illusionist ability
|
||||||
|
# Stun for 2 turns
|
||||||
|
|
||||||
|
ability_id: "mesmerize"
|
||||||
|
name: "Mesmerize"
|
||||||
|
description: "Mesmerize your target with illusions, stunning them for 2 turns"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 22
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "mesmerized"
|
||||||
|
name: "Mesmerized"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 2
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "mesmerize"
|
||||||
25
api/app/data/abilities/miracle.yaml
Normal file
25
api/app/data/abilities/miracle.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Miracle - Oathkeeper Redemption ultimate
|
||||||
|
# Full party heal + cleanse all
|
||||||
|
|
||||||
|
ability_id: "miracle"
|
||||||
|
name: "Miracle"
|
||||||
|
description: "Perform a divine miracle that fully heals all allies and cleanses all negative effects"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 100
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 70
|
||||||
|
cooldown: 8
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "miraculous_healing"
|
||||||
|
name: "Miraculous"
|
||||||
|
effect_type: "hot"
|
||||||
|
duration: 3
|
||||||
|
power: 20
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "miracle"
|
||||||
25
api/app/data/abilities/mirror_image.yaml
Normal file
25
api/app/data/abilities/mirror_image.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Mirror Image - Lorekeeper Illusionist ability
|
||||||
|
# Summon decoys
|
||||||
|
|
||||||
|
ability_id: "mirror_image"
|
||||||
|
name: "Mirror Image"
|
||||||
|
description: "Create illusory copies of yourself that absorb enemy attacks"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 28
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "mirror_images"
|
||||||
|
name: "Mirror Images"
|
||||||
|
effect_type: "shield"
|
||||||
|
duration: 4
|
||||||
|
power: 40
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 3
|
||||||
|
max_stacks: 3
|
||||||
|
source: "mirror_image"
|
||||||
16
api/app/data/abilities/multishot.yaml
Normal file
16
api/app/data/abilities/multishot.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Multishot - Wildstrider Marksmanship ability
|
||||||
|
# Hit multiple targets
|
||||||
|
|
||||||
|
ability_id: "multishot"
|
||||||
|
name: "Multishot"
|
||||||
|
description: "Fire multiple arrows in quick succession, hitting up to 3 targets"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 22
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 18
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 3
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/phantasmal_killer.yaml
Normal file
25
api/app/data/abilities/phantasmal_killer.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Phantasmal Killer - Lorekeeper Illusionist ability
|
||||||
|
# Psychic damage + fear
|
||||||
|
|
||||||
|
ability_id: "phantasmal_killer"
|
||||||
|
name: "Phantasmal Killer"
|
||||||
|
description: "Conjure a nightmarish illusion that terrifies and damages your target"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 55
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 42
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "terrified"
|
||||||
|
name: "Terrified"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 3
|
||||||
|
power: 30
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "phantasmal_killer"
|
||||||
25
api/app/data/abilities/piercing_shot.yaml
Normal file
25
api/app/data/abilities/piercing_shot.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Piercing Shot - Wildstrider Marksmanship ability
|
||||||
|
# Line AoE that pierces through enemies
|
||||||
|
|
||||||
|
ability_id: "piercing_shot"
|
||||||
|
name: "Piercing Shot"
|
||||||
|
description: "Fire a powerful arrow that pierces through all enemies in a line"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 40
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 28
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "armor_pierced"
|
||||||
|
name: "Armor Pierced"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 2
|
||||||
|
power: 15
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "piercing_shot"
|
||||||
25
api/app/data/abilities/plague.yaml
Normal file
25
api/app/data/abilities/plague.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Plague - Necromancer Dark Affliction ability
|
||||||
|
# Spreading poison DoT
|
||||||
|
|
||||||
|
ability_id: "plague"
|
||||||
|
name: "Plague"
|
||||||
|
description: "Infect your target with a virulent plague that spreads to nearby enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 15
|
||||||
|
damage_type: "poison"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 20
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "plagued"
|
||||||
|
name: "Plagued"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 4
|
||||||
|
power: 8
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 3
|
||||||
|
source: "plague"
|
||||||
16
api/app/data/abilities/power_strike.yaml
Normal file
16
api/app/data/abilities/power_strike.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Power Strike - Vanguard Weapon Master ability
|
||||||
|
# Heavy attack dealing 150% weapon damage
|
||||||
|
|
||||||
|
ability_id: "power_strike"
|
||||||
|
name: "Power Strike"
|
||||||
|
description: "A heavy attack that deals 150% weapon damage"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 15
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "strength"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 8
|
||||||
|
cooldown: 1
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/precise_strike.yaml
Normal file
16
api/app/data/abilities/precise_strike.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Precise Strike - Assassin Blade Specialist ability
|
||||||
|
# High crit chance attack
|
||||||
|
|
||||||
|
ability_id: "precise_strike"
|
||||||
|
name: "Precise Strike"
|
||||||
|
description: "A calculated strike aimed at vital points with increased critical chance"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 15
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 8
|
||||||
|
cooldown: 1
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/primal_fury.yaml
Normal file
16
api/app/data/abilities/primal_fury.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Primal Fury - Wildstrider Beast Companion ability
|
||||||
|
# Pet AoE attack
|
||||||
|
|
||||||
|
ability_id: "primal_fury"
|
||||||
|
name: "Primal Fury"
|
||||||
|
description: "Command your companion to unleash a devastating attack on all enemies"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 50
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 35
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/rain_of_arrows.yaml
Normal file
25
api/app/data/abilities/rain_of_arrows.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Rain of Arrows - Wildstrider Marksmanship ultimate
|
||||||
|
# Ultimate AoE attack
|
||||||
|
|
||||||
|
ability_id: "rain_of_arrows"
|
||||||
|
name: "Rain of Arrows"
|
||||||
|
description: "Call down a devastating rain of arrows upon all enemies"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 85
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 55
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "pinned"
|
||||||
|
name: "Pinned"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 1
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "rain_of_arrows"
|
||||||
25
api/app/data/abilities/raise_ghoul.yaml
Normal file
25
api/app/data/abilities/raise_ghoul.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Raise Ghoul - Necromancer Raise Dead ability
|
||||||
|
# Summon stronger ghoul
|
||||||
|
|
||||||
|
ability_id: "raise_ghoul"
|
||||||
|
name: "Raise Ghoul"
|
||||||
|
description: "Raise a powerful ghoul from the dead to serve you"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 22
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "ghoul_minion"
|
||||||
|
name: "Ghoul"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 99
|
||||||
|
power: 35
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "raise_ghoul"
|
||||||
34
api/app/data/abilities/reality_shift.yaml
Normal file
34
api/app/data/abilities/reality_shift.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Reality Shift - Lorekeeper Arcane Weaving ultimate
|
||||||
|
# Massive buff allies + debuff enemies
|
||||||
|
|
||||||
|
ability_id: "reality_shift"
|
||||||
|
name: "Reality Shift"
|
||||||
|
description: "Alter reality itself, greatly empowering allies while weakening all enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: "arcane"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 70
|
||||||
|
cooldown: 8
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "reality_empowered"
|
||||||
|
name: "Reality Empowered"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 5
|
||||||
|
power: 30
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "reality_shift"
|
||||||
|
- effect_id: "reality_weakened"
|
||||||
|
name: "Reality Distorted"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 5
|
||||||
|
power: 30
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "reality_shift"
|
||||||
25
api/app/data/abilities/rending_blow.yaml
Normal file
25
api/app/data/abilities/rending_blow.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Rending Blow - Vanguard Weapon Master ability
|
||||||
|
# Attack with bleed DoT
|
||||||
|
|
||||||
|
ability_id: "rending_blow"
|
||||||
|
name: "Rending Blow"
|
||||||
|
description: "Strike with such force that your enemy bleeds for 3 turns"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 35
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "strength"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 25
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "bleed"
|
||||||
|
name: "Bleeding"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 3
|
||||||
|
power: 8
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 3
|
||||||
|
source: "rending_blow"
|
||||||
16
api/app/data/abilities/resurrection.yaml
Normal file
16
api/app/data/abilities/resurrection.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Resurrection - Luminary Divine Protection ultimate
|
||||||
|
# Revive fallen ally
|
||||||
|
|
||||||
|
ability_id: "resurrection"
|
||||||
|
name: "Resurrection"
|
||||||
|
description: "Call upon the divine to bring a fallen ally back to life with 50% HP and mana"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 50
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 60
|
||||||
|
cooldown: 8
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/riposte.yaml
Normal file
16
api/app/data/abilities/riposte.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Riposte - Vanguard Shield Bearer ability
|
||||||
|
# Counter attack after blocking
|
||||||
|
|
||||||
|
ability_id: "riposte"
|
||||||
|
name: "Riposte"
|
||||||
|
description: "After blocking an attack, counter with a swift strike"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 30
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "strength"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 20
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/shadow_assault.yaml
Normal file
25
api/app/data/abilities/shadow_assault.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Shadow Assault - Assassin Shadow Dancer ultimate
|
||||||
|
# AoE guaranteed crits
|
||||||
|
|
||||||
|
ability_id: "shadow_assault"
|
||||||
|
name: "Shadow Assault"
|
||||||
|
description: "Become one with the shadows and strike all enemies with guaranteed critical hits"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 80
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 55
|
||||||
|
cooldown: 6
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "shadow_crit"
|
||||||
|
name: "Shadow Strike"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 1
|
||||||
|
power: 100
|
||||||
|
stat_affected: "crit_chance"
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "shadow_assault"
|
||||||
16
api/app/data/abilities/shadowstep.yaml
Normal file
16
api/app/data/abilities/shadowstep.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Shadowstep - Assassin Shadow Dancer ability
|
||||||
|
# Teleport and backstab
|
||||||
|
|
||||||
|
ability_id: "shadowstep"
|
||||||
|
name: "Shadowstep"
|
||||||
|
description: "Vanish into the shadows and reappear behind your target, striking from behind"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 18
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 10
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/shield_of_faith.yaml
Normal file
25
api/app/data/abilities/shield_of_faith.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Shield of Faith - Oathkeeper Aegis of Light ability
|
||||||
|
# Shield for self and allies
|
||||||
|
|
||||||
|
ability_id: "shield_of_faith"
|
||||||
|
name: "Shield of Faith"
|
||||||
|
description: "Create a shield of divine faith that protects you and nearby allies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 35
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 20
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "faith_shield"
|
||||||
|
name: "Shield of Faith"
|
||||||
|
effect_type: "shield"
|
||||||
|
duration: 3
|
||||||
|
power: 25
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "shield_of_faith"
|
||||||
25
api/app/data/abilities/shield_wall.yaml
Normal file
25
api/app/data/abilities/shield_wall.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Shield Wall - Vanguard Shield Bearer ability
|
||||||
|
# Defensive buff reducing damage
|
||||||
|
|
||||||
|
ability_id: "shield_wall"
|
||||||
|
name: "Shield Wall"
|
||||||
|
description: "Raise your shield to block incoming attacks, reducing damage by 50% for 3 turns"
|
||||||
|
ability_type: "defend"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "constitution"
|
||||||
|
scaling_factor: 0.3
|
||||||
|
mana_cost: 12
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "shield_wall_buff"
|
||||||
|
name: "Shield Wall"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 3
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "shield_wall"
|
||||||
16
api/app/data/abilities/smite.yaml
Normal file
16
api/app/data/abilities/smite.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Smite - Luminary Radiant Judgment ability
|
||||||
|
# Holy damage attack
|
||||||
|
|
||||||
|
ability_id: "smite"
|
||||||
|
name: "Smite"
|
||||||
|
description: "Call down holy light to smite your enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 20
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 10
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
25
api/app/data/abilities/smoke_bomb.yaml
Normal file
25
api/app/data/abilities/smoke_bomb.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Smoke Bomb - Assassin Shadow Dancer ability
|
||||||
|
# Evasion buff
|
||||||
|
|
||||||
|
ability_id: "smoke_bomb"
|
||||||
|
name: "Smoke Bomb"
|
||||||
|
description: "Throw a smoke bomb, making yourself untargetable for 1 turn"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.3
|
||||||
|
mana_cost: 15
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "smoke_screen"
|
||||||
|
name: "Smoke Screen"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 1
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "smoke_bomb"
|
||||||
34
api/app/data/abilities/soul_rot.yaml
Normal file
34
api/app/data/abilities/soul_rot.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
# Soul Rot - Necromancer Dark Affliction ability
|
||||||
|
# DoT + reduced healing on target
|
||||||
|
|
||||||
|
ability_id: "soul_rot"
|
||||||
|
name: "Soul Rot"
|
||||||
|
description: "Rot your target's soul, dealing shadow damage over time and reducing their healing received"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 45
|
||||||
|
damage_type: "shadow"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 38
|
||||||
|
cooldown: 4
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "rotting_soul"
|
||||||
|
name: "Soul Rot"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 4
|
||||||
|
power: 15
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "soul_rot"
|
||||||
|
- effect_id: "healing_reduction"
|
||||||
|
name: "Corrupted"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 4
|
||||||
|
power: 50
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "soul_rot"
|
||||||
25
api/app/data/abilities/stampede.yaml
Normal file
25
api/app/data/abilities/stampede.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Stampede - Wildstrider Beast Companion ultimate
|
||||||
|
# Summon beast horde AoE
|
||||||
|
|
||||||
|
ability_id: "stampede"
|
||||||
|
name: "Stampede"
|
||||||
|
description: "Call upon the spirits of the wild to summon a stampede of beasts that tramples all enemies"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 90
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 60
|
||||||
|
cooldown: 6
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "trampled"
|
||||||
|
name: "Trampled"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 2
|
||||||
|
power: 30
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "stampede"
|
||||||
25
api/app/data/abilities/summon_abomination.yaml
Normal file
25
api/app/data/abilities/summon_abomination.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Summon Abomination - Necromancer Raise Dead ability
|
||||||
|
# Summon powerful abomination
|
||||||
|
|
||||||
|
ability_id: "summon_abomination"
|
||||||
|
name: "Summon Abomination"
|
||||||
|
description: "Stitch together corpses to create a powerful abomination"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.6
|
||||||
|
mana_cost: 45
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "abomination_minion"
|
||||||
|
name: "Abomination"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 99
|
||||||
|
power: 60
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "summon_abomination"
|
||||||
25
api/app/data/abilities/summon_companion.yaml
Normal file
25
api/app/data/abilities/summon_companion.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Summon Companion - Wildstrider Beast Companion ability
|
||||||
|
# Summon animal pet
|
||||||
|
|
||||||
|
ability_id: "summon_companion"
|
||||||
|
name: "Summon Companion"
|
||||||
|
description: "Call your loyal animal companion to fight by your side"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 15
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "companion_active"
|
||||||
|
name: "Animal Companion"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 99
|
||||||
|
power: 20
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "summon_companion"
|
||||||
25
api/app/data/abilities/summon_skeleton.yaml
Normal file
25
api/app/data/abilities/summon_skeleton.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Summon Skeleton - Necromancer Raise Dead ability
|
||||||
|
# Summon skeleton warrior
|
||||||
|
|
||||||
|
ability_id: "summon_skeleton"
|
||||||
|
name: "Summon Skeleton"
|
||||||
|
description: "Raise a skeleton warrior from the dead to fight for you"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "charisma"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 15
|
||||||
|
cooldown: 0
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "skeleton_minion"
|
||||||
|
name: "Skeleton Warrior"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 99
|
||||||
|
power: 20
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "summon_skeleton"
|
||||||
25
api/app/data/abilities/sun_burst.yaml
Normal file
25
api/app/data/abilities/sun_burst.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Sun Burst - Arcanist Pyromancy ultimate
|
||||||
|
# Ultimate fire nuke
|
||||||
|
|
||||||
|
ability_id: "sun_burst"
|
||||||
|
name: "Sun Burst"
|
||||||
|
description: "Channel the power of the sun to unleash a devastating explosion of fire on all enemies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 100
|
||||||
|
damage_type: "fire"
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 65
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "incinerated"
|
||||||
|
name: "Incinerated"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 3
|
||||||
|
power: 15
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "sun_burst"
|
||||||
25
api/app/data/abilities/taunt.yaml
Normal file
25
api/app/data/abilities/taunt.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Taunt - Oathkeeper Aegis of Light ability
|
||||||
|
# Force enemies to attack you
|
||||||
|
|
||||||
|
ability_id: "taunt"
|
||||||
|
name: "Taunt"
|
||||||
|
description: "Force all enemies to focus their attacks on you"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "constitution"
|
||||||
|
scaling_factor: 0.3
|
||||||
|
mana_cost: 8
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "taunted"
|
||||||
|
name: "Taunted"
|
||||||
|
effect_type: "debuff"
|
||||||
|
duration: 2
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "taunt"
|
||||||
25
api/app/data/abilities/thousand_cuts.yaml
Normal file
25
api/app/data/abilities/thousand_cuts.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Thousand Cuts - Assassin Blade Specialist ultimate
|
||||||
|
# Multi-hit flurry
|
||||||
|
|
||||||
|
ability_id: "thousand_cuts"
|
||||||
|
name: "Thousand Cuts"
|
||||||
|
description: "Unleash a flurry of strikes, each with 50% crit chance"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 100
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 60
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "bleeding_wounds"
|
||||||
|
name: "Bleeding Wounds"
|
||||||
|
effect_type: "dot"
|
||||||
|
duration: 3
|
||||||
|
power: 15
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 5
|
||||||
|
source: "thousand_cuts"
|
||||||
25
api/app/data/abilities/time_warp.yaml
Normal file
25
api/app/data/abilities/time_warp.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Time Warp - Lorekeeper Arcane Weaving ability
|
||||||
|
# AoE extra actions
|
||||||
|
|
||||||
|
ability_id: "time_warp"
|
||||||
|
name: "Time Warp"
|
||||||
|
description: "Bend time itself, granting all allies increased speed"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "intelligence"
|
||||||
|
scaling_factor: 0.5
|
||||||
|
mana_cost: 45
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "time_warped"
|
||||||
|
name: "Time Warped"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 3
|
||||||
|
power: 75
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "time_warp"
|
||||||
25
api/app/data/abilities/titans_wrath.yaml
Normal file
25
api/app/data/abilities/titans_wrath.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Titan's Wrath - Vanguard Weapon Master ultimate
|
||||||
|
# Devastating AoE attack with stun
|
||||||
|
|
||||||
|
ability_id: "titans_wrath"
|
||||||
|
name: "Titan's Wrath"
|
||||||
|
description: "Unleash a devastating attack that deals 300% weapon damage and stuns all enemies"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 100
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "strength"
|
||||||
|
scaling_factor: 0.7
|
||||||
|
mana_cost: 60
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "titans_stun"
|
||||||
|
name: "Staggered"
|
||||||
|
effect_type: "stun"
|
||||||
|
duration: 1
|
||||||
|
power: 0
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "titans_wrath"
|
||||||
25
api/app/data/abilities/unbreakable.yaml
Normal file
25
api/app/data/abilities/unbreakable.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Unbreakable - Vanguard Shield Bearer ultimate
|
||||||
|
# Massive damage reduction
|
||||||
|
|
||||||
|
ability_id: "unbreakable"
|
||||||
|
name: "Unbreakable"
|
||||||
|
description: "Channel your inner strength to become nearly invulnerable, reducing all damage by 75% for 5 turns"
|
||||||
|
ability_type: "defend"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "constitution"
|
||||||
|
scaling_factor: 0.3
|
||||||
|
mana_cost: 50
|
||||||
|
cooldown: 6
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "unbreakable_buff"
|
||||||
|
name: "Unbreakable"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 5
|
||||||
|
power: 75
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "unbreakable"
|
||||||
25
api/app/data/abilities/vanish.yaml
Normal file
25
api/app/data/abilities/vanish.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
# Vanish - Assassin Shadow Dancer ability
|
||||||
|
# Stealth for 2 turns
|
||||||
|
|
||||||
|
ability_id: "vanish"
|
||||||
|
name: "Vanish"
|
||||||
|
description: "Disappear into the shadows, becoming invisible for 2 turns and dropping threat"
|
||||||
|
ability_type: "skill"
|
||||||
|
base_power: 0
|
||||||
|
damage_type: null
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.3
|
||||||
|
mana_cost: 25
|
||||||
|
cooldown: 5
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied:
|
||||||
|
- effect_id: "stealth"
|
||||||
|
name: "Stealthed"
|
||||||
|
effect_type: "buff"
|
||||||
|
duration: 2
|
||||||
|
power: 100
|
||||||
|
stat_affected: null
|
||||||
|
stacks: 1
|
||||||
|
max_stacks: 1
|
||||||
|
source: "vanish"
|
||||||
16
api/app/data/abilities/vital_strike.yaml
Normal file
16
api/app/data/abilities/vital_strike.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Vital Strike - Assassin Blade Specialist ability
|
||||||
|
# Massive crit damage
|
||||||
|
|
||||||
|
ability_id: "vital_strike"
|
||||||
|
name: "Vital Strike"
|
||||||
|
description: "Strike a vital organ for massive critical damage"
|
||||||
|
ability_type: "attack"
|
||||||
|
base_power: 30
|
||||||
|
damage_type: "physical"
|
||||||
|
scaling_stat: "dexterity"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 18
|
||||||
|
cooldown: 2
|
||||||
|
is_aoe: false
|
||||||
|
target_count: 1
|
||||||
|
effects_applied: []
|
||||||
16
api/app/data/abilities/word_of_healing.yaml
Normal file
16
api/app/data/abilities/word_of_healing.yaml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Word of Healing - Oathkeeper Redemption ability
|
||||||
|
# AoE heal
|
||||||
|
|
||||||
|
ability_id: "word_of_healing"
|
||||||
|
name: "Word of Healing"
|
||||||
|
description: "Speak a word of power that heals all nearby allies"
|
||||||
|
ability_type: "spell"
|
||||||
|
base_power: 40
|
||||||
|
damage_type: "holy"
|
||||||
|
scaling_stat: "wisdom"
|
||||||
|
scaling_factor: 0.55
|
||||||
|
mana_cost: 30
|
||||||
|
cooldown: 3
|
||||||
|
is_aoe: true
|
||||||
|
target_count: 0
|
||||||
|
effects_applied: []
|
||||||
177
api/app/data/affixes/prefixes.yaml
Normal file
177
api/app/data/affixes/prefixes.yaml
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
# Item Prefix Affixes
|
||||||
|
# Prefixes appear before the item name: "Flaming Dagger"
|
||||||
|
#
|
||||||
|
# Affix Structure:
|
||||||
|
# affix_id: Unique identifier
|
||||||
|
# name: Display name (what appears in the item name)
|
||||||
|
# affix_type: "prefix"
|
||||||
|
# tier: "minor" (RARE), "major" (EPIC), "legendary" (LEGENDARY only)
|
||||||
|
# description: Flavor text describing the effect
|
||||||
|
# stat_bonuses: Dict of stat_name -> bonus value
|
||||||
|
# defense_bonus: Direct defense bonus
|
||||||
|
# resistance_bonus: Direct resistance bonus
|
||||||
|
# damage_bonus: Flat damage bonus (weapons)
|
||||||
|
# damage_type: Elemental damage type
|
||||||
|
# elemental_ratio: Portion converted to elemental (0.0-1.0)
|
||||||
|
# crit_chance_bonus: Added to crit chance
|
||||||
|
# crit_multiplier_bonus: Added to crit multiplier
|
||||||
|
# allowed_item_types: [] = all types, or ["weapon", "armor"]
|
||||||
|
# required_rarity: null = any, or "legendary"
|
||||||
|
|
||||||
|
prefixes:
|
||||||
|
# ==================== ELEMENTAL PREFIXES (FIRE) ====================
|
||||||
|
flaming:
|
||||||
|
affix_id: "flaming"
|
||||||
|
name: "Flaming"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Imbued with fire magic, dealing bonus fire damage"
|
||||||
|
damage_type: "fire"
|
||||||
|
elemental_ratio: 0.25
|
||||||
|
damage_bonus: 3
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
blazing:
|
||||||
|
affix_id: "blazing"
|
||||||
|
name: "Blazing"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Wreathed in intense flames"
|
||||||
|
damage_type: "fire"
|
||||||
|
elemental_ratio: 0.35
|
||||||
|
damage_bonus: 6
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
# ==================== ELEMENTAL PREFIXES (ICE) ====================
|
||||||
|
frozen:
|
||||||
|
affix_id: "frozen"
|
||||||
|
name: "Frozen"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Enchanted with frost magic"
|
||||||
|
damage_type: "ice"
|
||||||
|
elemental_ratio: 0.25
|
||||||
|
damage_bonus: 3
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
glacial:
|
||||||
|
affix_id: "glacial"
|
||||||
|
name: "Glacial"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Encased in eternal ice"
|
||||||
|
damage_type: "ice"
|
||||||
|
elemental_ratio: 0.35
|
||||||
|
damage_bonus: 6
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
# ==================== ELEMENTAL PREFIXES (LIGHTNING) ====================
|
||||||
|
shocking:
|
||||||
|
affix_id: "shocking"
|
||||||
|
name: "Shocking"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Crackles with electrical energy"
|
||||||
|
damage_type: "lightning"
|
||||||
|
elemental_ratio: 0.25
|
||||||
|
damage_bonus: 3
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
thundering:
|
||||||
|
affix_id: "thundering"
|
||||||
|
name: "Thundering"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Charged with the power of storms"
|
||||||
|
damage_type: "lightning"
|
||||||
|
elemental_ratio: 0.35
|
||||||
|
damage_bonus: 6
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
# ==================== MATERIAL PREFIXES ====================
|
||||||
|
iron:
|
||||||
|
affix_id: "iron"
|
||||||
|
name: "Iron"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Reinforced with sturdy iron"
|
||||||
|
stat_bonuses:
|
||||||
|
constitution: 1
|
||||||
|
defense_bonus: 2
|
||||||
|
|
||||||
|
steel:
|
||||||
|
affix_id: "steel"
|
||||||
|
name: "Steel"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Forged from fine steel"
|
||||||
|
stat_bonuses:
|
||||||
|
constitution: 2
|
||||||
|
strength: 1
|
||||||
|
defense_bonus: 4
|
||||||
|
|
||||||
|
# ==================== QUALITY PREFIXES ====================
|
||||||
|
sharp:
|
||||||
|
affix_id: "sharp"
|
||||||
|
name: "Sharp"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Honed to a fine edge"
|
||||||
|
damage_bonus: 3
|
||||||
|
crit_chance_bonus: 0.02
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
keen:
|
||||||
|
affix_id: "keen"
|
||||||
|
name: "Keen"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Razor-sharp edge that finds weak points"
|
||||||
|
damage_bonus: 5
|
||||||
|
crit_chance_bonus: 0.04
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
|
||||||
|
# ==================== DEFENSIVE PREFIXES ====================
|
||||||
|
sturdy:
|
||||||
|
affix_id: "sturdy"
|
||||||
|
name: "Sturdy"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Built to withstand punishment"
|
||||||
|
defense_bonus: 3
|
||||||
|
allowed_item_types: ["armor"]
|
||||||
|
|
||||||
|
reinforced:
|
||||||
|
affix_id: "reinforced"
|
||||||
|
name: "Reinforced"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Heavily reinforced for maximum protection"
|
||||||
|
defense_bonus: 5
|
||||||
|
resistance_bonus: 2
|
||||||
|
allowed_item_types: ["armor"]
|
||||||
|
|
||||||
|
# ==================== LEGENDARY PREFIXES ====================
|
||||||
|
infernal:
|
||||||
|
affix_id: "infernal"
|
||||||
|
name: "Infernal"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "legendary"
|
||||||
|
description: "Burns with hellfire"
|
||||||
|
damage_type: "fire"
|
||||||
|
elemental_ratio: 0.45
|
||||||
|
damage_bonus: 12
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
required_rarity: "legendary"
|
||||||
|
|
||||||
|
vorpal:
|
||||||
|
affix_id: "vorpal"
|
||||||
|
name: "Vorpal"
|
||||||
|
affix_type: "prefix"
|
||||||
|
tier: "legendary"
|
||||||
|
description: "Cuts through anything with supernatural precision"
|
||||||
|
damage_bonus: 10
|
||||||
|
crit_chance_bonus: 0.08
|
||||||
|
crit_multiplier_bonus: 0.5
|
||||||
|
allowed_item_types: ["weapon"]
|
||||||
|
required_rarity: "legendary"
|
||||||
155
api/app/data/affixes/suffixes.yaml
Normal file
155
api/app/data/affixes/suffixes.yaml
Normal file
@@ -0,0 +1,155 @@
|
|||||||
|
# Item Suffix Affixes
|
||||||
|
# Suffixes appear after the item name: "Dagger of Strength"
|
||||||
|
#
|
||||||
|
# Suffix naming convention:
|
||||||
|
# - Minor tier: "of [Stat]" (e.g., "of Strength")
|
||||||
|
# - Major tier: "of the [Animal/Element]" (e.g., "of the Bear")
|
||||||
|
# - Legendary tier: "of the [Mythical]" (e.g., "of the Titan")
|
||||||
|
|
||||||
|
suffixes:
|
||||||
|
# ==================== STAT SUFFIXES (MINOR) ====================
|
||||||
|
of_strength:
|
||||||
|
affix_id: "of_strength"
|
||||||
|
name: "of Strength"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants physical power"
|
||||||
|
stat_bonuses:
|
||||||
|
strength: 2
|
||||||
|
|
||||||
|
of_dexterity:
|
||||||
|
affix_id: "of_dexterity"
|
||||||
|
name: "of Dexterity"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants agility and precision"
|
||||||
|
stat_bonuses:
|
||||||
|
dexterity: 2
|
||||||
|
|
||||||
|
of_constitution:
|
||||||
|
affix_id: "of_constitution"
|
||||||
|
name: "of Fortitude"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants endurance"
|
||||||
|
stat_bonuses:
|
||||||
|
constitution: 2
|
||||||
|
|
||||||
|
of_intelligence:
|
||||||
|
affix_id: "of_intelligence"
|
||||||
|
name: "of Intelligence"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants magical aptitude"
|
||||||
|
stat_bonuses:
|
||||||
|
intelligence: 2
|
||||||
|
|
||||||
|
of_wisdom:
|
||||||
|
affix_id: "of_wisdom"
|
||||||
|
name: "of Wisdom"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants insight and perception"
|
||||||
|
stat_bonuses:
|
||||||
|
wisdom: 2
|
||||||
|
|
||||||
|
of_charisma:
|
||||||
|
affix_id: "of_charisma"
|
||||||
|
name: "of Charm"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants social influence"
|
||||||
|
stat_bonuses:
|
||||||
|
charisma: 2
|
||||||
|
|
||||||
|
of_luck:
|
||||||
|
affix_id: "of_luck"
|
||||||
|
name: "of Fortune"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Grants favor from fate"
|
||||||
|
stat_bonuses:
|
||||||
|
luck: 2
|
||||||
|
|
||||||
|
# ==================== ENHANCED STAT SUFFIXES (MAJOR) ====================
|
||||||
|
of_the_bear:
|
||||||
|
affix_id: "of_the_bear"
|
||||||
|
name: "of the Bear"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Grants the might and endurance of a bear"
|
||||||
|
stat_bonuses:
|
||||||
|
strength: 4
|
||||||
|
constitution: 2
|
||||||
|
|
||||||
|
of_the_fox:
|
||||||
|
affix_id: "of_the_fox"
|
||||||
|
name: "of the Fox"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Grants the cunning and agility of a fox"
|
||||||
|
stat_bonuses:
|
||||||
|
dexterity: 4
|
||||||
|
luck: 2
|
||||||
|
|
||||||
|
of_the_owl:
|
||||||
|
affix_id: "of_the_owl"
|
||||||
|
name: "of the Owl"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Grants the wisdom and insight of an owl"
|
||||||
|
stat_bonuses:
|
||||||
|
intelligence: 3
|
||||||
|
wisdom: 3
|
||||||
|
|
||||||
|
# ==================== DEFENSIVE SUFFIXES ====================
|
||||||
|
of_protection:
|
||||||
|
affix_id: "of_protection"
|
||||||
|
name: "of Protection"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "minor"
|
||||||
|
description: "Offers physical protection"
|
||||||
|
defense_bonus: 3
|
||||||
|
|
||||||
|
of_warding:
|
||||||
|
affix_id: "of_warding"
|
||||||
|
name: "of Warding"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "major"
|
||||||
|
description: "Wards against physical and magical harm"
|
||||||
|
defense_bonus: 5
|
||||||
|
resistance_bonus: 3
|
||||||
|
|
||||||
|
# ==================== LEGENDARY SUFFIXES ====================
|
||||||
|
of_the_titan:
|
||||||
|
affix_id: "of_the_titan"
|
||||||
|
name: "of the Titan"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "legendary"
|
||||||
|
description: "Grants titanic strength and endurance"
|
||||||
|
stat_bonuses:
|
||||||
|
strength: 8
|
||||||
|
constitution: 4
|
||||||
|
required_rarity: "legendary"
|
||||||
|
|
||||||
|
of_the_wind:
|
||||||
|
affix_id: "of_the_wind"
|
||||||
|
name: "of the Wind"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "legendary"
|
||||||
|
description: "Swift as the wind itself"
|
||||||
|
stat_bonuses:
|
||||||
|
dexterity: 8
|
||||||
|
luck: 4
|
||||||
|
crit_chance_bonus: 0.05
|
||||||
|
required_rarity: "legendary"
|
||||||
|
|
||||||
|
of_invincibility:
|
||||||
|
affix_id: "of_invincibility"
|
||||||
|
name: "of Invincibility"
|
||||||
|
affix_type: "suffix"
|
||||||
|
tier: "legendary"
|
||||||
|
description: "Grants supreme protection"
|
||||||
|
defense_bonus: 10
|
||||||
|
resistance_bonus: 8
|
||||||
|
required_rarity: "legendary"
|
||||||
152
api/app/data/base_items/armor.yaml
Normal file
152
api/app/data/base_items/armor.yaml
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
# Base Armor Templates for Procedural Generation
|
||||||
|
#
|
||||||
|
# These templates define the foundation that affixes attach to.
|
||||||
|
# Example: "Leather Vest" + "Sturdy" prefix = "Sturdy Leather Vest"
|
||||||
|
#
|
||||||
|
# Armor categories:
|
||||||
|
# - Cloth: Low defense, high resistance (mages)
|
||||||
|
# - Leather: Balanced defense/resistance (rogues)
|
||||||
|
# - Chain: Medium defense, low resistance (versatile)
|
||||||
|
# - Plate: High defense, low resistance (warriors)
|
||||||
|
|
||||||
|
armor:
|
||||||
|
# ==================== CLOTH (MAGE ARMOR) ====================
|
||||||
|
cloth_robe:
|
||||||
|
template_id: "cloth_robe"
|
||||||
|
name: "Cloth Robe"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Simple cloth robes favored by spellcasters"
|
||||||
|
base_defense: 2
|
||||||
|
base_resistance: 5
|
||||||
|
base_value: 15
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.3
|
||||||
|
|
||||||
|
silk_robe:
|
||||||
|
template_id: "silk_robe"
|
||||||
|
name: "Silk Robe"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Fine silk robes that channel magical energy"
|
||||||
|
base_defense: 3
|
||||||
|
base_resistance: 8
|
||||||
|
base_value: 40
|
||||||
|
required_level: 3
|
||||||
|
drop_weight: 0.9
|
||||||
|
|
||||||
|
arcane_vestments:
|
||||||
|
template_id: "arcane_vestments"
|
||||||
|
name: "Arcane Vestments"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Robes woven with magical threads"
|
||||||
|
base_defense: 5
|
||||||
|
base_resistance: 12
|
||||||
|
base_value: 80
|
||||||
|
required_level: 5
|
||||||
|
drop_weight: 0.6
|
||||||
|
min_rarity: "uncommon"
|
||||||
|
|
||||||
|
# ==================== LEATHER (ROGUE ARMOR) ====================
|
||||||
|
leather_vest:
|
||||||
|
template_id: "leather_vest"
|
||||||
|
name: "Leather Vest"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Basic leather protection for agile fighters"
|
||||||
|
base_defense: 5
|
||||||
|
base_resistance: 2
|
||||||
|
base_value: 20
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.3
|
||||||
|
|
||||||
|
studded_leather:
|
||||||
|
template_id: "studded_leather"
|
||||||
|
name: "Studded Leather"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Leather armor reinforced with metal studs"
|
||||||
|
base_defense: 8
|
||||||
|
base_resistance: 3
|
||||||
|
base_value: 45
|
||||||
|
required_level: 3
|
||||||
|
drop_weight: 1.0
|
||||||
|
|
||||||
|
hardened_leather:
|
||||||
|
template_id: "hardened_leather"
|
||||||
|
name: "Hardened Leather"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Boiled and hardened leather for superior protection"
|
||||||
|
base_defense: 12
|
||||||
|
base_resistance: 5
|
||||||
|
base_value: 75
|
||||||
|
required_level: 5
|
||||||
|
drop_weight: 0.7
|
||||||
|
min_rarity: "uncommon"
|
||||||
|
|
||||||
|
# ==================== CHAIN (VERSATILE) ====================
|
||||||
|
chain_shirt:
|
||||||
|
template_id: "chain_shirt"
|
||||||
|
name: "Chain Shirt"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "A shirt of interlocking metal rings"
|
||||||
|
base_defense: 7
|
||||||
|
base_resistance: 2
|
||||||
|
base_value: 35
|
||||||
|
required_level: 2
|
||||||
|
drop_weight: 1.0
|
||||||
|
|
||||||
|
chainmail:
|
||||||
|
template_id: "chainmail"
|
||||||
|
name: "Chainmail"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Full chainmail armor covering torso and arms"
|
||||||
|
base_defense: 10
|
||||||
|
base_resistance: 3
|
||||||
|
base_value: 50
|
||||||
|
required_level: 3
|
||||||
|
drop_weight: 1.0
|
||||||
|
|
||||||
|
heavy_chainmail:
|
||||||
|
template_id: "heavy_chainmail"
|
||||||
|
name: "Heavy Chainmail"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Thick chainmail with reinforced rings"
|
||||||
|
base_defense: 14
|
||||||
|
base_resistance: 4
|
||||||
|
base_value: 85
|
||||||
|
required_level: 5
|
||||||
|
drop_weight: 0.7
|
||||||
|
min_rarity: "uncommon"
|
||||||
|
|
||||||
|
# ==================== PLATE (WARRIOR ARMOR) ====================
|
||||||
|
scale_mail:
|
||||||
|
template_id: "scale_mail"
|
||||||
|
name: "Scale Mail"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Overlapping metal scales on leather backing"
|
||||||
|
base_defense: 12
|
||||||
|
base_resistance: 2
|
||||||
|
base_value: 60
|
||||||
|
required_level: 4
|
||||||
|
drop_weight: 0.8
|
||||||
|
|
||||||
|
half_plate:
|
||||||
|
template_id: "half_plate"
|
||||||
|
name: "Half Plate"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Plate armor protecting vital areas"
|
||||||
|
base_defense: 16
|
||||||
|
base_resistance: 2
|
||||||
|
base_value: 120
|
||||||
|
required_level: 6
|
||||||
|
drop_weight: 0.5
|
||||||
|
min_rarity: "rare"
|
||||||
|
|
||||||
|
plate_armor:
|
||||||
|
template_id: "plate_armor"
|
||||||
|
name: "Plate Armor"
|
||||||
|
item_type: "armor"
|
||||||
|
description: "Full metal plate protection"
|
||||||
|
base_defense: 22
|
||||||
|
base_resistance: 3
|
||||||
|
base_value: 200
|
||||||
|
required_level: 7
|
||||||
|
drop_weight: 0.4
|
||||||
|
min_rarity: "rare"
|
||||||
227
api/app/data/base_items/weapons.yaml
Normal file
227
api/app/data/base_items/weapons.yaml
Normal file
@@ -0,0 +1,227 @@
|
|||||||
|
# Base Weapon Templates for Procedural Generation
|
||||||
|
#
|
||||||
|
# These templates define the foundation that affixes attach to.
|
||||||
|
# Example: "Dagger" + "Flaming" prefix = "Flaming Dagger"
|
||||||
|
#
|
||||||
|
# Template Structure:
|
||||||
|
# template_id: Unique identifier
|
||||||
|
# name: Base item name
|
||||||
|
# item_type: "weapon"
|
||||||
|
# description: Flavor text
|
||||||
|
# base_damage: Weapon damage
|
||||||
|
# base_value: Gold value
|
||||||
|
# damage_type: "physical" (default)
|
||||||
|
# crit_chance: Critical hit chance (0.0-1.0)
|
||||||
|
# crit_multiplier: Crit damage multiplier
|
||||||
|
# required_level: Min level to use/drop
|
||||||
|
# drop_weight: Higher = more common (1.0 = standard)
|
||||||
|
# min_rarity: Minimum rarity for this template
|
||||||
|
|
||||||
|
weapons:
|
||||||
|
# ==================== ONE-HANDED SWORDS ====================
|
||||||
|
dagger:
|
||||||
|
template_id: "dagger"
|
||||||
|
name: "Dagger"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A small, quick blade for close combat"
|
||||||
|
base_damage: 6
|
||||||
|
base_value: 15
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.08
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.5
|
||||||
|
|
||||||
|
short_sword:
|
||||||
|
template_id: "short_sword"
|
||||||
|
name: "Short Sword"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A versatile one-handed blade"
|
||||||
|
base_damage: 10
|
||||||
|
base_value: 30
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.06
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.3
|
||||||
|
|
||||||
|
longsword:
|
||||||
|
template_id: "longsword"
|
||||||
|
name: "Longsword"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A standard warrior's blade"
|
||||||
|
base_damage: 14
|
||||||
|
base_value: 50
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.05
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 3
|
||||||
|
drop_weight: 1.0
|
||||||
|
|
||||||
|
# ==================== TWO-HANDED WEAPONS ====================
|
||||||
|
greatsword:
|
||||||
|
template_id: "greatsword"
|
||||||
|
name: "Greatsword"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A massive two-handed blade"
|
||||||
|
base_damage: 22
|
||||||
|
base_value: 100
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.04
|
||||||
|
crit_multiplier: 2.5
|
||||||
|
required_level: 5
|
||||||
|
drop_weight: 0.7
|
||||||
|
min_rarity: "uncommon"
|
||||||
|
|
||||||
|
# ==================== AXES ====================
|
||||||
|
hatchet:
|
||||||
|
template_id: "hatchet"
|
||||||
|
name: "Hatchet"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A small throwing axe"
|
||||||
|
base_damage: 8
|
||||||
|
base_value: 20
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.06
|
||||||
|
crit_multiplier: 2.2
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.2
|
||||||
|
|
||||||
|
battle_axe:
|
||||||
|
template_id: "battle_axe"
|
||||||
|
name: "Battle Axe"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A heavy axe designed for combat"
|
||||||
|
base_damage: 16
|
||||||
|
base_value: 60
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.05
|
||||||
|
crit_multiplier: 2.3
|
||||||
|
required_level: 4
|
||||||
|
drop_weight: 0.9
|
||||||
|
|
||||||
|
# ==================== BLUNT WEAPONS ====================
|
||||||
|
club:
|
||||||
|
template_id: "club"
|
||||||
|
name: "Club"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A simple wooden club"
|
||||||
|
base_damage: 7
|
||||||
|
base_value: 10
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.04
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.5
|
||||||
|
|
||||||
|
mace:
|
||||||
|
template_id: "mace"
|
||||||
|
name: "Mace"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A flanged mace for crushing armor"
|
||||||
|
base_damage: 12
|
||||||
|
base_value: 40
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.05
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 2
|
||||||
|
drop_weight: 1.0
|
||||||
|
|
||||||
|
# ==================== STAVES ====================
|
||||||
|
quarterstaff:
|
||||||
|
template_id: "quarterstaff"
|
||||||
|
name: "Quarterstaff"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A simple wooden staff"
|
||||||
|
base_damage: 6
|
||||||
|
base_value: 10
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.05
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.2
|
||||||
|
|
||||||
|
wizard_staff:
|
||||||
|
template_id: "wizard_staff"
|
||||||
|
name: "Wizard Staff"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A staff attuned to magical energy"
|
||||||
|
base_damage: 4
|
||||||
|
base_spell_power: 12
|
||||||
|
base_value: 45
|
||||||
|
damage_type: "arcane"
|
||||||
|
crit_chance: 0.05
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 3
|
||||||
|
drop_weight: 0.8
|
||||||
|
|
||||||
|
arcane_staff:
|
||||||
|
template_id: "arcane_staff"
|
||||||
|
name: "Arcane Staff"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A powerful staff pulsing with arcane power"
|
||||||
|
base_damage: 6
|
||||||
|
base_spell_power: 18
|
||||||
|
base_value: 90
|
||||||
|
damage_type: "arcane"
|
||||||
|
crit_chance: 0.06
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 5
|
||||||
|
drop_weight: 0.6
|
||||||
|
min_rarity: "uncommon"
|
||||||
|
|
||||||
|
# ==================== WANDS ====================
|
||||||
|
wand:
|
||||||
|
template_id: "wand"
|
||||||
|
name: "Wand"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A simple magical focus"
|
||||||
|
base_damage: 2
|
||||||
|
base_spell_power: 8
|
||||||
|
base_value: 30
|
||||||
|
damage_type: "arcane"
|
||||||
|
crit_chance: 0.06
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.0
|
||||||
|
|
||||||
|
crystal_wand:
|
||||||
|
template_id: "crystal_wand"
|
||||||
|
name: "Crystal Wand"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A wand topped with a magical crystal"
|
||||||
|
base_damage: 3
|
||||||
|
base_spell_power: 14
|
||||||
|
base_value: 60
|
||||||
|
damage_type: "arcane"
|
||||||
|
crit_chance: 0.07
|
||||||
|
crit_multiplier: 2.2
|
||||||
|
required_level: 4
|
||||||
|
drop_weight: 0.8
|
||||||
|
|
||||||
|
# ==================== RANGED ====================
|
||||||
|
shortbow:
|
||||||
|
template_id: "shortbow"
|
||||||
|
name: "Shortbow"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A compact bow for quick shots"
|
||||||
|
base_damage: 8
|
||||||
|
base_value: 25
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.07
|
||||||
|
crit_multiplier: 2.0
|
||||||
|
required_level: 1
|
||||||
|
drop_weight: 1.1
|
||||||
|
|
||||||
|
longbow:
|
||||||
|
template_id: "longbow"
|
||||||
|
name: "Longbow"
|
||||||
|
item_type: "weapon"
|
||||||
|
description: "A powerful bow with excellent range"
|
||||||
|
base_damage: 14
|
||||||
|
base_value: 55
|
||||||
|
damage_type: "physical"
|
||||||
|
crit_chance: 0.08
|
||||||
|
crit_multiplier: 2.2
|
||||||
|
required_level: 4
|
||||||
|
drop_weight: 0.9
|
||||||
@@ -8,7 +8,7 @@ description: >
|
|||||||
excel in devastating spell damage, capable of incinerating groups of foes or freezing
|
excel in devastating spell damage, capable of incinerating groups of foes or freezing
|
||||||
enemies in place. Choose your element: embrace the flames or command the frost.
|
enemies in place. Choose your element: embrace the flames or command the frost.
|
||||||
|
|
||||||
# Base stats (total: 65)
|
# Base stats (total: 65 + luck)
|
||||||
base_stats:
|
base_stats:
|
||||||
strength: 8 # Low physical power
|
strength: 8 # Low physical power
|
||||||
dexterity: 10 # Average agility
|
dexterity: 10 # Average agility
|
||||||
@@ -16,11 +16,12 @@ base_stats:
|
|||||||
intelligence: 15 # Exceptional magical power
|
intelligence: 15 # Exceptional magical power
|
||||||
wisdom: 12 # Above average perception
|
wisdom: 12 # Above average perception
|
||||||
charisma: 11 # Above average social
|
charisma: 11 # Above average social
|
||||||
|
luck: 9 # Slight chaos magic boost
|
||||||
|
|
||||||
starting_equipment:
|
starting_equipment:
|
||||||
- worn_staff
|
- worn_staff
|
||||||
- cloth_armor
|
- cloth_armor
|
||||||
- rusty_knife
|
- health_potion_small
|
||||||
|
|
||||||
starting_abilities:
|
starting_abilities:
|
||||||
- basic_attack
|
- basic_attack
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ description: >
|
|||||||
capable of becoming an elusive phantom or a master of critical strikes. Choose your path: embrace
|
capable of becoming an elusive phantom or a master of critical strikes. Choose your path: embrace
|
||||||
the shadows or perfect the killing blow.
|
the shadows or perfect the killing blow.
|
||||||
|
|
||||||
# Base stats (total: 65)
|
# Base stats (total: 65 + luck)
|
||||||
base_stats:
|
base_stats:
|
||||||
strength: 11 # Above average physical power
|
strength: 11 # Above average physical power
|
||||||
dexterity: 15 # Exceptional agility
|
dexterity: 15 # Exceptional agility
|
||||||
@@ -16,11 +16,12 @@ base_stats:
|
|||||||
intelligence: 9 # Below average magic
|
intelligence: 9 # Below average magic
|
||||||
wisdom: 10 # Average perception
|
wisdom: 10 # Average perception
|
||||||
charisma: 10 # Average social
|
charisma: 10 # Average social
|
||||||
|
luck: 12 # High luck for crits and precision
|
||||||
|
|
||||||
starting_equipment:
|
starting_equipment:
|
||||||
- rusty_dagger
|
- rusty_dagger
|
||||||
- cloth_armor
|
- cloth_armor
|
||||||
- rusty_knife
|
- health_potion_small
|
||||||
|
|
||||||
starting_abilities:
|
starting_abilities:
|
||||||
- basic_attack
|
- basic_attack
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ description: >
|
|||||||
excel in supporting allies and controlling enemies through clever magic and mental manipulation.
|
excel in supporting allies and controlling enemies through clever magic and mental manipulation.
|
||||||
Choose your art: weave arcane power or bend reality itself.
|
Choose your art: weave arcane power or bend reality itself.
|
||||||
|
|
||||||
# Base stats (total: 67)
|
# Base stats (total: 67 + luck)
|
||||||
base_stats:
|
base_stats:
|
||||||
strength: 8 # Low physical power
|
strength: 8 # Low physical power
|
||||||
dexterity: 11 # Above average agility
|
dexterity: 11 # Above average agility
|
||||||
@@ -16,11 +16,12 @@ base_stats:
|
|||||||
intelligence: 13 # Above average magical power
|
intelligence: 13 # Above average magical power
|
||||||
wisdom: 11 # Above average perception
|
wisdom: 11 # Above average perception
|
||||||
charisma: 14 # High social/performance
|
charisma: 14 # High social/performance
|
||||||
|
luck: 10 # Knowledge is its own luck
|
||||||
|
|
||||||
starting_equipment:
|
starting_equipment:
|
||||||
- tome
|
- tome
|
||||||
- cloth_armor
|
- cloth_armor
|
||||||
- rusty_knife
|
- health_potion_small
|
||||||
|
|
||||||
starting_abilities:
|
starting_abilities:
|
||||||
- basic_attack
|
- basic_attack
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ description: >
|
|||||||
capable of becoming a guardian angel for their allies or a righteous crusader smiting evil.
|
capable of becoming a guardian angel for their allies or a righteous crusader smiting evil.
|
||||||
Choose your calling: protect the innocent or judge the wicked.
|
Choose your calling: protect the innocent or judge the wicked.
|
||||||
|
|
||||||
# Base stats (total: 68)
|
# Base stats (total: 68 + luck)
|
||||||
base_stats:
|
base_stats:
|
||||||
strength: 9 # Below average physical power
|
strength: 9 # Below average physical power
|
||||||
dexterity: 9 # Below average agility
|
dexterity: 9 # Below average agility
|
||||||
@@ -16,11 +16,12 @@ base_stats:
|
|||||||
intelligence: 12 # Above average magical power
|
intelligence: 12 # Above average magical power
|
||||||
wisdom: 14 # High perception/divine power
|
wisdom: 14 # High perception/divine power
|
||||||
charisma: 13 # Above average social
|
charisma: 13 # Above average social
|
||||||
|
luck: 11 # Divine favor grants fortune
|
||||||
|
|
||||||
starting_equipment:
|
starting_equipment:
|
||||||
- rusty_mace
|
- rusty_mace
|
||||||
- cloth_armor
|
- cloth_armor
|
||||||
- rusty_knife
|
- health_potion_small
|
||||||
|
|
||||||
starting_abilities:
|
starting_abilities:
|
||||||
- basic_attack
|
- basic_attack
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ description: >
|
|||||||
excel in draining enemies over time or overwhelming foes with undead minions.
|
excel in draining enemies over time or overwhelming foes with undead minions.
|
||||||
Choose your dark art: curse your enemies or raise an army of the dead.
|
Choose your dark art: curse your enemies or raise an army of the dead.
|
||||||
|
|
||||||
# Base stats (total: 65)
|
# Base stats (total: 65 + luck)
|
||||||
base_stats:
|
base_stats:
|
||||||
strength: 8 # Low physical power
|
strength: 8 # Low physical power
|
||||||
dexterity: 10 # Average agility
|
dexterity: 10 # Average agility
|
||||||
@@ -16,11 +16,12 @@ base_stats:
|
|||||||
intelligence: 14 # High magical power
|
intelligence: 14 # High magical power
|
||||||
wisdom: 11 # Above average perception
|
wisdom: 11 # Above average perception
|
||||||
charisma: 12 # Above average social (commands undead)
|
charisma: 12 # Above average social (commands undead)
|
||||||
|
luck: 7 # Dark arts come with a cost
|
||||||
|
|
||||||
starting_equipment:
|
starting_equipment:
|
||||||
- bone_wand
|
- bone_wand
|
||||||
- cloth_armor
|
- cloth_armor
|
||||||
- rusty_knife
|
- health_potion_small
|
||||||
|
|
||||||
starting_abilities:
|
starting_abilities:
|
||||||
- basic_attack
|
- basic_attack
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ description: >
|
|||||||
capable of becoming an unyielding shield for their allies or a beacon of healing light.
|
capable of becoming an unyielding shield for their allies or a beacon of healing light.
|
||||||
Choose your oath: defend the weak or redeem the fallen.
|
Choose your oath: defend the weak or redeem the fallen.
|
||||||
|
|
||||||
# Base stats (total: 67)
|
# Base stats (total: 67 + luck)
|
||||||
base_stats:
|
base_stats:
|
||||||
strength: 12 # Above average physical power
|
strength: 12 # Above average physical power
|
||||||
dexterity: 9 # Below average agility
|
dexterity: 9 # Below average agility
|
||||||
@@ -16,12 +16,13 @@ base_stats:
|
|||||||
intelligence: 10 # Average magic
|
intelligence: 10 # Average magic
|
||||||
wisdom: 12 # Above average perception
|
wisdom: 12 # Above average perception
|
||||||
charisma: 11 # Above average social
|
charisma: 11 # Above average social
|
||||||
|
luck: 9 # Honorable, modest fortune
|
||||||
|
|
||||||
starting_equipment:
|
starting_equipment:
|
||||||
- rusty_sword
|
- rusty_sword
|
||||||
- rusty_shield
|
- rusty_shield
|
||||||
- cloth_armor
|
- cloth_armor
|
||||||
- rusty_knife
|
- health_potion_small
|
||||||
|
|
||||||
starting_abilities:
|
starting_abilities:
|
||||||
- basic_attack
|
- basic_attack
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user