222 lines
6.5 KiB
GDScript
222 lines
6.5 KiB
GDScript
extends Node
|
|
|
|
signal room_changed(room_data: RoomData)
|
|
signal log_message(message: String)
|
|
signal stats_changed(hp: int, max_hp: int, inventory: Array)
|
|
signal combat_started(enemy: Resource)
|
|
signal combat_ended(won: bool)
|
|
signal combat_log(message: String)
|
|
signal damage_taken(amount: int)
|
|
|
|
var game_state: GameState
|
|
var rooms: Dictionary = {} # id -> RoomData
|
|
var llm_service: LLMService
|
|
var audio_manager: Node
|
|
var current_enemy: Resource = null
|
|
|
|
func _ready():
|
|
llm_service = LLMService.new()
|
|
add_child(llm_service)
|
|
llm_service.response_received.connect(_on_llm_response)
|
|
llm_service.error_occurred.connect(func(msg): emit_signal("log_message", "Error: " + msg))
|
|
|
|
audio_manager = load("res://scripts/AudioManager.gd").new()
|
|
add_child(audio_manager)
|
|
|
|
rooms = WorldData.generate_procedural_world(20)
|
|
start_new_game()
|
|
|
|
func start_new_game():
|
|
game_state = GameState.new()
|
|
game_state.current_room_id = "room_0_0"
|
|
_update_stats_ui()
|
|
_load_room(game_state.current_room_id)
|
|
|
|
func _load_room(room_id: String):
|
|
if room_id in rooms:
|
|
var room = rooms[room_id]
|
|
|
|
# Update Exploration
|
|
if not room_id in game_state.explored_rooms:
|
|
game_state.explored_rooms.append(room_id)
|
|
|
|
emit_signal("room_changed", room)
|
|
|
|
# Handle Description
|
|
if room.generated_description != "":
|
|
emit_signal("log_message", room.generated_description)
|
|
else:
|
|
_request_room_description(room)
|
|
else:
|
|
emit_signal("log_message", "Error: Room " + room_id + " not found.")
|
|
|
|
func move(direction: String):
|
|
var current_room = rooms.get(game_state.current_room_id)
|
|
if current_room and direction in current_room.exits:
|
|
game_state.current_room_id = current_room.exits[direction]
|
|
_load_room(game_state.current_room_id)
|
|
else:
|
|
emit_signal("log_message", "You can't go that way.")
|
|
|
|
func pickup_item(item_name: String):
|
|
var current_room = rooms[game_state.current_room_id]
|
|
var item_to_pickup = null
|
|
|
|
for item in current_room.items:
|
|
if item.name.to_lower() in item_name.to_lower() or item.id == item_name.to_lower():
|
|
item_to_pickup = item
|
|
break
|
|
|
|
if item_to_pickup:
|
|
current_room.items.erase(item_to_pickup)
|
|
game_state.inventory.append(item_to_pickup)
|
|
_update_stats_ui()
|
|
emit_signal("log_message", "You picked up " + item_to_pickup.name)
|
|
else:
|
|
emit_signal("log_message", "There is no " + item_name + " here.")
|
|
|
|
func save_game():
|
|
var error = ResourceSaver.save(game_state, "user://savegame.tres")
|
|
if error == OK:
|
|
emit_signal("log_message", "Game Saved.")
|
|
else:
|
|
emit_signal("log_message", "Error saving game: " + str(error))
|
|
|
|
func load_game():
|
|
if ResourceLoader.exists("user://savegame.tres"):
|
|
game_state = ResourceLoader.load("user://savegame.tres")
|
|
_update_stats_ui()
|
|
_load_room(game_state.current_room_id)
|
|
emit_signal("log_message", "Game Loaded.")
|
|
else:
|
|
emit_signal("log_message", "No save file found.")
|
|
|
|
func _update_stats_ui():
|
|
emit_signal("stats_changed", game_state.player_hp, game_state.player_max_hp, game_state.inventory)
|
|
|
|
# --- Combat System ---
|
|
|
|
func start_combat(enemy: Resource):
|
|
current_enemy = enemy
|
|
emit_signal("combat_started", enemy)
|
|
emit_signal("log_message", "Combat started with " + enemy.name + "!")
|
|
|
|
func player_attack():
|
|
if not current_enemy: return
|
|
|
|
# Calculate damage (simple for now)
|
|
var damage = 5 # Base damage
|
|
# Check for weapon
|
|
for item in game_state.inventory:
|
|
if item.effect_type == "DAMAGE":
|
|
damage += item.effect_value
|
|
|
|
current_enemy.hp -= damage
|
|
audio_manager.play_hit()
|
|
emit_signal("combat_log", "You hit " + current_enemy.name + " for " + str(damage) + " damage.")
|
|
|
|
if current_enemy.hp <= 0:
|
|
_win_combat()
|
|
else:
|
|
_enemy_turn()
|
|
|
|
func _enemy_turn():
|
|
var damage = current_enemy.damage
|
|
game_state.player_hp -= damage
|
|
_update_stats_ui()
|
|
audio_manager.play_hit()
|
|
emit_signal("damage_taken", damage)
|
|
emit_signal("combat_log", current_enemy.name + " hits you for " + str(damage) + " damage.")
|
|
|
|
if game_state.player_hp <= 0:
|
|
emit_signal("log_message", "You died!")
|
|
# Handle death (reload?)
|
|
|
|
func _win_combat():
|
|
emit_signal("combat_log", "You defeated " + current_enemy.name + "!")
|
|
# Remove enemy from room
|
|
var room = rooms[game_state.current_room_id]
|
|
room.enemies.erase(current_enemy)
|
|
current_enemy = null
|
|
emit_signal("combat_ended", true)
|
|
|
|
func flee():
|
|
emit_signal("log_message", "You fled!")
|
|
current_enemy = null
|
|
emit_signal("combat_ended", false)
|
|
|
|
# --- AI Integration ---
|
|
|
|
func process_user_input(text: String):
|
|
emit_signal("log_message", "> " + text)
|
|
var prompt = _construct_system_prompt()
|
|
llm_service.send_prompt(prompt, text)
|
|
|
|
func _construct_system_prompt() -> String:
|
|
var room = rooms[game_state.current_room_id]
|
|
var prompt = """
|
|
You are the Game Master for a text adventure.
|
|
Current Room: %s
|
|
Description: %s
|
|
Exits: %s
|
|
|
|
Your goal is to interpret the user's input and return a JSON object describing the outcome.
|
|
|
|
JSON Format:
|
|
{
|
|
"narrative": "Description of what happens...",
|
|
"action": {
|
|
"type": "MOVE" | "PICKUP" | "COMBAT" | "NONE",
|
|
"target": "north" | "item_name" | "enemy_name" | null
|
|
}
|
|
}
|
|
|
|
Rules:
|
|
- If user says "go north" and north is an exit, return action type "MOVE", target "north".
|
|
- If user says "attack goblin" and goblin is in room, return action type "COMBAT", target "goblin".
|
|
- If user says "look", just describe the room in "narrative".
|
|
- Keep narrative concise (2-3 sentences).
|
|
- Do NOT list player stats or inventory in the narrative.
|
|
""" % [room.room_name, room.description, str(room.exits.keys())]
|
|
return prompt
|
|
|
|
func _request_room_description(room: RoomData):
|
|
var prompt = """
|
|
You are a creative writer for a text adventure.
|
|
Describe the following location.
|
|
Name: %s
|
|
Base Details: %s
|
|
Exits: %s
|
|
|
|
Output JSON:
|
|
{
|
|
"narrative": "The atmospheric description...",
|
|
"save_as_description": true
|
|
}
|
|
""" % [room.room_name, room.description, str(room.exits.keys())]
|
|
|
|
llm_service.send_prompt(prompt, "Describe this place.")
|
|
|
|
func _on_llm_response(response: Dictionary):
|
|
if "narrative" in response:
|
|
emit_signal("log_message", response["narrative"])
|
|
|
|
if response.get("save_as_description") == true:
|
|
var room = rooms[game_state.current_room_id]
|
|
room.generated_description = response["narrative"]
|
|
|
|
if "action" in response:
|
|
var action = response["action"]
|
|
match action.get("type"):
|
|
"MOVE":
|
|
move(action.get("target"))
|
|
"PICKUP":
|
|
pickup_item(action.get("target"))
|
|
"COMBAT":
|
|
var target = action.get("target")
|
|
var room = rooms[game_state.current_room_id]
|
|
for enemy in room.enemies:
|
|
if enemy.name.to_lower() in target.to_lower() or enemy.id == target.to_lower():
|
|
start_combat(enemy)
|
|
break
|