class_name Quest extends Resource enum Status{ OPEN, TAKEN, IN_PROGRESS, COMPLETED, FAILED, CLOSED } enum Locations{ NESTORS_WOODS } class Event: var enemy_types: Dictionary[String, PackedScene] = { "goo": preload("res://templates/enemies/goo.tscn") } enum Type{ WAIT, COMBAT, CHOICE } enum CombatState{ FIGHTING, VICTORY, DEFEAT } var type : Type = Type.WAIT var enemies : Array[String] = [] var time : float = 1 var time_elapsed signal completed() signal failed() var participants : Array = [] var turn_queue : Array = [] var busy_list : Array = [] var combat_state var dex_speed : int 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_types[enemy_name].instantiate()) quest.questview.set_questor_animation("idle") for enemy in enemy_list: quest.questview.pause_setting() quest.questview.place_enemy(enemy) 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) var c_order : Array = [] var dex_speed = 0 for p in participants: 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 / 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 = 5 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: 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() execute_attack(combatant, target) func get_enemy_list(combatant) -> Array: var lst = [] for p in participants: if p != combatant: lst.append(p) return lst func resolve_combat() -> void: pass func process(delta : float) -> void: #TODO: Make quest combat work match(type): Type.COMBAT: 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: time_elapsed += delta if time_elapsed >= time: completed.emit() Type.WAIT: time_elapsed += delta if time_elapsed >= time: completed.emit() 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: 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) 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 func location_name() -> String: match(location): Locations.NESTORS_WOODS: return "Nestor's Woods" return "ERROR" 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"