class_name Quest extends Resource #The list of available quests static var list : Array[Quest] static var last_id : int = 1 enum Status{ OPEN, TAKEN, IN_PROGRESS, COMPLETED, FAILED, CLOSED } enum Locations{ VOID, NESTORS_WOODS } class Event: enum Type{ WAIT, COMBAT, CHOICE } enum CombatState{ FIGHTING, VICTORY, DEFEAT } var hidden : bool = false var type : Type = Type.WAIT var enemies : Array[String] = [] var progress_point : float = 0 var time : float = 1 var time_elapsed var complete : bool = false signal completed() signal failed() var participants : Array = [] var turn_queue : Array = [] var busy_list : Array = [] var combat_state var dex_speed : int func setup() -> void: pass func save() -> Dictionary: var d : Dictionary = {} d.hidden = hidden d.type = type d.enemies = enemies d.progress_point = progress_point d.time = time d.time_elapsed = time_elapsed d.complete = complete return d func start(quest : Quest) -> void: match(type): Type.WAIT: return Type.COMBAT: combat_state = CombatState.FIGHTING var enemy_list = [] for enemy_name in enemies: enemy_list.append(Enemy.list[enemy_name].instantiate()) quest.questview.set_questor_animation("idle") for enemy in enemy_list: enemy.flip_h() quest.questview.pause_setting() quest.questview.place_enemy(enemy, true) quest.questview.set_enemy_animation(enemy, "idle") start_combat([quest.questor.quest_sprite], enemy_list) func start_combat(adventurers : Array, enemies : Array) -> void: participants = [] participants.append_array(adventurers) participants.append_array(enemies) time = 1.25 time_elapsed = 0 var c_order : Array = [] var dex_speed = 0 for p in participants: p.show_lifebar(true) c_order.append([p, p.stats.DEX]) if p.stats.DEX > dex_speed: dex_speed = p.stats.DEX c_order.sort_custom(func(a,b): return a[1] > b[1]) var delay = 5 var last_time = 0 for c in c_order: c[0].busy.connect(_on_busy.bind(c[0])) c[0].action_complete.connect(_on_combat_action_complete.bind(c[0])) c[0].died.connect(_on_death.bind(c[0])) var time = delay * dex_speed if c[1] != 0: time = delay * dex_speed / c[1] turn_queue.append({"combatant":c[0], "time": time - last_time}) last_time = time func execute_attack(combatant : QuestSprite, target : QuestSprite) -> void: var tween = combatant.create_tween() tween.tween_interval(.25) tween.tween_callback(combatant.attack.bind(target)) func add_to_turn_queue(combatant) -> void: #Calculate time var time = dex_speed / max(1, combatant.stats.DEX) #Walk through list to find insertion point var idx = -1 for i in range(len(turn_queue)): if turn_queue[i].time > time: idx = i break else: time -= turn_queue[i].time var entry = {"combatant":combatant, "time":time} if idx == -1: turn_queue.append(entry) else: turn_queue[idx].time -= time turn_queue.insert(idx,entry) func _on_busy(combatant : QuestSprite) -> void: busy_list.append(combatant) func _on_death(killer : QuestSprite, combatant : QuestSprite) -> void: busy_list.erase(combatant) remove_from_queue(combatant) participants.erase(combatant) if killer != combatant: var xp = Game.calculate_kill_exp(killer, combatant) print("%s has earned %d exp" % [killer.name, xp]) killer.exp += xp var enemy_list : Array = get_enemy_list(killer) if len(enemy_list) == 0: if killer is QuestorSprite: victory() else: defeat() func victory(): print("Questor won!") combat_state = CombatState.VICTORY for p : QuestorSprite in participants: p.check_levelup() #TODO: Notify player if level up occurs time = 3 time_elapsed = 0 func defeat(): print("Questor lost!") combat_state = CombatState.DEFEAT failed.emit() func remove_from_queue(combatant : QuestSprite) -> void: var idx = -1 for i in range(len(turn_queue)): if turn_queue[i].combatant == combatant: idx = i break if idx != -1: turn_queue.remove_at(idx) else: printerr("Tried to remove someone not in the turn queue") func _on_combat_action_complete(requeue : bool, combatant : QuestSprite) -> void: if combatant.position != combatant.reset_position: combatant.position_reset() combatant.arrived.connect(_on_combat_action_complete.bind(requeue, combatant), CONNECT_ONE_SHOT) return busy_list.erase(combatant) if requeue: add_to_turn_queue(combatant) func execute_action(combatant) -> void: busy_list = [combatant] #TODO: Come up with other options than just swinging at each other var enemies : Array = get_enemy_list(combatant) var target = enemies.pick_random() combatant.approach(target, combatant.melee_range) combatant.arrived.connect(execute_attack.bind(combatant, target), CONNECT_ONE_SHOT) func get_enemy_list(combatant) -> Array: var lst = [] for p in participants: if p != combatant: lst.append(p) return lst func resolve_combat() -> void: for p in participants: p.show_lifebar(false) pass func process(delta : float) -> void: #TODO: Make quest combat work match(type): Type.COMBAT: if time != 0: time_elapsed += delta if time_elapsed >= time: time = 0 time_elapsed = 0 else: return match(combat_state): CombatState.FIGHTING: if len(busy_list) < 1: if len(turn_queue) > 0: turn_queue[0].time -= delta if turn_queue[0].time <= 0: var c = turn_queue.pop_front() print("%s taking a turn!" % [c.combatant.name]) if len(turn_queue) > 0: turn_queue[0].time += c.time execute_action(c.combatant) else: resolve_combat() CombatState.VICTORY: resolve_combat() complete = true completed.emit() Type.WAIT: time_elapsed += delta if time_elapsed >= time: complete = true completed.emit() var id : int var base_name : String = "" var name : String = "A Basic Quest" var desc : String = "The default quest, with no special anything." var difficulty : int = 1 var location : Locations var steps : int = 1 var rewards : Dictionary var guild_rewards : Dictionary var covenant_cost : int = 1 var length : float = 10 var events : Array[Event] = [] var progress : float = 0 var current_step : int = 0 var taken : bool = false var status : Status = Status.OPEN var questview : QuestView = null var questor : Adventurer = null signal status_changed(status : Status) func _init() -> void: last_id += 1 id = last_id pass func initiate(member : Adventurer) -> void: questor = member status = Status.TAKEN status_changed.emit(Status.TAKEN) func fail() -> void: status = Status.FAILED status_changed.emit(Status.FAILED) func complete() -> void: status = Status.COMPLETED status_changed.emit(Status.COMPLETED) questview.show_quest_complete() for reward in rewards.keys(): if reward == "gold": questor.gain_gold(rewards[reward]) elif reward == "exp": questor.gain_exp(rewards[reward]) #TODO: Implement other reward types #elif rewards[reward] is Item: # questor.gain_item() #else it's a guild item they'll bring back for us Game.notice("%s completed the quest '%s'!" % [questor.full_name(), name]) func num_events() -> int: return len(events) #TODO: Put in quest requirements func is_eligible(member : Adventurer) -> bool: return !taken func is_taken() -> bool: return status == Status.TAKEN static func location_name(location : Locations) -> String: match(location): Locations.VOID: return "The Endless Void" Locations.NESTORS_WOODS: return "Nestor's Woods" return "ERROR" func get_location_name() -> String: return Quest.location_name(location) func difficulty_name() -> String: match(difficulty): 0: return "None" 1: return "Trivial" 2: return "Moderate" 3: return "Severe" 4: return "Extreme" 5: return "Legendary" _: return "Unknown" static func load_quest_list() -> void: var path = ProjectSettings.get_setting_with_override("data/quests/directory") var dir = DirAccess.open(path) var quest : Quest if dir: dir.list_dir_begin() var filename = dir.get_next() while filename != "": if not dir.current_is_dir() and filename.get_extension() == "gd": var file = load(path + "/" + filename).new() if file is Quest: list.append(file) filename = dir.get_next() dir.list_dir_end() static func generate(parameters : Dictionary) -> Quest: var candidates : Array[Quest] = [] var l = list for q in list: if parameters.location != -1 and q.location != parameters.location: continue if q.difficulty < parameters.min_difficulty or q.difficulty > parameters.max_difficulty: continue candidates.append(q) var choice : Quest = candidates.pick_random() return choice.duplicate() func save() -> Dictionary: var d : Dictionary = {} d.id = id d.name = name d.base_name = base_name d.desc = desc d.difficulty = difficulty d.location = location d.steps = steps #TODO: Convert these! #d.rewards = rewards #d.guild_rewards = guild_rewards d.covenant_cost = covenant_cost d.length = length d.progress = progress d.current_step = current_step d.taken = taken d.status = status var lst : Array = [] for evt in events: lst.append(evt.save()) d.events = lst return d