Update src/addons/example_npc_addon.py
Browse filesPotionseller. Give me your strongest haste potions.
For context: https://youtu.be/R_FQU4KzN7A?si=1KJGQ-TRotJmWqZi
- src/addons/example_npc_addon.py +270 -230
src/addons/example_npc_addon.py
CHANGED
@@ -1,230 +1,270 @@
|
|
1 |
-
"""
|
2 |
-
Example NPC Addon - Template for creating new NPCs with custom functionality
|
3 |
-
"""
|
4 |
-
|
5 |
-
import gradio as gr
|
6 |
-
from typing import Dict, Any
|
7 |
-
from src.interfaces.npc_addon import NPCAddon
|
8 |
-
|
9 |
-
|
10 |
-
class ExampleNPCAddon(NPCAddon):
|
11 |
-
"""Example NPC addon demonstrating the complete NPC creation process."""
|
12 |
-
|
13 |
-
def __init__(self):
|
14 |
-
super().__init__()
|
15 |
-
# Initialize any state or data your NPC needs
|
16 |
-
self.npc_inventory = {
|
17 |
-
"health_potion": {"name": "Health Potion", "price": 25, "stock": 10},
|
18 |
-
"magic_scroll": {"name": "Magic Scroll", "price": 50, "stock": 5},
|
19 |
-
"steel_sword": {"name": "Steel Sword", "price": 100, "stock": 3}
|
20 |
-
}
|
21 |
-
self.greeting_messages = [
|
22 |
-
"Welcome to my shop, adventurer!",
|
23 |
-
"Looking for some fine wares?",
|
24 |
-
"I have the best deals in town!",
|
25 |
-
"What can I help you with today?"
|
26 |
-
]
|
27 |
-
|
28 |
-
@property
|
29 |
-
def addon_id(self) -> str:
|
30 |
-
"""Unique identifier for this addon - must match NPC ID in world."""
|
31 |
-
return "example_merchant"
|
32 |
-
|
33 |
-
@property
|
34 |
-
def addon_name(self) -> str:
|
35 |
-
"""Display name shown in the interface."""
|
36 |
-
return "Example Merchant"
|
37 |
-
|
38 |
-
def get_interface(self) -> gr.Component:
|
39 |
-
"""Create the Gradio interface for this NPC."""
|
40 |
-
with gr.Column() as interface:
|
41 |
-
gr.Markdown("""
|
42 |
-
## 🏪 Example Merchant
|
43 |
-
|
44 |
-
*Welcome to my humble shop! I offer fine wares for brave adventurers.*
|
45 |
-
|
46 |
-
### Available Services:
|
47 |
-
- **Shop**: Browse and purchase items
|
48 |
-
- **Appraisal**: Get item value estimates
|
49 |
-
- **Information**: Learn about the local area
|
50 |
-
|
51 |
-
*Walk near me in the game world to unlock full functionality!*
|
52 |
-
""")
|
53 |
-
|
54 |
-
with gr.Tabs():
|
55 |
-
# Shop Tab
|
56 |
-
with gr.Tab("🛒 Shop"):
|
57 |
-
gr.Markdown("### Available Items")
|
58 |
-
|
59 |
-
with gr.Row():
|
60 |
-
item_select = gr.Dropdown(
|
61 |
-
label="Select Item",
|
62 |
-
choices=list(self.npc_inventory.keys()),
|
63 |
-
value=None
|
64 |
-
)
|
65 |
-
quantity = gr.Number(
|
66 |
-
label="Quantity",
|
67 |
-
value=1,
|
68 |
-
minimum=1,
|
69 |
-
maximum=10
|
70 |
-
)
|
71 |
-
|
72 |
-
with gr.Row():
|
73 |
-
buy_btn = gr.Button("💰 Purchase", variant="primary")
|
74 |
-
refresh_btn = gr.Button("🔄 Refresh Stock", variant="secondary")
|
75 |
-
|
76 |
-
purchase_result = gr.Textbox(
|
77 |
-
label="Transaction Result",
|
78 |
-
lines=3,
|
79 |
-
interactive=False
|
80 |
-
)
|
81 |
-
# Shop display
|
82 |
-
shop_display = gr.JSON(
|
83 |
-
label="Current Inventory",
|
84 |
-
value=self.npc_inventory
|
85 |
-
)
|
86 |
-
|
87 |
-
def handle_purchase(item_key, qty):
|
88 |
-
if not item_key:
|
89 |
-
return "❌ Please select an item first!"
|
90 |
-
|
91 |
-
# Get current player (simplified for example)
|
92 |
-
from ..core.game_engine import GameEngine
|
93 |
-
game_engine = GameEngine()
|
94 |
-
game_world = game_engine.get_world()
|
95 |
-
current_players = list(game_world.players.keys())
|
96 |
-
if not current_players:
|
97 |
-
|
98 |
-
|
99 |
-
|
100 |
-
|
101 |
-
|
102 |
-
|
103 |
-
|
104 |
-
|
105 |
-
|
106 |
-
|
107 |
-
|
108 |
-
|
109 |
-
|
110 |
-
|
111 |
-
|
112 |
-
|
113 |
-
|
114 |
-
|
115 |
-
|
116 |
-
|
117 |
-
|
118 |
-
|
119 |
-
|
120 |
-
- **
|
121 |
-
- **
|
122 |
-
|
123 |
-
|
124 |
-
|
125 |
-
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
|
132 |
-
|
133 |
-
|
134 |
-
|
135 |
-
|
136 |
-
|
137 |
-
|
138 |
-
|
139 |
-
|
140 |
-
|
141 |
-
-
|
142 |
-
- Reward:
|
143 |
-
|
144 |
-
|
145 |
-
-
|
146 |
-
- Reward:
|
147 |
-
|
148 |
-
|
149 |
-
|
150 |
-
|
151 |
-
|
152 |
-
|
153 |
-
|
154 |
-
|
155 |
-
|
156 |
-
|
157 |
-
|
158 |
-
|
159 |
-
|
160 |
-
|
161 |
-
|
162 |
-
|
163 |
-
|
164 |
-
|
165 |
-
|
166 |
-
|
167 |
-
|
168 |
-
|
169 |
-
|
170 |
-
|
171 |
-
|
172 |
-
|
173 |
-
|
174 |
-
|
175 |
-
|
176 |
-
|
177 |
-
|
178 |
-
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
|
185 |
-
|
186 |
-
|
187 |
-
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
item
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
|
211 |
-
|
212 |
-
|
213 |
-
**
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Example NPC Addon - Template for creating new NPCs with custom functionality
|
3 |
+
"""
|
4 |
+
|
5 |
+
import gradio as gr
|
6 |
+
from typing import Dict, Any
|
7 |
+
from src.interfaces.npc_addon import NPCAddon
|
8 |
+
|
9 |
+
|
10 |
+
class ExampleNPCAddon(NPCAddon):
|
11 |
+
"""Example NPC addon demonstrating the complete NPC creation process."""
|
12 |
+
|
13 |
+
def __init__(self):
|
14 |
+
super().__init__()
|
15 |
+
# Initialize any state or data your NPC needs
|
16 |
+
self.npc_inventory = {
|
17 |
+
"health_potion": {"name": "Health Potion", "price": 25, "stock": 10},
|
18 |
+
"magic_scroll": {"name": "Magic Scroll", "price": 50, "stock": 5},
|
19 |
+
"steel_sword": {"name": "Steel Sword", "price": 100, "stock": 3}
|
20 |
+
}
|
21 |
+
self.greeting_messages = [
|
22 |
+
"Welcome to my shop, adventurer!",
|
23 |
+
"Looking for some fine wares?",
|
24 |
+
"I have the best deals in town!",
|
25 |
+
"What can I help you with today?"
|
26 |
+
]
|
27 |
+
|
28 |
+
@property
|
29 |
+
def addon_id(self) -> str:
|
30 |
+
"""Unique identifier for this addon - must match NPC ID in world."""
|
31 |
+
return "example_merchant"
|
32 |
+
|
33 |
+
@property
|
34 |
+
def addon_name(self) -> str:
|
35 |
+
"""Display name shown in the interface."""
|
36 |
+
return "Example Merchant"
|
37 |
+
|
38 |
+
def get_interface(self) -> gr.Component:
|
39 |
+
"""Create the Gradio interface for this NPC."""
|
40 |
+
with gr.Column() as interface:
|
41 |
+
gr.Markdown("""
|
42 |
+
## 🏪 Example Merchant
|
43 |
+
|
44 |
+
*Welcome to my humble shop! I offer fine wares for brave adventurers.*
|
45 |
+
|
46 |
+
### Available Services:
|
47 |
+
- **Shop**: Browse and purchase items
|
48 |
+
- **Appraisal**: Get item value estimates
|
49 |
+
- **Information**: Learn about the local area
|
50 |
+
|
51 |
+
*Walk near me in the game world to unlock full functionality!*
|
52 |
+
""")
|
53 |
+
|
54 |
+
with gr.Tabs():
|
55 |
+
# Shop Tab
|
56 |
+
with gr.Tab("🛒 Shop"):
|
57 |
+
gr.Markdown("### Available Items")
|
58 |
+
|
59 |
+
with gr.Row():
|
60 |
+
item_select = gr.Dropdown(
|
61 |
+
label="Select Item",
|
62 |
+
choices=list(self.npc_inventory.keys()),
|
63 |
+
value=None
|
64 |
+
)
|
65 |
+
quantity = gr.Number(
|
66 |
+
label="Quantity",
|
67 |
+
value=1,
|
68 |
+
minimum=1,
|
69 |
+
maximum=10
|
70 |
+
)
|
71 |
+
|
72 |
+
with gr.Row():
|
73 |
+
buy_btn = gr.Button("💰 Purchase", variant="primary")
|
74 |
+
refresh_btn = gr.Button("🔄 Refresh Stock", variant="secondary")
|
75 |
+
|
76 |
+
purchase_result = gr.Textbox(
|
77 |
+
label="Transaction Result",
|
78 |
+
lines=3,
|
79 |
+
interactive=False
|
80 |
+
)
|
81 |
+
# Shop display
|
82 |
+
shop_display = gr.JSON(
|
83 |
+
label="Current Inventory",
|
84 |
+
value=self.npc_inventory
|
85 |
+
)
|
86 |
+
|
87 |
+
def handle_purchase(item_key, qty):
|
88 |
+
if not item_key:
|
89 |
+
return "❌ Please select an item first!"
|
90 |
+
|
91 |
+
# Get current player (simplified for example)
|
92 |
+
from ..core.game_engine import GameEngine
|
93 |
+
game_engine = GameEngine()
|
94 |
+
game_world = game_engine.get_world()
|
95 |
+
current_players = list(game_world.players.keys())
|
96 |
+
if not current_players:
|
97 |
+
if item_key == "health_potion":
|
98 |
+
return "❌ My potions are to strong for you traveller. ... You can t handle my potions, they are to strong for you. MY potions would kill you traveller. You can not handle them. You are too weak for my potions! They will kill you! It is only for the strongest! Come to the game world and get stronger!"
|
99 |
+
if item_key == "strongest_haste_potions."
|
100 |
+
return "❌ You are too weak for this potion! It will kill you! It is only for the strongest! You are not amoung the Come to the game world and get stronger!"
|
101 |
+
return "❌ You must be in the game to make purchases!"
|
102 |
+
|
103 |
+
player_id = max(current_players, key=lambda pid: game_world.players[pid].last_active)
|
104 |
+
return self.purchase_item(player_id, item_key, int(qty))
|
105 |
+
|
106 |
+
def refresh_shop():
|
107 |
+
return self.npc_inventory
|
108 |
+
|
109 |
+
# Wire up the interface
|
110 |
+
buy_btn.click(handle_purchase, [item_select, quantity], purchase_result)
|
111 |
+
refresh_btn.click(refresh_shop, outputs=shop_display)
|
112 |
+
|
113 |
+
# Information Tab
|
114 |
+
with gr.Tab("ℹ️ Information"):
|
115 |
+
gr.Markdown("""
|
116 |
+
### 📍 Local Area Information
|
117 |
+
|
118 |
+
- **Location**: Village Market Square
|
119 |
+
- **Trading Hours**: Always open for brave adventurers
|
120 |
+
- **Specialties**: Weapons, potions, and magical items
|
121 |
+
- **Payment**: Gold coins accepted
|
122 |
+
|
123 |
+
### 🗺️ Nearby Locations
|
124 |
+
- **Village Elder**: North of here, near the fountain
|
125 |
+
- **Training Grounds**: East side of village
|
126 |
+
- **Mystic Oracle**: In the tower to the south
|
127 |
+
""")
|
128 |
+
|
129 |
+
info_btn = gr.Button("📋 Get Quest Information")
|
130 |
+
quest_info = gr.Textbox(
|
131 |
+
label="Available Quests",
|
132 |
+
lines=5,
|
133 |
+
interactive=False
|
134 |
+
)
|
135 |
+
|
136 |
+
def get_quest_info():
|
137 |
+
return """
|
138 |
+
🗡️ **Available Quests:**
|
139 |
+
|
140 |
+
1. **Goblin Problem** (Level 1-3)
|
141 |
+
- Clear goblins from the eastern caves
|
142 |
+
- Reward: 100 gold + basic equipment
|
143 |
+
|
144 |
+
2. **Herb Collection** (Level 1-2)
|
145 |
+
- Gather 10 healing herbs from the forest
|
146 |
+
- Reward: 50 gold + health potion
|
147 |
+
|
148 |
+
3. **Lost Merchant** (Level 3-5)
|
149 |
+
- Find the missing merchant on the trade route
|
150 |
+
- Reward: 200 gold + rare item
|
151 |
+
"""
|
152 |
+
|
153 |
+
info_btn.click(get_quest_info, outputs=quest_info)
|
154 |
+
|
155 |
+
return interface
|
156 |
+
|
157 |
+
def handle_command(self, player_id: str, command: str) -> str:
|
158 |
+
"""Handle commands sent via private messages to this NPC."""
|
159 |
+
command_lower = command.lower().strip()
|
160 |
+
# Get player info safely
|
161 |
+
from ..core.game_engine import GameEngine
|
162 |
+
game_engine = GameEngine()
|
163 |
+
game_world = game_engine.get_world()
|
164 |
+
player = game_world.players.get(player_id)
|
165 |
+
if not player:
|
166 |
+
return "❌ Player not found!"
|
167 |
+
|
168 |
+
player_name = player.name
|
169 |
+
|
170 |
+
# Command parsing
|
171 |
+
if any(word in command_lower for word in ['hello', 'hi', 'greeting', 'hey']):
|
172 |
+
import random
|
173 |
+
return f"🏪 {random.choice(self.greeting_messages)} {player_name}!"
|
174 |
+
|
175 |
+
elif any(word in command_lower for word in ['shop', 'buy', 'purchase', 'item']):
|
176 |
+
return self._get_shop_summary()
|
177 |
+
|
178 |
+
elif any(word in command_lower for word in ['quest', 'mission', 'task']):
|
179 |
+
return "📜 I have information about local quests! Visit my Information tab for details."
|
180 |
+
|
181 |
+
elif any(word in command_lower for word in ['help', 'commands']):
|
182 |
+
return """
|
183 |
+
🏪 **Available Commands:**
|
184 |
+
- **hello/hi**: Friendly greeting
|
185 |
+
- **shop/buy**: View available items
|
186 |
+
- **quest**: Learn about available quests
|
187 |
+
- **help**: Show this help message
|
188 |
+
|
189 |
+
*Visit the NPC Add-ons tab for my full interface!*
|
190 |
+
"""
|
191 |
+
|
192 |
+
else:
|
193 |
+
return f"🤔 Interesting words, {player_name}! Try 'help' to see what I can do, or visit my shop interface in the NPC Add-ons tab!"
|
194 |
+
|
195 |
+
def purchase_item(self, player_id: str, item_key: str, quantity: int) -> str:
|
196 |
+
"""Handle item purchase logic."""
|
197 |
+
if item_key not in self.npc_inventory:
|
198 |
+
return "❌ Item not found in inventory!"
|
199 |
+
|
200 |
+
item = self.npc_inventory[item_key]
|
201 |
+
total_cost = item["price"] * quantity
|
202 |
+
|
203 |
+
if item["stock"] < quantity:
|
204 |
+
return f"❌ Not enough stock! Only {item['stock']} available."
|
205 |
+
|
206 |
+
# Here you would check player's gold and deduct it
|
207 |
+
# For this example, we'll simulate the transaction
|
208 |
+
|
209 |
+
# Update inventory
|
210 |
+
self.npc_inventory[item_key]["stock"] -= quantity
|
211 |
+
|
212 |
+
return f"""
|
213 |
+
✅ **Purchase Successful!**
|
214 |
+
|
215 |
+
**Item**: {item['name']} x{quantity}
|
216 |
+
**Cost**: {total_cost} gold
|
217 |
+
**Remaining Stock**: {self.npc_inventory[item_key]['stock']}
|
218 |
+
|
219 |
+
*Thank you for your business, adventurer!*
|
220 |
+
"""
|
221 |
+
|
222 |
+
def _get_shop_summary(self) -> str:
|
223 |
+
"""Get a summary of available shop items."""
|
224 |
+
summary = "🏪 **Shop Inventory:**\n\n"
|
225 |
+
for key, item in self.npc_inventory.items():
|
226 |
+
if item["stock"] > 0:
|
227 |
+
summary += f"• **{item['name']}** - {item['price']} gold (Stock: {item['stock']})\n"
|
228 |
+
|
229 |
+
summary += "\n*Visit my shop interface in the NPC Add-ons tab to purchase items!*"
|
230 |
+
return summary
|
231 |
+
|
232 |
+
|
233 |
+
# Global instance for auto-registration
|
234 |
+
example_merchant_addon = ExampleNPCAddon()
|
235 |
+
|
236 |
+
|
237 |
+
def auto_register(game_engine):
|
238 |
+
"""Auto-register the Example NPC addon with the game engine."""
|
239 |
+
try:
|
240 |
+
# Create addon instance
|
241 |
+
addon_instance = ExampleNPCAddon()
|
242 |
+
|
243 |
+
# Get NPC config
|
244 |
+
npc_config = {
|
245 |
+
'id': 'example_merchant',
|
246 |
+
'name': '🏪 Example Merchant',
|
247 |
+
'x': 300, 'y': 250,
|
248 |
+
'char': '🏪',
|
249 |
+
'type': 'trader',
|
250 |
+
'personality': 'friendly'
|
251 |
+
}
|
252 |
+
|
253 |
+
# Register NPC in world
|
254 |
+
npc_service = game_engine.get_npc_service()
|
255 |
+
npc_service.register_npc(npc_config['id'], npc_config)
|
256 |
+
|
257 |
+
# Register addon for command handling
|
258 |
+
if not hasattr(game_engine.get_world(), 'addon_npcs'):
|
259 |
+
game_engine.get_world().addon_npcs = {}
|
260 |
+
game_engine.get_world().addon_npcs[npc_config['id']] = addon_instance
|
261 |
+
|
262 |
+
# Call startup
|
263 |
+
addon_instance.on_startup()
|
264 |
+
|
265 |
+
print(f"[ExampleNPCAddon] Auto-registered successfully as self-contained addon")
|
266 |
+
return True
|
267 |
+
|
268 |
+
except Exception as e:
|
269 |
+
print(f"[ExampleNPCAddon] Error during auto-registration: {e}")
|
270 |
+
return False
|